Is there any way to create a UIImage object from an offscreen surface without copying the underlying pixel data?
I would like to do something like this:
// These numbers are made up obviously...
CGContextRef offscreenContext = MyCreateBitmapContext(width, height);
// Draw into the offscreen buffer
// Draw commands not relevant...
// Convert offscreen into CGImage
// This consumes ~15MB
CGImageRef offscreenContextImage = CGBitmapContextCreateImage(offscreenContext);
// This allocates another ~15MB
// Is there any way to share the bits from the
// CGImageRef instead of copying the data???
UIImage * newImage = [[UIImage alloc] initWithCGImage:offscreenContextImage];
// Releases the original 15MB, but the spike of 30MB total kills the app.
CGImageRelease(offscreenContextImage);
CGContextRelease(offscreenContext);
The memory is released and levels out at the acceptable size, but the 30MB memory spike is what kills the application. Is there any way to share the pixel data?
I've considered saving the offscreen buffer to a file and loading the data again, but this is a hack and the convenience methods for the iPhone require a UIImage to save it...
You could try releasing the context right after you created the CGImage, releasing the memory used by the context, because CGBitmapContextCreateImage() creates a copy of the context.
Like this:
CGImageRef offscreenContextImage = CGBitmapContextCreateImage(offscreenContext);
CGContextRelease(offscreenContext);
UIImage * newImage = [[UIImage alloc] initWithCGImage:offscreenContextImage];
// ...
CGImageRelease(offscreenContextImage);
Maybe
UIImage *newImage = [[UIImage alloc[ initWithData:offScreenContext];
Related
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.
I have this app with a full screen tableView that displays a bunch of tiny images. Those images are pulled from the web, processed on a background thread, and then saved to disk using something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
// code that adds some glosses, shadows, etc
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
NSData* cacheData = UIImagePNGRepresentation(output);
[cacheData writeToFile:thumbPath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
self.image = output; // refreshes the cell using KVO
});
});
This code is only executed the first time the cell is displayed (since after that the image is already on disk). In that case, the cell is loaded using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *savedImage = [UIImage imageWithContentsOfFile:thumbPath];
if(savedImage) {
dispatch_async(dispatch_get_main_queue(), ^{
self.image = savedImage; // refreshes the cell using KVO
});
}
});
My problem is that in the first case, scrolling is butter smooth. But in the 2nd case (where it's reading the image directly from disk), scrolling is super jerky, even once the image is loaded. Drawing is what's causing the lag. Using Instruments, I see copyImageBlockSetPNG, png_read_now and inflate are taking up most of the cpu (they aren't when assigning self.image to UIGraphicsGetImageFromCurrentImageContext())
I'm assuming this happens because in the first case the UIImage is a raw output of the drawing, whereas in the second case it has to decompress the PNG every time it's drawing it. I tried using JPGs instead of PNGs and I get similar results.
Is there a way to fix this? Maybe to have it only decompress the PNG the first time it gets drawn?
Your problem is that +imageWithContentsOfFile: is cached and lazy loading. If you want to do something like this, instead use this code on your background queue:
// Assuming ARC
NSData* imageFileData = [[NSData alloc] initWithContentsOfFile:thumbPath];
UIImage* savedImage = [[UIImage alloc] initWithData:imageFileData];
// Dispatch back to main queue and set image...
Now, with this code, the actual decompression of the image data will still be lazy and cost a little bit, but not nearly as much as the file access hit you're getting with the lazy loading in your code example.
Since you're still seeing a performance issue, you can also force UIImage to decompress the image on the background thread:
// Still on background, before dispatching to main
UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // this isn't that important since you just want UIImage to decompress the image data before switching back to main thread
[savedImage drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();
// dispatch back to main thread...
Jason's tip about pre-drawing the image to decompress it is the key, but you'll get even better performance by copying the whole image and discarding the original.
Images created at runtime on iOS seem to be better optimised for drawing than ones that have been loaded from a file, even after you've forced them to decompress. For that reason, you should load like this (it's also a good idea to put the decompressed image into an NSCache so you don't have to keep reloading it):
- (void)loadImageWithPath:(NSString *)path block:(void(^)(UIImage *image))block
{
static NSCache *cache = nil;
if (!cache)
{
cache = [[NSCache alloc] init];
}
//check cache first
UIImage *image = [cache objectForKey:path];
if (image)
{
block(image);
return;
}
//switch to background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//load image
UIImage *image = [UIImage imageWithContentsOfFile:path];
//redraw image using device context
UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//back to main thread
dispatch_async(dispatch_get_main_queue(), ^{
//cache the image
[cache setObject:image forKey:path];
//return the image
block(image);
});
});
}
Another way image decompression:
NS_INLINE void forceImageDecompression(UIImage *image)
{
CGImageRef imageRef = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, CGImageGetWidth(imageRef) * 4, colorSpace,kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colorSpace);
if (!context) { NSLog(#"Could not create context for image decompression"); return; }
CGContextDrawImage(context, (CGRect){{0.0f, 0.0f}, {CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}}, imageRef);
CFRelease(context);
}
Using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage *image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%u.jpg", pageIndex]];
forceImageDecompression(image);
dispatch_async(dispatch_get_main_queue(), ^{
[((UIImageView*)page)setImage:image];
});
}
I have following code as UIImage+Scale.h category.
-(UIImage*)scaleToSize:(CGSize)size
{
UIGraphicsBeginImageContext(size);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
// is this scaledImage auto-released?
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
I use image obtained as above and use it as following.
UIImage* image = [[UIImage alloc] initWithData: myData];
image = [image scaleToSize: size]; <- wouldn't this code create a leak since
image(before scaling) is lost somewhere?
i guess above codes work fine if image was first created with auto-release.
But if image was created using 'alloc', it would create a leak in my short knowledge.
How should I change scaleToSize: to guard against it?
Thank you
EDIT -
I'd like to use alloc(or retain)/release on UIImage so that I can keep the # of UIImage in memory at a point small.
(i'm loading many UIImages in a loop and device can't take it)
Notice that your code could be rewritten as:
UIImage *image = [[UIImage alloc] initWithData:myData];
UIImage *scaledImage = [image scaleToSize:size];
image = scaledImage;
so let’s see what happens:
image is obtained via alloc, hence you own that object
scaledImage is obtained via a method that returns an autoreleased object since UIGraphicsGetImageFromCurrentImageContext() returns an autoreleased object
you own the original image but you don’t own scaledImage. You are responsible for releasing the original image, otherwise you have a leak.
In your code, you use a single variable to refer to both objects: the original image and the scaled image. This doesn’t change the fact that you own the first image, hence you need to release it to avoid leaks. Since you lose the original image reference by using the same variable, one common idiom is to send -autorelease to the original object:
UIImage *image = [[[UIImage alloc] initWithData:myData] autorelease];
image = [image scaleToSize:size];
Or, if you’d rather release the original image instead of autoreleasing it,
UIImage *image = [[UIImage alloc] initWithData:myData];
UIImage *scaledImage = [image scaleToSize:size];
[image release];
// use scaledImage from this point on, or assign image = scaledImage
IMO, it doesn’t make sense to change scaleToSize:. It is an instance method that creates an (autoreleased) image based on a given UIImage instance. It’s similar to -[NSString stringByAppendingString:], which creates a (an autoreleased) string based on a given NSString instance. It doesn’t and shouldn’t care about the ownership of the original string, and the same applies to your scaleToSize: method. How would the method know whether the caller wants to keep the original image?
I’d also rename scaleToSize: to imageByScalingToSize to make it similar to Cocoa’s naming convention — you’re getting an image by applying an operation to an existing image.
Yeah, it is sure that you have a leak. The object stored previously in the image is not referenced anymore but not deallocated yet
I'd like to display the same UIView multiple times. At the moment, I have my drawing in a primary UIView, then copy this into an image using renderInContext: and UIGraphicsGetImageFromCurrentImageContext. Then I set the contents of the other proxy UIViews to be this image.
UIGraphicsBeginImageContext(size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * clonedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [clonedImage CGImage];
I'm experiencing a bottleneck in the renderInContext: call, presumably because it has to copy the image of the view. I'm seeing hot spots in resample_byte_h_3cpp and resample_byte_v_Ncpp, but I'm not sure what these are doing.
Is it possible to display the same UIView multiple times to reduce this overhead? Or is there a more efficient way to render the image?
How about making a copy of the UIImage instead of generating new images over and over from the UIView?
//.. Create the clonedImage from UIView
CGImageRef cgImageRef = [clonedImage CGImage];
UIImage *twinImage = [[UIImage alloc] initWithCGImage:cgImageRef];
//.. Use the images
[clonedImage release]; // if needed
[twinImage release]; // if needed
I have a little sketch program that I've managed to throw together using Quartz2D, but I'm very new to the iOS platform and I'm having trouble figuring out how to allow the user to save their sketch. I have a CGContextRef that contains the sketch, how can I save it so that it can later be retrieved? Once it's saved, how can I retrieve it?
Thanks so much in advance for your help! I'm going to continue reading up on this now...
There are different types of CGContexts. You are most likely having a screen based context, but you would need either a bitmapContext or a pdfContext, to create a bitmap or a pdf file.
See the UIKit Functions Reference. Functions that should be of interest are:
UIGraphicsBeginImageContext()
UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageJPEGRepresentation()
UIImageWriteToSavedPhotosAlbum()
UIGraphicsBeginPDFContextToFile()
UIGraphicsEndPDFContext()
Here's how I did it...
Saving
CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage* image = [[UIImage alloc] initWithCGImage:imageRef];
NSData* imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:filePath atomically:YES];
Retrieving
UIImage* image = [UIImage imageWithContentsOfFile:filePath];
CGImageRef imageRef = [image CGImage];