I want to mask a CALayer
with CAShapeLayer
, because changes to the shape can be animated.
When I use the CAShapeLayer
as a mask, the rounded corners are stretched. However, if I take the same shape, create an NSImage
with it, and use the image to mask my CALayer
, the rounded corners are perfectly fine.
Here's the code I'm using to mask the layers (you can also download the whole example project):
CGColorRef backgroundColor = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0f);
[self.window.contentView setLayer:[CALayer layer]];
[self.window.contentView setWantsLayer:YES];
[[self.window.contentView layer] setFrame:[self.window.contentView frame]];
CALayer *imageBasedMaskLayer = [CALayer layer];
[imageBasedMaskLayer setContents:(id)[[self maskWithSize:NSMakeSize(50, 50)] CGImageForProposedRect:NULL context:nil hints:nil]];
[imageBasedMaskLayer setFrame:CGRectMake(0, 0, 50, 50)];
CALayer *layerWithImageBasedMaskLayer = [CALayer layer];
[layerWithImageBasedMaskLayer setBackgroundColor:backgroundColor];
[layerWithImageBasedMaskLayer setMask:imageBasedMaskLayer];
CAShapeLayer *shapeBasedMaskLayer = [CAShapeLayer layer];
CGPathRef maskShape = [self newMaskPathWithFrame:NSMakeRect(0, 0, 50, 50)];
[shapeBasedMaskLayer setPath:maskShape];
[shapeBasedMaskLayer setFillRule:kCAFillRuleEvenOdd];
CGPathRelease(maskShape);
[shapeBasedMaskLayer setFrame:CGRectMake(0, 0, 50, 50)];
CALayer *layerWithShapeBasedMaskLayer = [CALayer layer];
[layerWithShapeBasedMaskLayer setBackgroundColor:backgroundColor];
[layerWithShapeBasedMaskLayer setMask:nil];
[layerWithShapeBasedMaskLayer setMask:shapeBasedMaskLayer];
[layerWithImageBasedMaskLayer setFrame:CGRectMake(50, 50, 50, 50)];
[layerWithShapeBasedMaskLayer setFrame:CGRectMake(120, 50, 50, 50)];
[[self.window.contentView layer] addSublayer:layerWithImageBasedMaskLayer];
[[self.window.contentView layer] addSublayer:layerWithShapeBasedMaskLayer];
CGColorRelease(backgroundColor);
And the two methods I'm using to create NSImage
and CGPathRef
.
- (NSImage *)maskWithSize:(CGSize)size;
{
NSImage *maskImage = [[NSImage alloc] initWithSize:size];
[maskImage lockFocus];
[[NSColor blackColor] setFill];
CGPathRef mask = [self newMaskPathWithFrame:CGRectMake(0, 0, size.width, size.height)];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextAddPath(context, mask);
CGContextFillPath(context);
CGPathRelease(mask);
[maskImage unlockFocus];
return [maskImage autorelease];
}
- (CGPathRef)newMaskPathWithFrame:(CGRect)frame;
{
CGFloat cornerRadius = 3;
CGFloat height = CGRectGetMaxY(frame);
CGFloat width = CGRectGetMaxX(frame);
CGMutablePathRef maskPath = CGPathCreateMutable();
CGPathMoveToPoint(maskPath, NULL, 0, height-cornerRadius);
CGPathAddArcToPoint(maskPath, NULL, 0, height, cornerRadius, height, cornerRadius);
CGPathAddLineToPoint(maskPath, NULL, width-cornerRadius, height);
CGPathAddArcToPoint(maskPath, NULL, width, height, width, height-cornerRadius, cornerRadius);
CGPathAddLineToPoint(maskPath, NULL, width, cornerRadius);
CGPathAddArcToPoint(maskPath, NULL, width, 0, width-cornerRadius, 0, cornerRadius);
CGPathAddLineToPoint(maskPath, NULL, cornerRadius, 0);
CGPathAddArcToPoint(maskPath, NULL, 0, 0, 0, cornerRadius, cornerRadius);
CGPathCloseSubpath(maskPath);
return maskPath;
}
Please note that the actual mask I want to create is more complicated than a rounded rect, otherwise I would've used some of the simpler drawing techniques. I've tried various CGPath
drawing functions, the problem did not disappear.
What am I missing here?
Thats because CAShapeLayer
favors speed over accuracy. Thats why it might be off sometimes.
The shape will be drawn antialiased, and whenever possible it will be mapped into screen space before being rasterized to preserve resolution independence. However, certain kinds of image processing operations, such as CoreImage filters, applied to the layer or its ancestors may force rasterization in a local coordinate space.
Note: Shape rasterization may favor speed over accuracy. For example, pixels with multiple intersecting path segments may not give exact results.
http://developer.apple.com/library/Mac/#documentation/GraphicsImaging/Reference/CAShapeLayer_class/Reference/Reference.html
You could use
layerWithShapeBasedMaskLayer.cornerRadius = 3.0;
to round all your corners. You don't need the CAShapeLayer
anymore. You can even continue to use your CAShapeLayer
as a sublayer for animating the path. Just set
layerWithShapeBasedMaskLayer.masksToBounds = YES;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With