/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageGraphics.h" #import "NSImage+Compatibility.h" #import "SDImageCoderHelper.h" #import "objc/runtime.h" #if SD_MAC static void *kNSGraphicsContextScaleFactorKey; static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) { if (scale == 0) { // Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the device’s main screen if scale is 0. NSScreen *mainScreen = nil; if (@available(macOS 10.12, *)) { mainScreen = [NSScreen mainScreen]; } else { mainScreen = [NSScreen screens].firstObject; } scale = mainScreen.backingScaleFactor ?: 1.0f; } size_t width = ceil(size.width * scale); size_t height = ceil(size.height * scale); if (width < 1 || height < 1) return NULL; CGColorSpaceRef space = [SDImageCoderHelper colorSpaceGetDeviceRGB]; // kCGImageAlphaNone is not supported in CGBitmapContextCreate. // Check #3330 for more detail about why this bitmap is choosen. // From v5.17.0, use runtime detection of bitmap info instead of hardcode. // However, macOS's runtime detection will also call this function, cause recursive, so still hardcode here CGBitmapInfo bitmapInfo; if (!opaque) { // [NSImage imageWithSize:flipped:drawingHandler:] returns float(16-bits) RGBA8888 on alpha image, which we don't need bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast; } else { bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; } CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo); if (!context) { return NULL; } CGContextScaleCTM(context, scale, scale); return context; } #endif CGContextRef SDGraphicsGetCurrentContext(void) { #if SD_UIKIT || SD_WATCH return UIGraphicsGetCurrentContext(); #else return NSGraphicsContext.currentContext.CGContext; #endif } void SDGraphicsBeginImageContext(CGSize size) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContext(size); #else SDGraphicsBeginImageContextWithOptions(size, NO, 1.0); #endif } void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContextWithOptions(size, opaque, scale); #else CGContextRef context = SDCGContextCreateBitmapContext(size, opaque, scale); if (!context) { return; } NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]; objc_setAssociatedObject(graphicsContext, &kNSGraphicsContextScaleFactorKey, @(scale), OBJC_ASSOCIATION_RETAIN); CGContextRelease(context); [NSGraphicsContext saveGraphicsState]; NSGraphicsContext.currentContext = graphicsContext; #endif } void SDGraphicsEndImageContext(void) { #if SD_UIKIT || SD_WATCH UIGraphicsEndImageContext(); #else [NSGraphicsContext restoreGraphicsState]; #endif } UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { #if SD_UIKIT || SD_WATCH return UIGraphicsGetImageFromCurrentImageContext(); #else NSGraphicsContext *context = NSGraphicsContext.currentContext; CGContextRef contextRef = context.CGContext; if (!contextRef) { return nil; } CGImageRef imageRef = CGBitmapContextCreateImage(contextRef); if (!imageRef) { return nil; } CGFloat scale = 0; NSNumber *scaleFactor = objc_getAssociatedObject(context, &kNSGraphicsContextScaleFactorKey); if ([scaleFactor isKindOfClass:[NSNumber class]]) { scale = scaleFactor.doubleValue; } if (!scale) { // reset to the scale factor of the device’s main screen if scale is 0. NSScreen *mainScreen = nil; if (@available(macOS 10.12, *)) { mainScreen = [NSScreen mainScreen]; } else { mainScreen = [NSScreen screens].firstObject; } scale = mainScreen.backingScaleFactor ?: 1.0f; } NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp]; CGImageRelease(imageRef); return image; #endif }