Correct GraphicsContext When Using UIGraphicsGetImageFromCurrentImageContext - iphone

I have the following method where I'm trying to do some drawing into an image:
- (UIImage*) renderImage
{
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//drawing code
UIImage *image = [UIGraphicsGetImageFromCurrentImageContext() retain];
UIGraphicsEndImageContext();
return [image autorelease];
}
When I run this code I noticed that I'm getting hit much harder than I did when I was simply drawing this code in drawRect of a UIView. Am I drawing into the wrong graphics context here (ie CGContextRef context = UIGraphicsGetCurrentContext();)? Or is UIGraphicsGetImageFromCurrentImageContext just that much more expensive than drawing in drawRect?

The main difference is that the context that you create requires an offscreen rendering, it isn't the same context created in -drawRect. So you are adding an additional memory to the heap that stays until you will release the image.

Related

How to draw a Mirrored UIImage in UIView with drawRect?

I load a image and create a mirror in this way:
originalImg = [UIImage imageNamed:#"ms06.png"];
mirrorImg = [UIImage imageWithCGImage:[originalImg CGImage] scale:1.0 orientation:UIImageOrientationUpMirrored];
Then I set the above UIImage object to a subclass of UIView, and override the drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform t0 = CGContextGetCTM(context);
CGContextConcatCTM(context, t0);
CGContextDrawImage(context, self.bounds, [image CGImage]);
CGContextRestoreGState(context);
}
No matter which image I draw, the displayed image always be the original one, mirrored image was never displayed when set to the UIView subclass.
I'm sure that the mirrored image was set to the UIView correctly because the debug info showed that the orientation member variable equals to 4, which means "UIImageOrientationUpMirrored", while the original image equals to 0.
Does anyone could help me with this problem, thanks.
I also tried to display the mirrored image in UIImageView with setImage: and it works correctly. By the way I found that the breakpoint in drawRect was never hit when call the setImage of UIImageView, how can we define the drawing behavior(such as draw a line above the image) when loading image to the UIImageView?
You mirrow the image on UI-Level. This returns a new UIImage, but the CGImage stays the same. If you do some NSLogs, you will notice this.
You can also do transformations on UI-Level. If you use this approach, I would suggest to use originalImg.scale instead of 1.0. Then the code would work for retina and non-retina displays.
[UIImage imageWithCGImage:[originalImg CGImage] scale:originalImg.scale orientation:UIImageOrientationUpMirrored];
If you really need to mirror the CGImage, take a look at NYXImagesKit on GitHub (see UIImage+Rotating.m)

UIImage returned from UIGraphicsGetImageFromCurrentImageContext leaks

The screenshot of Leak Profiling in Instruments Tool: http://i.stack.imgur.com/rthhI.png
I found my UIImage objects leaking using Instruments tool.
Per Apple's documentation, the object returned from UIGraphicsGetImageFromCurrentImageContext should be autoreleased, I can also see "Autorelease" event when profiling (see the first 2 lines of history of my attached screenshot). However, it seems that the "autorelease" event takes no effect. Why?
EDIT:
Code attached, I use the below code to "mix" two UIImages, also, later on, I use a UIMutableDictionary to cache those UIImage I "mixed". And I'm quite sure that I've called [UIMutableDictionary removeAllObjects] to clear the cache, so those UIImages "should be cleaned"
+ (UIImage*) mixUIImage:(UIImage*)i1 :(UIImage*)i2 :(CGPoint)i1Offset :(CGPoint)i2Offset{
CGFloat width , height;
if (i1) {
width = i1.size.width;
height = i1.size.height;
}else if(i2){
width = i2.size.width;
height = i2.size.height;
}else{
width = 1;
height = 1;
}
// create a new bitmap image context
//
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, i1.scale);
// get context
//
CGContextRef context = UIGraphicsGetCurrentContext();
// push context to make it current
// (need to do this manually because we are not drawing in a UIView)
//
UIGraphicsPushContext(context);
// drawing code comes here- look at CGContext reference
// for available operations
//
// this example draws the inputImage into the context
//
[i2 drawInRect:CGRectMake(i2Offset.x, i2Offset.y, width, height)];
[i1 drawInRect:CGRectMake(i1Offset.x, i1Offset.y, width, height)];
// pop context
//
UIGraphicsPopContext();
// get a UIImage from the image context- enjoy!!!
//
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
//
UIGraphicsEndImageContext();
return outputImage;
}
I was getting a strange UIImage memory leak using a retained UIImage image from UIGraphicsGetImageFromCurrentImageContext(). I was calling it in a background thread (in response to a timer event). The problem turned out to be - as mentioned deep in the documentation by apple - "you should only call this function from the main thread of your application". Beware.

How can I save a photo of part of the screen to the local iPhone's Photos?

I have put a UILabel that the user has chosen over a UIImageView that was also chosen by the user. I would like to put these two into a picture, kind of like a screenshot of a small part of the screen. I have absolutely no idea how to do this and have no experience in this. Any help is appreciated!!
You could setup a Bitmap context with a clipping mask of the area you want to save. Then you use the backing layer's renderInContext method to draw onto that context.
CGSize imageSize = CGSizeMake(960, 580);
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToRect(context, CGRectMake(10,10,200,200); // whatever rect you want
[self.layer renderInContext:context];
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Save to camera roll
UIImageWriteToSavedPhotosAlbum(myImage, self, #selector(didSaveImage), null);

What's the correct code to save a CGLayer as a PNG file?

Please note that this question is about CGLayer (which you typically use to draw offscreen), it is not about CALayer.
In iOS, what's the correct code to save a CGLayer as a PNG file? Thanks!
Again, that's CGLayer, not CALayer.
Note that you CAN NOT use UIGraphicsGetImageFromCurrentImageContext.
(From the documentation, "You can call UIGraphicsGetImageFromCurrentImageContext only when a bitmap-based graphics context is the current graphics context.")
Note that you CAN NOT use renderInContext:. renderInContext: is strictly for CALayers. CGLayers are totally different.
So, how can you actually convert a CGLayer to a PNG image? Or indeed, how to render a CGLayer in to a bitmap in some way (of course you can then easily save as an image).
Later ... Ken has answered this difficult question. I will paste in a long example code that may help people. Thanks again Ken! Amazing!
-(void)drawingExperimentation
{
// this code uses the ASTOUNDING solution by KENNYTM -- Oct/Nov2010
//
// create a CGLayer for offscreen drawing
// note. for "yourContext", ideally it should be a context from your screen, ie the
// context you "normally get" in one of your drawRect routines associated with
// drawing to the screen normally.
// UIGraphicsGetCurrentContext() also normally works but you could have colorspace woes
// so create the CGLayer called notepad...
CGLayerRef notepad = CGLayerCreateWithContext(yourContext,CGSizeMake(1500,1500), NULL);
CGContextRef notepadContext = CGLayerGetContext(notepad);
// you can for example write an image in to notepad
CGImageRef imageExamp = [[UIImage imageWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:#"smallTestImage" ofType:#"png"] ] CGImage];
CGContextDrawImage( notepadContext, CGRectMake(100,100, 50,50), imageExamp);
// setting the colorspace may or may not be relevant to you
CGContextSetFillColorSpace( notepadContext, CGColorSpaceCreateDeviceRGB() );
// you can draw to notepad as much as you like in the normal way
// don't forget to push it's context on and off your work space so you can draw to it
UIGraphicsPushContext(notepadContext);
// set the colors
CGContextSetRGBFillColor(notepadContext, 0.15,0.25,0.35, 0.45);
// draw rects
UIRectFill(CGRectMake(x,y,w,h));
// draw ovals, filled stroked or whatever you wish
UIBezierPath* d = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x,y,w,h)];
[d fill];
// draw cubic and other curves
UIBezierPath *longPath;
longPath.lineWidth = 42;
longPath.lineCapStyle = kCGLineCapRound;
longPath.lineJoinStyle = kCGLineJoinRound;
[longPath moveToPoint:p];
[longPath addCurveToPoint:q controlPoint1:r controlPoint2:s];
[longPath addCurveToPoint:a controlPoint1:b controlPoint2:c];
[longPath addCurveToPoint:m controlPoint1:n controlPoint2:o];
[longPath closePath];
[longPath stroke];
UIGraphicsPopContext();
// so now you have a nice CGLayer.
// how to save it to a file?
// you can save it to a file using the amazing KENNY-TM-METHOD !!!
UIGraphicsBeginImageContext( CGLayerGetSize(notepad) );
CGContextRef rr = UIGraphicsGetCurrentContext();
CGContextDrawLayerAtPoint(rr, CGPointZero, notepad);
UIImage* ii = UIGraphicsGetImageFromCurrentImageContext();
NSData* pp = UIImagePNGRepresentation(ii);
[pp writeToFile:#"foo.png" atomically:YES];
UIGraphicsEndImageContext();
// you may prefer to look at it like this:
UIGraphicsBeginImageContext( CGLayerGetSize(notepad) );
CGContextDrawLayerAtPoint(UIGraphicsGetCurrentContext(), CGPointZero, notepad);
[UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()) writeToFile:#"foo.png" atomically:YES];
UIGraphicsEndImageContext();
// there are three clever steps in the KENNY-TM-METHOD:
// - start a new UIGraphics image context
// - CGContextDrawLayerAtPoint which can, in fact, draw a CGLayer
// - just use the usual UIImagePNGRepresentation to convert to a png
// done! a miracle
// if you are testing on your mac-simulator, you'll find the file
// simply in the main drive directory
return;
}
For iPhone OS, it should be possible to draw a CGLayer on a CGContext and then convert into a UIImage, which can then be encoded into PNG and saved.
CGSize size = CGLayerGetSize(layer);
UIGraphicsBeginImageContext(size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextDrawLayerAtPoint(ctx, CGPointZero, layer);
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
NSData* pngData = UIImagePNGRepresentation(image);
[pngData writeToFile:... atomically:YES];
UIGraphicsEndImageContext();
(not tested)

How can I draw an CGImageRef context on the screen?

I have a beautiful CGImageRef context, which I created the whole day to get alpha values ;)
It's defined like that:
CGContextRef context = CGBitmapContextCreate (bitmapData, pixWidth, pixHeiht 8, pixWidth, NULL, kCGImageAlphaOnly);
So for my understanding, that context represents somehow my image. But "virtually", non-visible somewhere in memory.
Can I stuff that in an UIImageView or draw that directly to the screen? I guess that alpha would be converted to grayscale or something like that.
You can create a UIImage by calling:
+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage
and then draw the UIImage using:
- (void)drawAtPoint:(CGPoint)point
Go look at CGBitmapContextCreateImage(), that can give you a CGImageRef from your bitmap context. You can then draw that using the CGContext... functions or make a UIImage using +[UIImage imageWithCGImage:].
CGSize size = ...;
UIGraphicsBeginImageContext(size);
...
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
...
CGPoint pt = ...;
[img drawAtPoint:pt];