CALayerWithClipRender.m 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. //
  2. // CALayerWithClipRender.m
  3. // SVGKit-iOS
  4. //
  5. // Created by David Gileadi on 8/14/14.
  6. // Copyright (c) 2014 na. All rights reserved.
  7. //
  8. #import "CALayerWithClipRender.h"
  9. @interface CALayer (ContentsTransform)
  10. @property CGAffineTransform contentsTransform;
  11. @end
  12. @implementation CALayerWithClipRender
  13. - (void)renderInContext:(CGContextRef)ctx {
  14. CALayer *mask = nil;
  15. if( self.mask != nil ) {
  16. [CALayerWithClipRender maskLayer:self inContext:ctx];
  17. mask = self.mask;
  18. self.mask = nil;
  19. }
  20. // We use the flipped coordinate system on macOS, to match the behavior of iOS. However, the `contents` (which is a CGImageRef) bitmap provided by image element is not been flipped as we want. The `renderInContext:` which used by `SVGKFastImageView` will not correct this coordinate system issue, only `SVGKLayeredImageView` do. So we use the `contentsTransform` to manually fix it.
  21. #if SVGKIT_MAC
  22. // If already flipped, which should be handled by Core Animation itself, ignore
  23. if (self.contentsAreFlipped) {
  24. [super renderInContext:ctx];
  25. } else {
  26. self.contentsTransform = CGAffineTransformMake(1, 0, 0, -1, 0, self.bounds.size.height);
  27. [super renderInContext:ctx];
  28. self.contentsTransform = CGAffineTransformIdentity;
  29. }
  30. #else
  31. [super renderInContext:ctx];
  32. #endif
  33. if( mask != nil ) {
  34. self.mask = mask;
  35. }
  36. }
  37. + (void)maskLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
  38. // if all that's masking is a single path, just clip to it
  39. if( layer.mask.sublayers.count == 1 && [[layer.mask.sublayers objectAtIndex:0] isKindOfClass:[CAShapeLayer class]] ) {
  40. CGPathRef maskPath = ((CAShapeLayer *) [layer.mask.sublayers objectAtIndex:0]).path;
  41. // we have to undo the offset from SVGClipPathLayer.layoutLayer
  42. CGAffineTransform offset = CGAffineTransformMakeTranslation(layer.mask.frame.origin.x, layer.mask.frame.origin.y);
  43. CGPathRef translatedPath = CGPathCreateCopyByTransformingPath(maskPath, &offset);
  44. CGContextAddPath(ctx, translatedPath);
  45. CGPathRelease(translatedPath);
  46. CGContextClip(ctx);
  47. } else {
  48. // otherwise, create an offscreen bitmap at screen resolution,
  49. CGFloat scale = MAX(layer.contentsScale, layer.mask.contentsScale);
  50. #if SVGKIT_MAC
  51. scale = MAX(scale, [[NSScreen mainScreen] backingScaleFactor]);
  52. #else
  53. scale = MAX(scale, [[UIScreen mainScreen] scale]);
  54. #endif
  55. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
  56. CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
  57. layer.bounds.size.width * scale,
  58. layer.bounds.size.height * scale,
  59. 8, 0,
  60. colorSpace,
  61. (CGBitmapInfo)kCGImageAlphaOnly);
  62. CGContextScaleCTM(offscreenContext, scale, scale);
  63. // render the mask to it, undoing the offset from SVGClipPathLayer.layoutLayer
  64. CGPoint offset = layer.mask.frame.origin;
  65. for (CALayer *child in layer.mask.sublayers)
  66. child.frame = CGRectOffset(child.frame, offset.x, offset.y);
  67. [layer.mask renderInContext:offscreenContext];
  68. for (CALayer *child in layer.mask.sublayers)
  69. child.frame = CGRectOffset(child.frame, -offset.x, -offset.y);
  70. // get an image from it,
  71. CGImageRef maskImage = CGBitmapContextCreateImage(offscreenContext);
  72. CGContextRelease(offscreenContext);
  73. CGColorSpaceRelease(colorSpace);
  74. // and mask with that
  75. CGContextClipToMask(ctx, layer.bounds, maskImage);
  76. CFRelease(maskImage);
  77. }
  78. }
  79. @end