UIImage returned from UIGraphicsGetImageFromCurrentImageContext leaks - iphone

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.

Related

iPhone Difference Handling Images Between Device and Simulator

I have an image with a transparent border, and I am trying to directly manipulate the image pixels, following the Apple guide found here. Everything works perfectly well when run on the device. However, when I run my code on the simulator, I find that the transparent border of the image slowly turns black with each call to this function. The strange thing is that even if I don't modify the image data, the transparent border still begins to turn black with each call to this function. For example, I see the same problem even if my image manipulation code calls CGBitmapContextGetData but doesn't use the returned data pointer. To make the problem go away on the simulator, I have to comment out the call to CGBitmapContextGetData (and the freeing of the data pointer of course). Example code that still modifies the image on the simulator:
+ (UIImage *) updateImage:(UIImage *)inputImage
{
UIImage *updatedImage;
/* Update colors in image appropriately */
CGImageRef image = [inputImage CGImage];
CGContextRef cgctx = [ColorHandler CreateARGBBitmapContext:image];
if (cgctx == NULL)
{
// error creating context
NSLog(#"Error creating context.\n");
return nil;
}
size_t w = CGImageGetWidth(image);
size_t h = CGImageGetHeight(image);
CGRect rect = {{0,0},{w,h}};
// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawImage(cgctx, rect, image);
// Now we can get a pointer to the image data associated with the bitmap
// context.
void *data = CGBitmapContextGetData(cgctx);
CGImageRef ref = CGBitmapContextCreateImage(cgctx);
updatedImage = [UIImage imageWithCGImage:ref];
// When finished, release the context
CGContextRelease(cgctx);
CGImageRelease(ref);
// Free image data memory for the context
if (data)
{
free(data);
}
return updatedImage;
}
I read the comments and answers here regarding how images are managed differently between the device and simulator, but it hasn't helped me figure out my problem.
The only difference between my CreateARGBBitmapContext and the example one is that I call CGColorSpaceCreateDeviceRGB instead of CGColorSpaceCreateWithName because I am targeting iOS. The image is edited exactly as designed when run on the iOS device.
I am currently doing all image manipulation in the main thread for debugging this issue.
Specs: Mountain Lion, XCode 4.5.2, iOS 6 device, iOS 6 simulator
I was able to solve the issue by allowing Quartz to allocate and manage the memory for the bitmap (Apple doc). To do this, I updated the call to CGBitmapContextCreate in CreateARGBBitmapContext to pass NULL, and I removed all references to bitmapData.
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (NULL,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
Then, in the updateImage method, I removed the freeing of data. Now it seems to work on both device and simulator without any issues.

Correct GraphicsContext When Using UIGraphicsGetImageFromCurrentImageContext

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.

images from documents asynchronous

I need to read images from NSDocumentDirectory to multiple uiimageview async so it won't block the UI.
I know i can use perform selector in background to load a uiimage, but then how can i associate it with the dynamic uiimageview ?
One convenient way is to use blocks, something like:
[self loadFullImageAt:imagePath completion:^(UIIMage * image){
self.imageView.image = image;
}];
Where you would load the image as data (since UIImage otherwise loads the image data deferred - when you first access it). It's also a good idea to decompress the image while still in the background thread, so the main thread doesn't have to do it when we first use the image.
- (void)loadFullImageAt:(NSString *)imageFilePath completion:(MBLoaderCompletion)completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSData *imageData = [NSData dataWithContentsOfFile:imageFilePath];
UIImage *image = nil;
if (imageData) {
image = [[[UIImage alloc] initWithData:imageData] decodedImage];
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(image);
});
});
}
The callback is defined as:
typedef void (^MBLoaderCompletion)(UIImage *image);
Here's an UIImage category that implements the decompression code:
UIIMage+Decode.h
#import <UIKit/UIKit.h>
#interface UIImage (Decode)
- (UIImage *)decodedImage;
#end
UIIMage+Decode.m
#import "UIImage+Decode.h"
#implementation UIImage (Decode)
- (UIImage *)decodedImage {
CGImageRef imageRef = self.CGImage;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
CGImageGetWidth(imageRef),
CGImageGetHeight(imageRef),
8,
// Just always return width * 4 will be enough
CGImageGetWidth(imageRef) * 4,
// System only supports RGB, set explicitly
colorSpace,
// Makes system don't need to do extra conversion when displayed.
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGRect rect = (CGRect){CGPointZero,{CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}};
CGContextDrawImage(context, rect, imageRef);
CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(decompressedImageRef);
return decompressedImage;
}
#end
The sample code provided here assumes that we're using ARC.
When you say "dynamic" UIImageView, are these programmatically created on a UIScrollView? on a UITableView? samfisher is quite right on the basic question, but the details differ a little based upon how you created the UIImageView (e.g. if UITableView, you need to make sure that the cell is still visible and hasn't been dequeued; if UIScrollView, even then you might want to only load the UIImageView if the image is still visible on the screen (esp if the images are large or numerous)).
But the basic idea is that you might do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [self getTheImage];
// ok, now that you have the image, dispatch the update of the UI back to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
// if the image view is still visible, update it
});
});
Note that you invoke the retrieval of the image on some background queue or thread, but make sure to update the UI back on the main thread!
If you're updating a scrollview, you might want to do some checking that the view is still visible, such as contemplated here or here. If you're updating a tableview, perhaps something like this which checks if the cell is still visible. It all depends upon what you're trying to do.
you can use NSThread/dispatch queue for creating threads which can create your UIImageView-s and loads up images in them.

UIGraphicsGetImageFromCurrentImageContext returns unexpected nil

I have the following code:
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIGraphicsBeginImageContext(size);
screenImageContext = UIGraphicsGetCurrentContext();
ctx = screenImageContext;
UIGraphicsPushContext(UIGraphicsGetCurrentContext());
ctx = UIGraphicsGetCurrentContext();
NSLog(#" %#",screenImageContext);
UIImage * result = UIGraphicsGetImageFromCurrentImageContext(); // Returns nil
UIImageWriteToSavedPhotosAlbum(result, nil, nil, nil);
UIGraphicsPopContext();
result = UIGraphicsGetImageFromCurrentImageContext(); // returns valid result
My problem is that UIGraphicsGetImageFromCurrentImageContext returns nil, while the second one after UIGraphicsPopContext returns the correct result.
The docs clearly states that UIGraphicsGetImageFromCurrentImageContext will return nil when either the context is nil or the current context isn't a graphic context, but both these problems aren't happening here.
If anyone could shed some light over this i'd be very grateful
Shai.
From my understanding of things your issue is stemming from the following line
UIGraphicsPushContext(UIGraphicsGetCurrentContext());
With this call you're trying to make the current context the current context. This really doesn't make any sense.
Also with your code it's a little scrambled, you call UIGraphicsGetCurrentContext() which will return the current context, then you call UIGraphisBeginImageContext(CGSize size) which as stated in the doc's
Creates a bitmap-based graphics context and makes it the current context
Then you get the current graphics context again which is now a bitmap-based graphics context thanks to the previous call, then you overwrite the original CGContextRef ("ctx") that you just retrieved.
I'm not 100% certain what you were aiming to achieve with your code but if you were just trying to capture the contents of a bitmap-based context in an image and save it to the photo album then the following code will do that.
CGSize size = CGSizeMake(320, 480); //Screen Size on iPhone device
UIGraphicsBeginImageContext(size); //Create a new Bitmap-based graphics context (also makes this the current context)
CGContextRef screenImageContext = UIGraphicsGetCurrentContext(); //get a reference to the context we just made above
NSLog(#" %#",screenImageContext);
//NOTE: without any drawring code in here this will just be a blank image (white/alpha)
// or an image set to whatever the current UIColor is set to
//So you may want to add some drawing code in here. Although TBH I'm not sure what you were originally
// trying to achieve.
UIImage * result = UIGraphicsGetImageFromCurrentImageContext(); // Returns nil
NSLog(#" %#",result); //just output this to demonstrate that it's non null/nil
UIImageWriteToSavedPhotosAlbum(result, nil, nil, nil);
UIGraphicsEndImageContext(); //Removes the current bitmap-based graphics context from the top of the stack
Hope that helps.

CGImage/UIImage lazily loading on UI thread causes stutter

My program displays a horizontal scrolling surface tiled with UIImageViews from left to right. Code runs on the UI thread to ensure that newly-visible UIImageViews have a freshly loaded UIImage assigned to them. The loading happens on a background thread.
Everything works almost fine, except there is a stutter as each image becomes visible. At first I thought my background worker was locking something in the UI thread. I spent a lot of time looking at it and eventually realized that the UIImage is doing some extra lazy processing on the UI thread when it first becomes visible. This puzzles me, since my worker thread has explicit code for decompressing JPEG data.
Anyway, on a hunch I wrote some code to render into a temporary graphics context on the background thread and - sure enough, the stutter went away. The UIImage is now being pre-loaded on my worker thread. So far so good.
The issue is that my new "force lazy load of image" method is unreliable. It causes intermittent EXC_BAD_ACCESS. I have no idea what UIImage is actually doing behind the scenes. Perhaps it is decompressing the JPEG data. Anyway, the method is:
+ (void)forceLazyLoadOfImage: (UIImage*)image
{
CGImageRef imgRef = image.CGImage;
CGFloat currentWidth = CGImageGetWidth(imgRef);
CGFloat currentHeight = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
CGAffineTransform transform = CGAffineTransformIdentity;
CGFloat scaleRatioX = bounds.size.width / currentWidth;
CGFloat scaleRatioY = bounds.size.height / currentHeight;
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
CGContextTranslateCTM(context, 0, -currentHeight);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);
UIGraphicsEndImageContext();
}
And the EXC_BAD_ACCESS happens on the CGContextDrawImage line. QUESTION 1: Am I allowed to do this on a thread other than the UI thread? QUESTION 2: What is the UIImage actually "pre-loading"? QUESTION 3: What is the official way to solve this problem?
Thanks for reading all that, any advice would be greatly appreciated!
I've had the same stuttering problem, with some help I figured out the proper solution here: Non-lazy image loading in iOS
Two important things to mention:
Don't use UIKit methods in a worker-thread. Use CoreGraphics instead.
Even if you have a background thread for loading and decompressing images, you'll still have a little stutter if you use the wrong bitmask for your CGBitmapContext. This are the options you have to choose (it's still a bit unclear to me why):
-
CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
I've posted a sample project here: SwapTest, it has about the same performace as Apples' Photos app for loading/displaying images.
I used #jasamer's SwapTest UIImage category to force load my large UIImage (about 3000x2100 px) in a worker thread (with NSOperationQueue). This reduces the stutter time when setting the image into the UIImageView to an acceptable value (about 0.5 sec on iPad1).
Here is SwapTest UIImage category... thanks again #jasamer :)
UIImage+ImmediateLoading.h file
#interface UIImage (UIImage_ImmediateLoading)
- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path;
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path;
#end
UIImage+ImmediateLoading.m file
#import "UIImage+ImmediateLoading.h"
#implementation UIImage (UIImage_ImmediateLoading)
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path {
return [[[UIImage alloc] initImmediateLoadWithContentsOfFile: path] autorelease];
}
- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path {
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
CGImageRef imageRef = [image CGImage];
CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
rect.size.width,
rect.size.height,
CGImageGetBitsPerComponent(imageRef),
CGImageGetBytesPerRow(imageRef),
CGImageGetColorSpace(imageRef),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
);
//kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little are the bit flags required so that the main thread doesn't have any conversions to do.
CGContextDrawImage(bitmapContext, rect, imageRef);
CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage* decompressedImage = [[UIImage alloc] initWithCGImage: decompressedImageRef];
CGImageRelease(decompressedImageRef);
CGContextRelease(bitmapContext);
[image release];
return decompressedImage;
}
#end
And this is how I create NSOpeationQueue and set the image on main thread...
// Loads low-res UIImage at a given index and start loading a hi-res one in background.
// After finish loading, set the hi-res image into UIImageView. Remember, we need to
// update UI "on main thread" otherwise its result will be unpredictable.
-(void)loadPageAtIndex:(int)index {
prevPage = index;
//load low-res
imageViewForZoom.image = [images objectAtIndex:index];
//load hi-res on another thread
[operationQueue cancelAllOperations];
NSInvocationOperation *operation = [NSInvocationOperation alloc];
filePath = [imagesHD objectAtIndex:index];
operation = [operation initWithTarget:self selector:#selector(loadHiResImage:) object:[imagesHD objectAtIndex:index]];
[operationQueue addOperation:operation];
[operation release];
operation = nil;
}
// background thread
-(void)loadHiResImage:(NSString*)file {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"loading");
// This doesn't load the image.
//UIImage *hiRes = [UIImage imageNamed:file];
// Loads UIImage. There is no UI updating so it should be thread-safe.
UIImage *hiRes = [[UIImage alloc] initImmediateLoadWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType: nil]];
[imageViewForZoom performSelectorOnMainThread:#selector(setImage:) withObject:hiRes waitUntilDone:NO];
[hiRes release];
NSLog(#"loaded");
[pool release];
}
The UIGraphics* methods are designed to be called from the main thread only. They are probably the source of your trouble.
You can replace UIGraphicsBeginImageContext() with a call to CGBitmapContextCreate(); it's a little more involved (you need to create a color space, figure out the right sized buffer to create, and allocate it yourself). The CG* methods are fine to run from a different thread.
I'm not sure how you're initializing UIImage, but if you're doing it with imageNamed: or initWithFile: then you might be able to force it to load by loading the data yourself and then calling initWithData:. The stutter is probably due to lazy file I/O, so initializing it with a data object won't give it the option of reading from a file.
I had the same problem, even though I initialized the image using data. (I guess the data is loaded lazily, too?) I’ve succeeded to force decoding using the following category:
#interface UIImage (Loading)
- (void) forceLoad;
#end
#implementation UIImage (Loading)
- (void) forceLoad
{
const CGImageRef cgImage = [self CGImage];
const int width = CGImageGetWidth(cgImage);
const int height = CGImageGetHeight(cgImage);
const CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage);
const CGContextRef context = CGBitmapContextCreate(
NULL, /* Where to store the data. NULL = don’t care */
width, height, /* width & height */
8, width * 4, /* bits per component, bytes per row */
colorspace, kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context);
}
#end