SVGKImage+CGContext.m 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. //
  2. // SVGKImage+CGContext.m
  3. // SVGKit-iOS
  4. //
  5. // Created by adam on 22/12/2013.
  6. // Copyright (c) 2013 na. All rights reserved.
  7. //
  8. #import "SVGKImage+CGContext.h"
  9. #import "SVGRect.h"
  10. #import "SVGSVGElement.h"
  11. @implementation SVGKImage (CGContext)
  12. -(CGContextRef) newCGContextAutosizedToFit
  13. {
  14. NSAssert( [self hasSize], @"Cannot export this image because the SVG file has infinite size. Either fix the SVG file, or set an explicit size you want it to be exported at (by calling .size = something on this SVGKImage instance");
  15. if( ! [self hasSize] )
  16. return NULL;
  17. SVGKitLogVerbose(@"[%@] DEBUG: Generating a CGContextRef using the current root-object's viewport (may have been overridden by user code): {0,0,%2.3f,%2.3f}", [self class], self.size.width, self.size.height);
  18. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  19. CGContextRef context = CGBitmapContextCreate( NULL/*malloc( self.size.width * self.size.height * 4 )*/, self.size.width, self.size.height, 8, 4 * self.size.width, colorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipLast );
  20. CGColorSpaceRelease( colorSpace );
  21. return context;
  22. }
  23. - (void)renderInContext:(CGContextRef)ctx
  24. {
  25. CALayer *layerTree = self.CALayerTree;
  26. [self temporaryWorkaroundPreprocessRenderingLayerTree:layerTree];
  27. [layerTree renderInContext:ctx];
  28. [self temporaryWorkaroundPostProcessRenderingLayerTree:layerTree];
  29. }
  30. /**
  31. Shared between multiple different "export..." methods
  32. */
  33. -(void) renderToContext:(CGContextRef) context antiAliased:(BOOL) shouldAntialias curveFlatnessFactor:(CGFloat) multiplyFlatness interpolationQuality:(CGInterpolationQuality) interpolationQuality flipYaxis:(BOOL) flipYaxis
  34. {
  35. NSAssert( [self hasSize], @"Cannot scale this image because the SVG file has infinite size. Either fix the SVG file, or set an explicit size you want it to be exported at (by calling .size = something on this SVGKImage instance");
  36. NSDate* startTime;
  37. startTime = [NSDate date];
  38. if( SVGRectIsInitialized(self.DOMTree.viewport) )
  39. SVGKitLogInfo(@"[%@] DEBUG: rendering to CGContext using the current root-object's viewport (may have been overridden by user code): %@", [self class], NSStringFromSVGRect(self.DOMTree.viewport) );
  40. /** Typically a 10% performance improvement right here */
  41. if( !shouldAntialias )
  42. CGContextSetShouldAntialias( context, FALSE );
  43. /** Apple refuses to let you reset this, because they are selfish */
  44. CGContextSetFlatness( context, multiplyFlatness );
  45. /** Apple's own performance hints system */
  46. CGContextSetInterpolationQuality( context, interpolationQuality );
  47. /** Quartz, CoreGraphics, and CoreAnimation all use an "upside-down" co-ordinate system.
  48. This means that images rendered are upside down.
  49. Apple's UIImage class automatically "un-flips" this - but if you are rendering raw NSData (which is 5x-10x faster than creating UIImages!) then the flipping is "lost"
  50. by Apple's API's.
  51. The only way to fix it is to pre-transform by y = -y
  52. This is VERY useful if you want to render SVG's into OpenGL textures!
  53. */
  54. if( flipYaxis )
  55. {
  56. NSAssert( [self hasSize], @"Cannot flip this image in Y because the SVG file has infinite size. Either fix the SVG file, or set an explicit size you want it to be treated as (by calling .size = something on this SVGKImage instance");
  57. CGContextTranslateCTM(context, 0, self.size.height );
  58. CGContextScaleCTM(context, 1.0, -1.0);
  59. }
  60. /**
  61. The method that everyone hates, because Apple refuses to fix / implement it properly: renderInContext:
  62. It's slow.
  63. It's broken (according to the official API docs)
  64. But ... it's all that Apple gives us
  65. */
  66. [self renderInContext:context];
  67. NSMutableString* perfImprovements = [NSMutableString string];
  68. if( shouldAntialias )
  69. [perfImprovements appendString:@" NO-ANTI-ALIAS"];
  70. if( perfImprovements.length < 1 )
  71. [perfImprovements appendString:@"NONE"];
  72. SVGKitLogVerbose(@"[%@] renderToContext: time taken to render CALayers to CGContext (perf improvements:%@): %2.3f seconds)", [self class], perfImprovements, -1.0f * [startTime timeIntervalSinceNow] );
  73. }
  74. /**
  75. macOS (at least macOS 10.13 still exist) contains bug in `-[CALayer renderInContext:]` method for `CATextLayer` or `CALayer` with CGImage contents
  76. which will use flipped coordinate system to draw text/image content. However, iOS/tvOS works fine. We have to hack to fix it. :)
  77. note when using sublayer drawing (`USE_SUBLAYERS_INSTEAD_OF_BLIT` = 1) this issue disappear
  78. @param layerTree layerTree
  79. */
  80. - (void)temporaryWorkaroundPreprocessRenderingLayerTree:(CALayer *)layerTree {
  81. #if SVGKIT_MAC
  82. BOOL fixFlip = NO;
  83. if ([layerTree isKindOfClass:[CATextLayer class]]) {
  84. fixFlip = YES;
  85. } else if (layerTree.contents != nil) {
  86. fixFlip = YES;
  87. }
  88. if (fixFlip) {
  89. // Hack to apply flip for content
  90. NSAffineTransform *flip = [NSAffineTransform transform];
  91. [flip translateXBy:0 yBy:layerTree.bounds.size.height];
  92. [flip scaleXBy:1.0 yBy:-1.0];
  93. [layerTree setValue:flip forKey:@"contentsTransform"];
  94. }
  95. for (CALayer *layer in layerTree.sublayers) {
  96. [self temporaryWorkaroundPreprocessRenderingLayerTree:layer];
  97. }
  98. #endif
  99. }
  100. - (void)temporaryWorkaroundPostProcessRenderingLayerTree:(CALayer *)layerTree {
  101. #if SVGKIT_MAC
  102. BOOL fixFlip = NO;
  103. if ([layerTree isKindOfClass:[CATextLayer class]]) {
  104. fixFlip = YES;
  105. } else if (layerTree.contents != nil) {
  106. fixFlip = YES;
  107. }
  108. if (fixFlip) {
  109. // Hack to recover flip for content
  110. NSAffineTransform *flip = [NSAffineTransform transform];
  111. [layerTree setValue:flip forKey:@"contentsTransform"];
  112. }
  113. for (CALayer *layer in layerTree.sublayers) {
  114. [self temporaryWorkaroundPostProcessRenderingLayerTree:layer];
  115. }
  116. #endif
  117. }
  118. @end