View Full Version : Saving CGImage to disk
Soulstorm
Aug 7, 2009, 07:14 AM
I have a UIImage that I want to write to disk as PNG. The thing is that for some reason I don't want to use the UIImagePNGRepresentation . That's because I want to do stuff with the original CGImage embedded in the UIImage before I make the save.
Here is my code so far (provided I have a test png image that I load to perform my tests)
UIImage *image = [UIImage imageNamed:@"cameraicon.png"];
//self.imageView.image = image;
CGDataProviderRef dataProvider = CGImageGetDataProvider(image.CGImage);
CFDataRef data = CGDataProviderCopyData(dataProvider);
CGDataProviderRelease(dataProvider);
NSString *imagePathLocation = [[self documentsDirectory]stringByAppendingString:@"/image.png"];
const char *fileNameC = [imagePathLocation cStringUsingEncoding:NSUTF8StringEncoding];
const UInt8 *buffer = CFDataGetBytePtr(data);
SInt32 bufferLength = CFDataGetLength(data);
FILE *file = fopen(fileNameC, "wb");
if (!file) {
NSLog(@"error opening file!");
}
NSLog(@"trying to write %i bytes to: %@",bufferLength, imagePathLocation);
fwrite(buffer, 1, bufferLength, file);
for convenience reasons, here is the code I use to obtain the path of the documents directory:
- (NSString *)documentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
return documentsDirectoryPath;
}
Although this code has a result, the result is an unreadable "image.png" file that when opened with "Preview" i get an error that is is corrupted.
I am grateful for any ideas or recommendations.
robbieduncan
Aug 7, 2009, 07:22 AM
I don't think you can guarantee that the CGImage data will be a PNG, even if the data was created from a PNG. Your best option would be to do whatever you want to the CGImage and then, just before save, convert it into a UIImagePNGRepresentation.
Soulstorm
Aug 7, 2009, 08:43 AM
Damn. I am trying to avoid that... An image taken from the camera will be 1.5 in size or so. Creating an NSData object using UIImagePNGRepresentation will take another 1.5 mbytes, that are autoreleased and not managed by the developer. That's why I am trying to avoid that function.
Anyway, I should mention what I am trying to do. I am trying to take a picture from the camera, and save it 3 times in 3 different sizes. In order to shrink the original image, I am changing the underlying CGImage using CoreGraphics. That's the only way of shrinking an image. So imagine this:
I have the original image. 1.5mbytes.
I have the medium sized image. 750kbytes.
I have the small image. 250kbytes.
For those 3, I must create UIImagePNGRepresentations that take as much as the original images! And then save them to disk. That's a huge memory overhead.
EDIT: Will CGDataConsumer help me?
Can you recommend another way avoiding all this RAM usage?
robbieduncan
Aug 7, 2009, 08:51 AM
So the initial image you get is a UIImage right? So you can scale using drawInRect: (http://developer.apple.com/IPhone/library/documentation/UIKit/Reference/UIImage_Class/Reference/Reference.html#//apple_ref/occ/instm/UIImage/drawInRect:) after locking focus on a new UIImage that is the size you want. Then you can ask for he reps you want and write that. I can't see any way round creating the UIImagePNGRepresentation. If you don't do that then you won't have a PNG: CGImage doesn't hold the data in a file format: is stores it in a good format for display.
I've no idea what you are considering using CGDataConsumer for? It won't turn a CGImage into a PNG for you...
Soulstorm
Aug 7, 2009, 09:00 AM
So the initial image you get is a UIImage right? So you can scale using drawInRect: (http://developer.apple.com/IPhone/library/documentation/UIKit/Reference/UIImage_Class/Reference/Reference.html#//apple_ref/occ/instm/UIImage/drawInRect:) after locking focus on a new UIImage that is the size you want. Then you can ask for he reps you want and write that. I can't see any way round creating the UIImagePNGRepresentation. If you don't do that then you won't have a PNG: CGImage doesn't hold the data in a file format: is stores it in a good format for display.
I've no idea what you are considering using CGDataConsumer for? It won't turn a CGImage into a PNG for you...
From the Quartz 2D programming Guide:
Data consumers can be used to write image or PDF data, and all, except for CGDataConsumerCreateWithCFData, are available in Mac OS X v10.0 or later.
I already use this method you describe: here is a sample code:
This is the addition to the UIImage. The extra code is needed to take care the different orientations of a UIImage. You can see that the actual processing is done through the CGContect class and the CGGraphicsDrawImage function. Will your method be faster? I hope so, because my code looks like it adds a lot of overhead...
- (UIImage *)scaledImage:(float)maxResolution
{
CGImageRef imgRef = self.CGImage;
float kMaxResolution = maxResolution;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
if (width <= kMaxResolution && height <= kMaxResolution && self.imageOrientation == UIImageOrientationUp) {
//NSLog(@"no changes applied");
return self;
}
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
if (width > kMaxResolution || height > kMaxResolution) {
CGFloat ratio = width/height;
if (ratio > 1) {
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else {
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}
CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
CGFloat boundHeight;
switch(self.imageOrientation) {
case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;
case UIImageOrientationUpMirrored: //EXIF = 2
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
break;
case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationDownMirrored: //EXIF = 4
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
break;
case UIImageOrientationLeftMirrored: //EXIF = 5
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationRightMirrored: //EXIF = 7
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
default:
[NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];
}
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.imageOrientation == UIImageOrientationRight || self.imageOrientation == UIImageOrientationLeft) {
CGContextScaleCTM(context, -scaleRatio, scaleRatio);
CGContextTranslateCTM(context, -height, 0);
}
else {
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
}
CGContextConcatCTM(context, transform);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//self.image = imageCopy;
return imageCopy;
}
And this is for the UIimagePicker:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
NSLog(@"image height: %f, image Width: %f", image.size.height, image.size.width);
UIImage *mediumImage = [image scaledImage:428];
UIImage *shrinkedImage = [image scaledImage:100];
imageView.image = shrinkedImage;
[self saveImageToImagesDirectory:image fileID:currentImageID];
[self saveThumbnailToThumbnailsDirectory:shrinkedImage thumbailName:currentImageID];
[self saveImageToMediumsDirectory:mediumImage fileID:currentImageID];
}
- (void)saveImageToMediumsDirectory:(UIImage *)image fileID:(NSString *)idName
{
NSLog(@"saving medium image with id: %@", idName);
NSData *imageData = UIImagePNGRepresentation(image);
NSString *mediumsDirectory = [[SFGlobals sharedSFGlobals]applicationMediumSizedImagesDirectory];
[imageData writeToFile:[mediumsDirectory stringByAppendingPathComponent:idName] atomically:NO];
}
...and so on...
Can you tell me a way of reducing RAM usage? Should I use your method?
robbieduncan
Aug 7, 2009, 09:08 AM
From the Quartz 2D programming Guide:
Ok, it looks like there may be a way to get Quartz 2D to do the conversion for you. I think you need to use CGImageDestinationCreateWithDataConsumer (http://developer.apple.com/documentation/GraphicsImaging/Reference/CGImageDestination/Reference/reference.html#//apple_ref/c/func/CGImageDestinationCreateWithDataConsumer) passing it the UTI for a png.
I think maybe you can use CGImageDestinationCreateWithURL and then CGImageDestinationAddImage and finally CGImageDestinationFinalize to not even need the CGDataConsumer...
Soulstorm
Aug 7, 2009, 09:14 AM
Ok, it looks like there may be a way to get Quartz 2D to do the conversion for you. I think you need to use CGImageDestinationCreateWithDataConsumer (http://developer.apple.com/documentation/GraphicsImaging/Reference/CGImageDestination/Reference/reference.html#//apple_ref/c/func/CGImageDestinationCreateWithDataConsumer) passing it the UTI for a png.
I think maybe you can use CGImageDestinationCreateWithURL and then CGImageDestinationAddImage and finally CGImageDestinationFinalize to not even need the CGDataConsumer...
Actually CGImageDestination is not available for the iPhone OS... :(
robbieduncan
Aug 7, 2009, 09:16 AM
Actually CGImageDestination is not available for the iPhone OS... :(
Ah, in that case I'm not sure how you can use a CGDataConsumer and get the image converted to a PNG. I'm not a Quartz 2D expert though...
kainjow
Aug 7, 2009, 10:06 AM
If you're worried about autoreleased objects, try creating your own autorelease pool around that code.
Soulstorm
Aug 8, 2009, 01:42 PM
If you're worried about autoreleased objects, try creating your own autorelease pool around that code.
Hm... that's something that I haven't thought about! You mean that wrapping this code using an autorelease pool will force-release the objects I use?
kainjow
Aug 8, 2009, 01:57 PM
Hm... that's something that I haven't thought about! You mean that wrapping this code using an autorelease pool will force-release the objects I use?
Whenever autorelease is called, the object it's called on is placed into the current autorelease pool. When you alloc/init a new NSAutoreleasePool, it becomes the current pool, so any objects after that pool is created are placed into it and once the pool is drained, all the objects are sent a release.
So, you could do something like this:
CGImageRef myImage = ...
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *img = [UIImage imageWithCGImage:myImage];
NSData *pngData = UIImagePNGRepresentation(img);
[pngData writeToFile:path atomically:YES];
[pool drain];
And once the drain is called pngData and img will deallocate.
vBulletin® v3.8.6, Copyright ©2000-2012, Jelsoft Enterprises Ltd.