Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clipping shadows created by CALayer.shadow

I want to add a shadow around the OUTSIDE edge of a HOLLOW CGPath (those two words being critical: outside + hollow :) ).

Apple's shadow implementation only allows you to do "filled" shadows.

So ... how to clip-away the inside part of the shadow itself? If it were a render call, I'd call CGContextSetClip* methods to clip it to where I wanted it ... but I can't see how you'd get at it, since it's neither a render call, nor a CALayer itself.

(an unfortunate consequence of Apple "hiding" it as a set of magic variables, I guess?)

NB: this is iOS only - IIRC on Mac you have direct access to the filters used to create shadows, so you can create your own (arbitrary) shadows by hand.


UPDATE:

I worked out how to clip to show only the INNER part of the shadow: set the "layer.mask", to be a new layer that's a clone of the current layer (i.e. same path), and set the path fillcolor to anything with full alpha.

This is the opposite of what I'm trying, so if I could figure a way to get the mask layer to flip alpha (0 becomes 1, 1 becomes 0), I'd be there...

like image 822
Adam Avatar asked Mar 19 '12 18:03

Adam


1 Answers

Assuming you have a method:

-(CAShapeLayer*) cloneShapeLayer; // creates a new CAShapeLayer and copies the values

...then do this:

  1. Copy the layer, creating "mask"
  2. Take mask - same CGPath, remember - and replace its path with a path that has:
    1. The rect of the whole layer
    2. MINUS the original path
    3. (achieved by using the EO rule)
  3. set the mask of the original layer (the one with the shadow) to be the new layer "mask"

i.e. in code:

CAShapeLayer* maskLayer = [originalLayer cloneShapeLayer];
// got to make it a bit bigger if your original path reaches to the edge
// since the shadow needs to stretch "outside" the frame:
CGFloat shadowBorder = 50.0;
maskLayer.frame = CGRectInset( maskLayer.frame, - shadowBorder, - shadowBorder );
    maskLayer.frame = CGRectOffset( maskLayer.frame, shadowBorder/2.0, shadowBorder/2.0 );
maskLayer.fillColor = [UIColor blackColor].CGColor;
maskLayer.lineWidth = 0.0;
maskLayer.fillRule = kCAFillRuleEvenOdd;

CGMutablePathRef pathMasking = CGPathCreateMutable();
CGPathAddPath(pathMasking, NULL, [UIBezierPath bezierPathWithRect:maskLayer.frame].CGPath);
CGAffineTransform catShiftBorder = CGAffineTransformMakeTranslation( shadowBorder/2.0, shadowBorder/2.0);
CGPathAddPath(pathMasking, NULL, CGPathCreateCopyByTransformingPath(maskLayer.path, &catShiftBorder ) );
maskLayer.path = pathMasking;

shapeLayer.mask = maskLayer;

NB: this actually works, except that the shadow seems to get enlarged a little compared to when I wasn't masking. Oh well.

like image 80
Adam Avatar answered Jan 02 '23 11:01

Adam