views:

824

answers:

3

After applying a 3d transform to an UIImageView.layer, I need to save the resulting "view" as a new UIImage... Seemed like a simple task at first :-) but no luck so far, and searching hasn't turned up any clues :-( so I'm hoping someone will be kind enough to point me in the right direction.

A very simple iPhone project is available here.

Thanks.

- (void)transformImage {
 float degrees = 12.0;
 float zDistance = 250;
 CATransform3D transform3D = CATransform3DIdentity;
 transform3D.m34 = 1.0 / zDistance; // the m34 cell of the matrix controls perspective, and zDistance affects the "sharpness" of the transform
 transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(degrees), 1, 0, 0); // perspective transform on y-axis
 imageView.layer.transform = transform3D;
}

/* FAIL : capturing layer contents doesn't get the transformed image -- just the original

CGImageRef newImageRef = (CGImageRef)imageView.layer.contents;

UIImage *image = [UIImage imageWithCGImage:newImageRef];

*/


/* FAIL : docs for renderInContext states that it does not render 3D transforms

UIGraphicsBeginImageContext(imageView.image.size);

[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

*/
//
// header
//
#import <QuartzCore/QuartzCore.h>
#define DEGREES_TO_RADIANS(x) x * M_PI / 180
UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UIImageView *imageView;

//
// code
//
@synthesize imageView;

- (void)transformImage {
 float degrees = 12.0;
 float zDistance = 250;
 CATransform3D transform3D = CATransform3DIdentity;
 transform3D.m34 = 1.0 / zDistance; // the m34 cell of the matrix controls perspective, and zDistance affects the "sharpness" of the transform
 transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(degrees), 1, 0, 0); // perspective transform on y-axis
 imageView.layer.transform = transform3D;
}

- (UIImage *)captureView:(UIImageView *)view {
 UIGraphicsBeginImageContext(view.frame.size);
 [view.layer renderInContext:UIGraphicsGetCurrentContext()];
 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
 return newImage;
}

- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
 NSString *title = @"Save to Photo Album";
 NSString *message = (error ? [error description] : @"Success!");
 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
 [alert show];
 [alert release];
}

- (IBAction)saveButtonClicked:(id)sender {
 UIImage *newImage = [self captureView:imageView];
 UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(imageSavedToPhotosAlbum: didFinishSavingWithError: contextInfo:), nil);  
}
+1  A: 

Theoretically, you could use the (now-allowed) undocumented call UIGetScreenImage() after quickly rendering it to the screen on a black background, but in practice this will be slow and ugly, so don't use it ;P.

chpwn
UIGetScreenImage is allowed now http://www.steveperks.co.uk/post/Apple-Allows-UIGetScreenImage-For-iPhone.aspx
slf
Tried calling UIGetScreenImage to test this out, but am getting "warning: implicit declaration of function 'UIGetScreenImage'". Which header do I need to include?
David Balmer
It's private API, so it probably isn't in any headers. Just put your own declaration (`extern CGImageRef UIGetScreenImage();`) somewhere in the file that calls it. If the function is not actually there the linker will complain.
benzado
A: 

Have you had a look at this? UIImage from UIView

slf
A: 

In your captureView: method, try replacing this line:

[view.layer renderInContext:UIGraphicsGetCurrentContext()];

with this:

[view.layer.superlayer renderInContext:UIGraphicsGetCurrentContext()];

You may have to adjust the size you use to create the image context.

I don't see anything in the API doc that says renderInContext: ignores 3D transformations. However, the transformations apply to the layer, not its contents, which is why you need to render the superlayer to see the transformation applied.

Note that calling drawRect: on the superview definitely won't work, as drawRect: does not draw subviews.

benzado
Calling renderInContext on the superlayer gives me something that looks more like a screenshot, but still doesn't render the 3d transform. CALayer docs say: **Important**: The Mac OS X v10.5 implementation of this method does not support the entire Core Animation composition model. QCCompositionLayer, CAOpenGLLayer, and QTMovieLayer layers are not rendered. Additionally, layers that use 3D transforms are not rendered...
David Balmer
Bummer, dude. I think you need to use UIGetScreenImage() or write your own low-level thing to transform pixels from one buffer into another.
benzado
@benzado... and how do I do that? can you point a direction? thanks
Digital Robot