CALayerWithClipRender.m 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. #import "SVGKDefine.h"
  10. #if SVGKIT_UIKIT
  11. #import <UIKit/UIKit.h>
  12. #endif
  13. @interface CALayer (ContentsTransform)
  14. @property CGAffineTransform contentsTransform;
  15. @end
  16. @implementation CALayerWithClipRender
  17. - (void)renderInContext:(CGContextRef)ctx {
  18. CALayer *mask = nil;
  19. if( self.mask != nil ) {
  20. [CALayerWithClipRender maskLayer:self inContext:ctx];
  21. mask = self.mask;
  22. self.mask = nil;
  23. }
  24. // 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.
  25. #if SVGKIT_MAC
  26. // If already flipped, which should be handled by Core Animation itself, ignore
  27. if (self.contentsAreFlipped) {
  28. [super renderInContext:ctx];
  29. } else {
  30. self.contentsTransform = CGAffineTransformMake(1, 0, 0, -1, 0, self.bounds.size.height);
  31. [super renderInContext:ctx];
  32. self.contentsTransform = CGAffineTransformIdentity;
  33. }
  34. #else
  35. [super renderInContext:ctx];
  36. #endif
  37. if( mask != nil ) {
  38. self.mask = mask;
  39. }
  40. }
  41. + (void)maskLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
  42. // if all that's masking is a single path, just clip to it
  43. if( layer.mask.sublayers.count == 1 && [[layer.mask.sublayers objectAtIndex:0] isKindOfClass:[CAShapeLayer class]] ) {
  44. CGPathRef maskPath = ((CAShapeLayer *) [layer.mask.sublayers objectAtIndex:0]).path;
  45. // we have to undo the offset from SVGClipPathLayer.layoutLayer
  46. CGAffineTransform offset = CGAffineTransformMakeTranslation(layer.mask.frame.origin.x, layer.mask.frame.origin.y);
  47. CGPathRef translatedPath = CGPathCreateCopyByTransformingPath(maskPath, &offset);
  48. CGContextAddPath(ctx, translatedPath);
  49. CGPathRelease(translatedPath);
  50. CGContextClip(ctx);
  51. } else {
  52. // otherwise, create an offscreen bitmap at screen resolution,
  53. CGFloat scale = MAX(layer.contentsScale, layer.mask.contentsScale);
  54. #if SVGKIT_MAC
  55. scale = MAX(scale, [[NSScreen mainScreen] backingScaleFactor]);
  56. #else
  57. scale = MAX(scale, [[UIScreen mainScreen] scale]);
  58. #endif
  59. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
  60. CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
  61. layer.bounds.size.width * scale,
  62. layer.bounds.size.height * scale,
  63. 8, 0,
  64. colorSpace,
  65. (CGBitmapInfo)kCGImageAlphaOnly);
  66. CGContextScaleCTM(offscreenContext, scale, scale);
  67. // render the mask to it, undoing the offset from SVGClipPathLayer.layoutLayer
  68. CGPoint offset = layer.mask.frame.origin;
  69. for (CALayer *child in layer.mask.sublayers)
  70. child.frame = CGRectOffset(child.frame, offset.x, offset.y);
  71. [layer.mask renderInContext:offscreenContext];
  72. for (CALayer *child in layer.mask.sublayers)
  73. child.frame = CGRectOffset(child.frame, -offset.x, -offset.y);
  74. // get an image from it,
  75. CGImageRef maskImage = CGBitmapContextCreateImage(offscreenContext);
  76. CGContextRelease(offscreenContext);
  77. CGColorSpaceRelease(colorSpace);
  78. // and mask with that
  79. CGContextClipToMask(ctx, layer.bounds, maskImage);
  80. if( maskImage != nil ) {
  81. CFRelease(maskImage);
  82. }
  83. }
  84. }
  85. @end