I'm trying to create previews images of pages in a PDF
but I have some problems with the release of memory.
I wrote a simple test algorithm that cycles on the problem,
the app crashes near the 40th iteration:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:#"myPdf.pdf"];
CFURLRef url = CFURLCreateWithFileSystemPath( NULL, (CFStringRef)pdfPath, kCFURLPOSIXPathStyle, NO );
CGPDFDocumentRef myPdf = CGPDFDocumentCreateWithURL( url );
CFRelease (url);
CGPDFPageRef page = CGPDFDocumentGetPage( myPdf, 1 );
int i=0;
while(i < 1000){
UIGraphicsBeginImageContext(CGSizeMake(768,1024));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,CGRectMake(0, 0, 768, 1024));
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, 1024);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
// --------------------------
// The problem is here (without this line the application doesn't crash)
UIImageView *backgroundImageView1 = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
// --------------------------
UIGraphicsEndImageContext();
[backgroundImageView1 release];
NSLog(#"Loop: %d", i++);
}
CGPDFDocumentRelease(myPdf);
The above-mentioned line seems to generate a memory leak,
however, instruments doesn't show memory problems;
Can I escape from this kind of mistake?someone can explain me in which way?
Are there other ways to show previews of a pdf?
UPDATE
I think the problem isn't the release of UIImage created by the method UIGraphicsGetImageFromCurrentImageContext() but the release of UIImageView created with this autorelease image.
I have divided the line of code in three steps:
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIImageView *myImageView = [[UIImageView alloc] init];
[myImageView setImage: myImage]; // Memory Leak
The first and second lines doesn't create memory leaks so I think that the method UIGraphicsGetImageFromCurrentImageContext is not the problem.
I also tried as follows but the problem persists:
UIImageView *myImageView = [[UIImageView alloc] initWithImage:myImage];
I think there is a memory leak in the release of a UIImageView that contains a UIImage with the autorelease property.
I tried to write my object UIImageView inheriting a UIView as explained in this thread.
This solution works but isn't very elegant, it's a workaround, I would prefer to use the object UIImageView solving the memory problem.
The problem is this:
UIGraphicsGetImageFromCurrentImageContext()
returns an autoreleased UIImage. The autorelease pool holds on to this image until your code returns control to the runloop, which you do not do for a long time. To solve this problem, you would have to create and drain a fresh autorelease pool on every iteration (or every few iterations) of your while loop.
I know it's an old question, but I've just been banging my head against the wall on this for a few hours. In my app repeatedly calling
UIImage *image = UIGraphicsGetImageFromCurrentImageContext()
in a loop does hold on to the memory despite me calling image = nil; Not sure how long the app would keep hold of the memory before freeing, but it's certainly long enough for my app to get a memory warning then crash.
I managed to solve it finally by wrapping the code that calls / uses the image from UIGraphicsGetImageFromCurrentImageContext() in #autoreleasepool. So I have:
#autoreleasepool {
UIImage *image = [self imageWithView:_outputImageView]; //create the image
[movie addImage:image frameNum:i fps:kFramesPerSec]; //use the image as a frame in movie
image = nil;
}
Hope that might help someone.
For future reference here's what I did to solve this (tested in Swift 4).
I was calling the function below for every new image downloaded from the internet (on a utility queue). Before implementing the autorelease pool it would crash after processing about 100.
For simplicity, in the resizeImage function I've removed needed code except for the autoreleasepool and the part that was leaking.
private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {
return autoreleasepool { () -> UIImage in
[...]
let newImage = UIGraphicsGetImageFromCurrentImageContext() //Leaked
UIGraphicsEndImageContext()
return newImage!
}
}
I hope this helps!
For those who tried all solution above and still has a memory leak, check if you are using a dispatch queue. If so, be sure to set its autoreleaseFrequency to .workItem. Or the autorelease pool you set up inside the will not execute.
DispatchQueue(label: "imageQueue", qos: .userInitiated, autoreleaseFrequency: .workItem)
Hope it helps, it has bugged me for hours until I finally realize that's DispatchQueue that is holding the block.
Is this code running on the main thread? The documentation of the UIGraphicsGetImageFromCurrentImageContext (link) says it must run that way.
your line of crash you can update it like following
get one UIimage out of loop
rendered_image = UIGraphicsGetImageFromCurrentImageContext();
Related
I make use of the UIImage+Resize library to resize my images directly after taking them.
I do get a memory leak with the following code:
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, srcSize.width, srcSize.height), imgRef);
UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGDataProviderCreateWithCopyOfData memory leak in Instruments.
I tried adding it into a #autoreleasepool as per other StackOverflow posts.
Note: You've probably have already fixed your problem, this answer for future readers having the same problem.
I've figured out what's going on my own implementation (video analysis with GPUImage).
I needed to add an autoreleasepool closure around the call to CGBitmapContextCreateImage caller (it also calls CGDataProviderCreateWithCopyOfData).
func run() {
while (parent != nil) {
if semaphore.acquireSemaphore(5000) {
autoreleasepool { // Without this I was leaking images until crash.
parent?.analyseImage(self.sampleBuffer!, lineFinder:finder) //This calls CGBitmapContextCreateImage
}
}
}
thread = nil
}
I have to merge multiple images in to single (all of high resolution), It acquires lots of memory. I saved original images to local directory and set resized images to imageviews, placed on different locations on main image. Now at the time of saving final merged image, I then read the original images from local directory. here the memory increases, that cause error (crash due to memory) for higher number of images.
here is code: retrieving original image from local directory
UIImage *originalImage = [UIImage imageWithContentsOfFile:[self getOriginalImagePath:imageview.tag]];
Is there any other way to get images from local directory without loading it into memory.
Thanks in advance
There is no way to load an image without it going into memory. With some image formats you could, in theory, implement your own reader that scales the image down while reading the file, so that the original size never ends up in memory, but that would require a lot of work for little gain.
Overall you would be better off just saving the different sizes of images as separate files and loading only the correct size (you seem to be scaling them based on the screen size, so there are not that many different versions required).
If you do keep to resizing them on the fly, try to ensure that you get rid of the original versions as soon as possible, i.e., don't keep any image reference no longer required, and perhaps wrap the whole thing in #autoreleasepool (assuming ARC is being used):
#autoreleasepool {
UIImage *originalImage = [UIImage imageWithContentsOfFile:[self getOriginalImagePath:imageview.tag]];
UIImage *pThumbsImage = [self scaleImageToSize:CGSizeMake(AppScreenBound.size.width, AppScreenBound.size.height) imageWithImage:pOrignalImage];
originalImage = nil;
imageView.image = pThumbImage;
pThumbImage = nil;
// … ?
}
Similarly treat any other image handling that creates intermediate versions, i.e., get rid of references no longer required as soon as possible (such as by assigning nil or having them fall out of scope), and put #autoreleasepool { … } around subsections that may generate temporary objects.
Found a solution, posting it as an answer to my own question, might help other people. reference from Image I/O Programming Guide
An alternative to "imageWithContentsOfFile:", one can use an Image Source
here is a code how I use it.
UIImage *originalWMImage = [self createCGImageFromFile:your-image-path];
the method createCGImageFromFile: get an image content without loading it to memory
-(UIImage*) createCGImageFromFile :(NSString*)path
{
// Get the URL for the pathname passed to the function.
NSURL *url = [NSURL fileURLWithPath:path];
CGImageRef myImage = NULL;
CGImageSourceRef myImageSource;
CFDictionaryRef myOptions = NULL;
CFStringRef myKeys[2];
CFTypeRef myValues[2];
// Set up options if you want them. The options here are for
// caching the image in a decoded form and for using floating-point
// values if the image format supports them.
myKeys[0] = kCGImageSourceShouldCache;
myValues[0] = (CFTypeRef)kCFBooleanTrue;
myKeys[1] = kCGImageSourceShouldAllowFloat;
myValues[1] = (CFTypeRef)kCFBooleanTrue;
// Create the dictionary
myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
(const void **) myValues, 2,
&kCFTypeDictionaryKeyCallBacks,
& kCFTypeDictionaryValueCallBacks);
// Create an image source from the URL.
myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, myOptions);
CFRelease(myOptions);
// Make sure the image source exists before continuing
if (myImageSource == NULL){
fprintf(stderr, "Image source is NULL.");
return NULL;
}
// Create an image from the first item in the image source.
myImage = CGImageSourceCreateImageAtIndex(myImageSource,
0,
NULL);
CFRelease(myImageSource);
// Make sure the image exists before continuing
if (myImage == NULL){
fprintf(stderr, "Image not created from image source.");
return NULL;
}
return [UIImage imageWithCGImage:myImage];
}
Here is code: resized image and simply assigned to imageview. Then i perform scaling and rotation on imageview.
UIImage *pThumbsImage = [self scaleImageToSize:CGSizeMake(AppScreenBound.size.width, AppScreenBound.size.height) imageWithImage:pOrignalImage];
[imageView setImage:pThumbImage];
here when saving:this code is within for loop: (upto number of images to merge on main image)
// get size of the second image
CGFloat backgroundWidth = canvasSize.width;
CGFloat backgroundHeight = canvasSize.height;
//Image View: to be merged
UIImageView* imageView = [[UIImageView alloc] initWithImage:stampImage];
[imageView setFrame:CGRectMake(0, 0, stampFrameSize.size.width , stampFrameSize.size.height)];
// Rotate Image View
CGAffineTransform currentTransform = imageView.transform;
CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform, radian);
[imageView setTransform:newTransform];
// Scale Image View
CGRect imageFrame = [imageView frame];
// Create Final Stamp View
UIView *finalStamp = nil;
finalStamp = [[UIView alloc] initWithFrame:CGRectMake(0, 0, imageFrame.size.width, imageFrame.size.height)];
// Set Center of Stamp Image
[imageView setCenter:CGPointMake(imageFrame.size.width /2, imageFrame.size.height /2)];
[finalImageView addSubview:imageView];
// Create Image From image View;
UIGraphicsBeginImageContext(finalStamp.frame.size);
[finalStamp.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *pfinalMainImage = nil;
// Create Final Image With Stamp
UIGraphicsBeginImageContext(CGSizeMake(backgroundWidth, backgroundHeight));
[canvasImage drawInRect:CGRectMake(0, 0, backgroundWidth, backgroundHeight)];
[viewImage drawInRect:CGRectMake(stampFrameSize.origin.x , stampFrameSize.origin.y , stampImageFrame.size.width , stampImageFrame.size.height) blendMode:kCGBlendModeNormal alpha:fAlphaValue];
pfinalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
everything is okay here. the problem occurs while saving it or generating merged image.
This is an old question, but I had to face something like that recently... so there is my answer.
I had to merge a lot of images into one, and had the same problem. The memory increased until the app crashes. The functions that I created, returned UIImage and that was the problem. The ARC was not releasing at time, so I had to change to return CGImageRef and release them at properly time.
I have following method to take a pdf file in documents directory and create a thumbnail out of it. This method is leaking memory in two places as shown in comments. Since I am using ARC I am not sure why is it leaking memory. How can I solve this.
+ (UIImage*)createPdfThumbnail:(NSString*)pdfFilePath {
NSURL *targetURL = [NSURL fileURLWithPath:pdfFilePath];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)targetURL); // 3.0% of memory leak
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);//for the first page
CGRect aRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -(aRect.origin.x), -(aRect.origin.y));
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, aRect, 0, false);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); // 97% of memory leak
CGContextRestoreGState(context);
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);
return thumbnail;
}
EDIT:
-(void)fromJSON:(NSDictionary *)JSON{
[super fromJSON:JSON];
self.path = JSON[#"path"];
//Create and save thumbnail
if (self.parentSpecSheet != nil){
#autoreleasepool {
UIImage* thumbnail = [Utilities createPdfThumbnail:self.path];
Photo* thumbnailPhoto = [Photo addObject];
[thumbnailPhoto setDelta:#(0)];
[thumbnailPhoto setImage:thumbnail];
[thumbnailPhoto.file setDelta:#(0)];
self.parentSpecSheet.thumbnail = thumbnailPhoto;
}
}
}
Two thoughts:
I experience a significant leak from CGContextDrawPDFPage when I test your code in iOS5 (and if you search for "CGContextDrawPDFPage leak", you'll see tons of references to permutations of this problem). This appears to be a known problem.
I see no appreciable leak in iOS 6 from the above code, though.
If you're still seeing this leak in iOS 6, then I suspect the problem does not rest in the above code. Do you have any other leaks reported? I'd also suggest you confirm that the object that owns this thumbnail is successfully getting deallocated itself (e.g. log/breakpoint in its dealloc method).
Unfortunately, when you look at the leaks tool, it's reporting where the leaked object was instantiated, not where the leak took place. You might want to confirm that the owner of this thumbnail is not, somehow, maintaining a strong reference to it (e.g., the owner, itself, has a retain cycle, or something like that).
I am trying a simple test for a much more complex project but I am baffled as to why the code below is crashing and giving an EXC_BAD_ACCESS error?
This is called from a UIView.
- (void)testing {
NSString *imagePath = [[NSBundle mainBundle] pathForResource:#"ball.png" ofType:nil];
CGImageRef imageRef = [[[UIImage alloc]initWithContentsOfFile:imagePath]CGImage];
// CGImageRetain(imageRef);
UIImage *newImage = [[UIImage alloc]initWithCGImage:imageRef];
UIImageView *iv = [[UIImageView alloc]initWithImage:newImage];
[self addSubview:iv];
}
My guess is that the CGImageRef is not being retained but adding CGImageRetain(imageRef); makes no difference.
I should also note that this project has ARC turned on.
EDIT
I did a little bit more testing and have discovered that this is directly related to ARC as I created 2 basic projects including only the code above. The first with ARC turned off and it worked perfectly. The next with ARC turned on and BAM crash with the same error. The interesting thing is that I got an actual log error ONLY the first time I ran the project before the crash.
Error: ImageIO: ImageProviderCopyImageBlockSetCallback 'ImageProviderCopyImageBlockSetCallback' header is not a CFDictionary...
This line is the problem:
CGImageRef imageRef = [[[UIImage alloc]initWithContentsOfFile:imagePath]CGImage];
The created UIImage will be released immediately following this full-expression (e.g. after this line). So even trying to add a CGImageRetain() afterwards won't work.
The fundamental problem is the CGImageRef returned from CGImage is almost certainly an ivar of the UIImage and will be released when the UIImage is deallocted.
The generic way to fix this is to extend the lifetime of the UIImage. You can do this by placing the UIImage into a local variable and referencing it after your last reference to the CGImage (e.g. with (void)uiimageVar). Alternatively, you can retain the CGImageRef on that same line, as in
CGImageRef imageRef = CGImageRetain([[[UIImage alloc] initWithContentsOfFile:imagePath] CGImage]);
But if you do this, don't forget to release the imageRef when you're done.
You wrote this:
CGImageRef imageRef = [[[UIImage alloc]initWithContentsOfFile:imagePath]CGImage];
That line creates a UIImage, gets its CGImage property, and then releases the UIImage object. Since you're not retaining the CGImage on that line, the only owner of the CGImage is the UIImage. So when the UIImage is released and immediately deallocated, it deallocates the CGImage too.
You need to retain the CGImage before the UIImage is released. Try this:
CGImageRef imageRef = CGImageRetain([[UIImage alloc]initWithContentsOfFile:imagePath].CGImage);
and then at the end of the function:
CGImageRelease(imageRef);
I have written the following code snippet to take screen snapshot:
UIGraphicsBeginImageContext(animationView.frame.size);
[[window layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage* screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
But, UIGraphicsGetImageFromCurrentImageContext seems to be leaking. Is this correct?
In Instruments I could not get the exact leak point. In activity monitor I observed that when I switch to the UI that executes the above code snippet memory increments by some MB. After this point it never decreases.
Does UIGraphicsGetImageFromCurrentImageContext has memory leak? How do I solve this?
Edit
Instruments analysis
Activity Monitor: shows the memory hike when this line of code is executed; never decreases even after releasing screenshot (UIImage)
Leaks and allocation, Heap Snapshot: Does not show any leak OR this allocation.
You have just created a UIImage with data for your animationView (which could be some MB). Perhaps you should wrap this functionality in an autorelease pool.
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
UIImage* screenshot = UIGraphicsGetImageFromCurrentImageContext();
//[screenshot retain]; //If you want precise control over when it is released and you will use it later.
[pool release];
CGContextRef context = UIGraphicsGetCurrentContext();
/* you code */
CGContextRelease(context);
clear the context when done