Search code examples
ioscocoa-touchcore-graphics

Inner shadow with Core Graphics


Saw the generated code for drawing inner shadow. Everything is pretty clear and understood except for the portion whereby the shadow is created with copysign.

I understand what copysign does but why and how is it actually being used in the code below.

The 0.1 value seem insignificant to the xOffset value.

  CGContextRef context = UIGraphicsGetCurrentContext();
  
  UIColor *shadow = [UIColor redColor];
  CGSize shadowOffset = CGSizeMake(-2, -0);
  CGFloat shadowBlurRadius = 2;

  UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(50, 50, 50, 50)];
  [[UIColor grayColor] setFill];
  [rectanglePath fill];
  
  CGRect rectangleBorderRect = CGRectInset([rectanglePath bounds], -shadowBlurRadius, -shadowBlurRadius);
  rectangleBorderRect = CGRectOffset(rectangleBorderRect, -shadowOffset.width, -shadowOffset.height);
  rectangleBorderRect = CGRectInset(CGRectUnion(rectangleBorderRect, [rectanglePath bounds]), -1, -1);
  
  UIBezierPath *rectangleNegativePath = [UIBezierPath bezierPathWithRect: rectangleBorderRect];
  [rectangleNegativePath appendPath: rectanglePath];
  rectangleNegativePath.usesEvenOddFillRule = YES;
  
  CGContextSaveGState(context);
  {
    CGFloat xOffset = shadowOffset.width + round(rectangleBorderRect.size.width);
    CGFloat yOffset = shadowOffset.height;
    CGContextSetShadowWithColor(context,
                                CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
                                shadowBlurRadius,
                                shadow.CGColor);

    
    [rectanglePath addClip];
    CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rectangleBorderRect.size.width), 0);
    [rectangleNegativePath applyTransform: transform];
    [[UIColor grayColor] setFill];
    [rectangleNegativePath fill];
  }
  CGContextRestoreGState(context);

Solution

  • Developer of PaintCode here.

    Shadows offsets are always "rounded" to integral pixels. Unfortunately, there is some sort of precision/rounding issue in Core Graphics. For certain values of shadow offsets, the rounding somehow goes off and an incorrect offset is used (for example, 2 instead of 3).

    By strategically adding or subtracting 0.1 from the shadow offset, we force it to always behave correctly.