SVGKExporterNSImage.m 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #import "SVGKExporterNSImage.h"
  2. #import "SVGUtils.h"
  3. #import "SVGKImage+CGContext.h" // needed for Context calls
  4. #import <objc/runtime.h>
  5. @implementation SVGKExporterNSImage
  6. +(NSImage*) exportAsNSImage:(SVGKImage *)image
  7. {
  8. return [self exportAsNSImage:image antiAliased:TRUE curveFlatnessFactor:1.0 interpolationQuality:kCGInterpolationDefault];
  9. }
  10. +(NSImage*) exportAsNSImage:(SVGKImage*) image antiAliased:(BOOL) shouldAntialias curveFlatnessFactor:(CGFloat) multiplyFlatness interpolationQuality:(CGInterpolationQuality) interpolationQuality
  11. {
  12. if( [image hasSize] )
  13. {
  14. SVGKitLogVerbose(@"[%@] DEBUG: Generating a NSImage using the current root-object's viewport (may have been overridden by user code): {0,0,%2.3f,%2.3f}", [self class], image.size.width, image.size.height);
  15. SVGKGraphicsBeginImageContextWithOptions( image.size, FALSE, [NSScreen mainScreen].backingScaleFactor);
  16. CGContextRef context = SVGKGraphicsGetCurrentContext();
  17. [image renderToContext:context antiAliased:shouldAntialias curveFlatnessFactor:multiplyFlatness interpolationQuality:interpolationQuality flipYaxis:TRUE];
  18. NSImage* result = SVGKGraphicsGetImageFromCurrentImageContext();
  19. SVGKGraphicsEndImageContext();
  20. return result;
  21. }
  22. else
  23. {
  24. NSAssert(FALSE, @"You asked to export an SVG to bitmap, but 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");
  25. return nil;
  26. }
  27. }
  28. static void *kNSGraphicsContextScaleFactorKey;
  29. static CGContextRef SVGKCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
  30. size_t width = ceil(size.width * scale);
  31. size_t height = ceil(size.height * scale);
  32. if (width < 1 || height < 1) return NULL;
  33. //pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc
  34. CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
  35. CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
  36. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
  37. CGColorSpaceRelease(space);
  38. if (!context) {
  39. return NULL;
  40. }
  41. if (scale == 0) {
  42. // Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the device’s main screen if scale is 0.
  43. scale = [NSScreen mainScreen].backingScaleFactor;
  44. }
  45. CGContextScaleCTM(context, scale, scale);
  46. return context;
  47. }
  48. static void SVGKGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) {
  49. CGContextRef context = SVGKCreateBitmapContext(size, opaque, scale);
  50. if (!context) {
  51. return;
  52. }
  53. NSGraphicsContext *graphicsContext;
  54. #pragma clang diagnostic push
  55. #pragma clang diagnostic ignored "-Wunguarded-availability"
  56. if ([NSGraphicsContext respondsToSelector:@selector(graphicsContextWithGraphicsPort:flipped:)]) {
  57. graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
  58. } else {
  59. graphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
  60. }
  61. #pragma clang diagnostic pop
  62. objc_setAssociatedObject(graphicsContext, &kNSGraphicsContextScaleFactorKey, @(scale), OBJC_ASSOCIATION_RETAIN);
  63. CGContextRelease(context);
  64. [NSGraphicsContext saveGraphicsState];
  65. NSGraphicsContext.currentContext = graphicsContext;
  66. }
  67. static CGContextRef SVGKGraphicsGetCurrentContext(void) {
  68. NSGraphicsContext *context = NSGraphicsContext.currentContext;
  69. #pragma clang diagnostic push
  70. #pragma clang diagnostic ignored "-Wunguarded-availability"
  71. if ([context respondsToSelector:@selector(CGContext)]) {
  72. return context.CGContext;
  73. } else {
  74. return context.graphicsPort;
  75. }
  76. #pragma clang diagnostic pop
  77. }
  78. static void SVGKGraphicsEndImageContext(void) {
  79. [NSGraphicsContext restoreGraphicsState];
  80. }
  81. static NSImage * SVGKGraphicsGetImageFromCurrentImageContext(void) {
  82. NSGraphicsContext *context = NSGraphicsContext.currentContext;
  83. CGContextRef contextRef;
  84. #pragma clang diagnostic push
  85. #pragma clang diagnostic ignored "-Wunguarded-availability"
  86. if ([context respondsToSelector:@selector(CGContext)]) {
  87. contextRef = context.CGContext;
  88. } else {
  89. contextRef = context.graphicsPort;
  90. }
  91. #pragma clang diagnostic pop
  92. if (!contextRef) {
  93. return nil;
  94. }
  95. CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
  96. if (!imageRef) {
  97. return nil;
  98. }
  99. CGFloat scale = 0;
  100. NSNumber *scaleFactor = objc_getAssociatedObject(context, &kNSGraphicsContextScaleFactorKey);
  101. if ([scaleFactor isKindOfClass:[NSNumber class]]) {
  102. scale = scaleFactor.doubleValue;
  103. }
  104. if (!scale) {
  105. // reset to the scale factor of the device’s main screen if scale is 0.
  106. scale = [NSScreen mainScreen].backingScaleFactor;
  107. }
  108. NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:imageRef];
  109. CGFloat pixelWidth = imageRep.pixelsWide;
  110. CGFloat pixelHeight = imageRep.pixelsHigh;
  111. NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale);
  112. NSImage *image = [[NSImage alloc] initWithSize:size];
  113. [image addRepresentation:imageRep];
  114. CGImageRelease(imageRef);
  115. return image;
  116. }
  117. @end