Saving CGImage to disk

Discussion in 'iOS Programming' started by Soulstorm, Aug 7, 2009.

  1. Soulstorm macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #1
    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)

    Code:
    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:
    Code:
    - (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.
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    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.
     
  3. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #3
    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?
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    So the initial image you get is a UIImage right? So you can scale using 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...
     
  5. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #5
    From the Quartz 2D programming Guide:
    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...
    Code:
    - (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:
    Code:
    - (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?
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    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 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...
     
  7. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #7
    Actually CGImageDestination is not available for the iPhone OS... :(
     
  8. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #8
    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...
     
  9. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #9
    If you're worried about autoreleased objects, try creating your own autorelease pool around that code.
     
  10. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #10
    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?
     
  11. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #11
    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:
    Code:
    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.
     

Share This Page