Search code examples
iphoneobjective-ccalayercatransform3d

graphic context of rotated layers ignores any transformations


I'm composing a view by several layers, some of them rotated. The display of these layers works perfectly, however when I try to make an image, all rotations/transformations are ignored.

For example if I have a CALayer containing an image of an arrow pointing to the left, after a rotation around the z axis (using CATransform3DMakeRotation) it points to the top, and the layer is correctly displayed. However if I get an image of this layer (using renderInContext), the arrow still points to the left, ignoring any transformations.

Anyone any idea why, and what do I have to do to get my wanted result? :-)

Example code:

//  ViewController.m

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

// Don't forget to add the QuartzCore framework to your target.

@interface ViewController ()
@end

@implementation ViewController

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.view.backgroundColor = [UIColor lightGrayColor];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // Get the arrow image (tip to the left, 50 x 50 pixels).
    // (Download the image from here: http://www.4shared.com/photo/uQfor7vC/arrow.html )
    UIImage *arrowImage = [UIImage imageNamed:@"arrow"];

    // The arrow layer in it's starting orientation (tip to the left).
    CGRect layerFrame = CGRectMake(50.0, 50.0, arrowImage.size.width, arrowImage.size.height);
    CALayer *arrowLayerStart = [CALayer new];
    arrowLayerStart.frame = layerFrame;
    [arrowLayerStart setContents:(id)[arrowImage CGImage]];
    [self.view.layer addSublayer:arrowLayerStart];

    // The arrow layer rotated around the z axis by 90 degrees.
    layerFrame.origin = CGPointMake(layerFrame.origin.x + layerFrame.size.width + 25.0, layerFrame.origin.y);
    CALayer *arrowLayerZ90 = [CALayer new];
    arrowLayerZ90.frame = layerFrame;
    [arrowLayerZ90 setContents:(id)[arrowImage CGImage]];
    arrowLayerZ90.transform = CATransform3DMakeRotation(M_PI/2.0, 0.0, 0.0, 1.0);
    [self.view.layer addSublayer:arrowLayerZ90];

    // Now make images of each of these layers and display them in a second row.

    // The starting layer without any rotation.
    UIGraphicsBeginImageContext(arrowLayerStart.frame.size);
    [arrowLayerStart renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *arrowStartImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    layerFrame.origin = CGPointMake(arrowLayerStart.frame.origin.x,
                                    arrowLayerStart.frame.origin.y + arrowLayerStart.frame.size.height + 25.0);
    CALayer *arrowLayerStartCaptured = [CALayer new];
    arrowLayerStartCaptured.frame = layerFrame;
    [arrowLayerStartCaptured setContents:(id)[arrowStartImage CGImage]];
    [self.view.layer addSublayer:arrowLayerStartCaptured];

    // The second layer, rotated around the z axis by 90 degrees.
    UIGraphicsBeginImageContext(arrowLayerZ90.frame.size);
    [arrowLayerZ90 renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *arrowZ90Image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    layerFrame.origin = CGPointMake(arrowLayerZ90.frame.origin.x,
                                    arrowLayerZ90.frame.origin.y + arrowLayerZ90.frame.size.height + 25.0);
    CALayer *arrowLayerZ90Captured = [CALayer new];
    arrowLayerZ90Captured.frame = layerFrame;
    [arrowLayerZ90Captured setContents:(id)[arrowZ90Image CGImage]];
    [self.view.layer addSublayer:arrowLayerZ90Captured];    
}

@end


//  ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@end


//  AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end


//  AppDelegate.m

#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[ViewController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Solution

  • The documentation for -[CALayer renderInContext:] says:

    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, nor are layers that specify backgroundFilters, filters, compositingFilter, or a mask values. Future versions of Mac OS X may add support for rendering these layers and properties.

    (This limitation is present on iOS, too.)

    The header CALayer.h also says:

     * WARNING: currently this method does not implement the full
     * CoreAnimation composition model, use with caution. */
    

    Essentially, -renderInContext: is useful in a few simple cases, but will not work as expected in more complicated situations. You will need to find some other way to do your drawing. Quartz (Core Graphics) will work for 2D, but for 3D you're on your own.