123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "UIImage+Transform.h"
- #import "NSImage+Compatibility.h"
- #import "SDImageGraphics.h"
- #import "SDGraphicsImageRenderer.h"
- #import "NSBezierPath+SDRoundedCorners.h"
- #import <Accelerate/Accelerate.h>
- #if SD_UIKIT || SD_MAC
- #import <CoreImage/CoreImage.h>
- #endif
- static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) {
- rect = CGRectStandardize(rect);
- size.width = size.width < 0 ? -size.width : size.width;
- size.height = size.height < 0 ? -size.height : size.height;
- CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
- switch (scaleMode) {
- case SDImageScaleModeAspectFit:
- case SDImageScaleModeAspectFill: {
- if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
- size.width < 0.01 || size.height < 0.01) {
- rect.origin = center;
- rect.size = CGSizeZero;
- } else {
- CGFloat scale;
- if (scaleMode == SDImageScaleModeAspectFit) {
- if (size.width / size.height < rect.size.width / rect.size.height) {
- scale = rect.size.height / size.height;
- } else {
- scale = rect.size.width / size.width;
- }
- } else {
- if (size.width / size.height < rect.size.width / rect.size.height) {
- scale = rect.size.width / size.width;
- } else {
- scale = rect.size.height / size.height;
- }
- }
- size.width *= scale;
- size.height *= scale;
- rect.size = size;
- rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
- }
- } break;
- case SDImageScaleModeFill:
- default: {
- rect = rect;
- }
- }
- return rect;
- }
- static inline UIColor * SDGetColorFromGrayscale(Pixel_88 pixel, CGBitmapInfo bitmapInfo, CGColorSpaceRef cgColorSpace) {
- // Get alpha info, byteOrder info
- CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
- CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
- CGFloat w = 0, a = 1;
-
- BOOL byteOrderNormal = NO;
- switch (byteOrderInfo) {
- case kCGBitmapByteOrderDefault: {
- byteOrderNormal = YES;
- } break;
- case kCGBitmapByteOrder32Little: {
- } break;
- case kCGBitmapByteOrder32Big: {
- byteOrderNormal = YES;
- } break;
- default: break;
- }
- switch (alphaInfo) {
- case kCGImageAlphaPremultipliedFirst:
- case kCGImageAlphaFirst: {
- if (byteOrderNormal) {
- // AW
- a = pixel[0] / 255.0;
- w = pixel[1] / 255.0;
- } else {
- // WA
- w = pixel[0] / 255.0;
- a = pixel[1] / 255.0;
- }
- }
- break;
- case kCGImageAlphaPremultipliedLast:
- case kCGImageAlphaLast: {
- if (byteOrderNormal) {
- // WA
- w = pixel[0] / 255.0;
- a = pixel[1] / 255.0;
- } else {
- // AW
- a = pixel[0] / 255.0;
- w = pixel[1] / 255.0;
- }
- }
- break;
- case kCGImageAlphaNone: {
- // W
- w = pixel[0] / 255.0;
- }
- break;
- case kCGImageAlphaNoneSkipLast: {
- if (byteOrderNormal) {
- // WX
- w = pixel[0] / 255.0;
- } else {
- // XW
- a = pixel[1] / 255.0;
- }
- }
- break;
- case kCGImageAlphaNoneSkipFirst: {
- if (byteOrderNormal) {
- // XW
- a = pixel[1] / 255.0;
- } else {
- // WX
- a = pixel[0] / 255.0;
- }
- }
- break;
- case kCGImageAlphaOnly: {
- // A
- a = pixel[0] / 255.0;
- }
- break;
- default:
- break;
- }
- #if SD_MAC
- // Mac supports ColorSync, to ensure the same bahvior, we convert color to sRGB
- NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:cgColorSpace];
- CGFloat components[2] = {w, a};
- NSColor *color = [NSColor colorWithColorSpace:colorSpace components:components count:2];
- return [color colorUsingColorSpace:NSColorSpace.genericGamma22GrayColorSpace];
- #else
- return [UIColor colorWithWhite:w alpha:a];
- #endif
- }
- static inline UIColor * SDGetColorFromRGBA(Pixel_8888 pixel, CGBitmapInfo bitmapInfo, CGColorSpaceRef cgColorSpace) {
- // Get alpha info, byteOrder info
- CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
- CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
- CGFloat r = 0, g = 0, b = 0, a = 1;
-
- BOOL byteOrderNormal = NO;
- switch (byteOrderInfo) {
- case kCGBitmapByteOrderDefault: {
- byteOrderNormal = YES;
- } break;
- case kCGBitmapByteOrder16Little:
- case kCGBitmapByteOrder32Little: {
- } break;
- case kCGBitmapByteOrder16Big:
- case kCGBitmapByteOrder32Big: {
- byteOrderNormal = YES;
- } break;
- default: break;
- }
- switch (alphaInfo) {
- case kCGImageAlphaPremultipliedFirst:
- case kCGImageAlphaFirst: {
- if (byteOrderNormal) {
- // ARGB8888
- a = pixel[0] / 255.0;
- r = pixel[1] / 255.0;
- g = pixel[2] / 255.0;
- b = pixel[3] / 255.0;
- } else {
- // BGRA8888
- b = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- r = pixel[2] / 255.0;
- a = pixel[3] / 255.0;
- }
- }
- break;
- case kCGImageAlphaPremultipliedLast:
- case kCGImageAlphaLast: {
- if (byteOrderNormal) {
- // RGBA8888
- r = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- b = pixel[2] / 255.0;
- a = pixel[3] / 255.0;
- } else {
- // ABGR8888
- a = pixel[0] / 255.0;
- b = pixel[1] / 255.0;
- g = pixel[2] / 255.0;
- r = pixel[3] / 255.0;
- }
- }
- break;
- case kCGImageAlphaNone: {
- if (byteOrderNormal) {
- // RGB
- r = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- b = pixel[2] / 255.0;
- } else {
- // BGR
- b = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- r = pixel[2] / 255.0;
- }
- }
- break;
- case kCGImageAlphaNoneSkipLast: {
- if (byteOrderNormal) {
- // RGBX
- r = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- b = pixel[2] / 255.0;
- } else {
- // XBGR
- b = pixel[1] / 255.0;
- g = pixel[2] / 255.0;
- r = pixel[3] / 255.0;
- }
- }
- break;
- case kCGImageAlphaNoneSkipFirst: {
- if (byteOrderNormal) {
- // XRGB
- r = pixel[1] / 255.0;
- g = pixel[2] / 255.0;
- b = pixel[3] / 255.0;
- } else {
- // BGRX
- b = pixel[0] / 255.0;
- g = pixel[1] / 255.0;
- r = pixel[2] / 255.0;
- }
- }
- break;
- case kCGImageAlphaOnly: {
- // A
- a = pixel[0] / 255.0;
- }
- break;
- default:
- break;
- }
- #if SD_MAC
- // Mac supports ColorSync, to ensure the same bahvior, we convert color to sRGB
- NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:cgColorSpace];
- CGFloat components[4] = {r, g, b, a};
- NSColor *color = [NSColor colorWithColorSpace:colorSpace components:components count:4];
- return [color colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
- #else
- return [UIColor colorWithRed:r green:g blue:b alpha:a];
- #endif
- }
- #if SD_UIKIT || SD_MAC
- // Create-Rule, caller should call CGImageRelease
- static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ciImage) {
- CGImageRef imageRef = NULL;
- if (@available(iOS 10, macOS 10.12, tvOS 10, *)) {
- imageRef = ciImage.CGImage;
- }
- if (!imageRef) {
- CIContext *context = [CIContext context];
- imageRef = [context createCGImage:ciImage fromRect:ciImage.extent];
- } else {
- CGImageRetain(imageRef);
- }
- return imageRef;
- }
- #endif
- @implementation UIImage (Transform)
- - (void)sd_drawInRect:(CGRect)rect context:(CGContextRef)context scaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
- CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode);
- if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
- if (clips) {
- if (context) {
- CGContextSaveGState(context);
- CGContextAddRect(context, rect);
- CGContextClip(context);
- [self drawInRect:drawRect];
- CGContextRestoreGState(context);
- }
- } else {
- [self drawInRect:drawRect];
- }
- }
- - (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode {
- if (size.width <= 0 || size.height <= 0) return nil;
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = self.scale;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) context:context scaleMode:scaleMode clipsToBounds:NO];
- }];
- return image;
- }
- - (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect {
- rect.origin.x *= self.scale;
- rect.origin.y *= self.scale;
- rect.size.width *= self.scale;
- rect.size.height *= self.scale;
- if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
-
- #if SD_UIKIT || SD_MAC
- // CIImage shortcut
- if (self.CIImage) {
- CGRect croppingRect = CGRectMake(rect.origin.x, self.size.height - CGRectGetMaxY(rect), rect.size.width, rect.size.height);
- CIImage *ciImage = [self.CIImage imageByCroppingToRect:croppingRect];
- #if SD_UIKIT
- UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- return image;
- }
- #endif
-
- CGImageRef imageRef = self.CGImage;
- if (!imageRef) {
- return nil;
- }
-
- CGImageRef croppedImageRef = CGImageCreateWithImageInRect(imageRef, rect);
- if (!croppedImageRef) {
- return nil;
- }
- #if SD_UIKIT || SD_WATCH
- UIImage *image = [UIImage imageWithCGImage:croppedImageRef scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCGImage:croppedImageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- CGImageRelease(croppedImageRef);
- return image;
- }
- - (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor {
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = self.scale;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
-
- CGFloat minSize = MIN(self.size.width, self.size.height);
- if (borderWidth < minSize / 2) {
- #if SD_UIKIT || SD_WATCH
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
- #else
- NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
- #endif
- [path closePath];
-
- CGContextSaveGState(context);
- [path addClip];
- [self drawInRect:rect];
- CGContextRestoreGState(context);
- }
-
- if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
- CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
- CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
- CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
- #if SD_UIKIT || SD_WATCH
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
- #else
- NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
- #endif
- [path closePath];
-
- path.lineWidth = borderWidth;
- [borderColor setStroke];
- [path stroke];
- }
- }];
- return image;
- }
- - (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize {
- size_t width = self.size.width;
- size_t height = self.size.height;
- CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height),
- fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity);
- #if SD_UIKIT || SD_MAC
- // CIImage shortcut
- if (self.CIImage) {
- CIImage *ciImage = self.CIImage;
- if (fitSize) {
- CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
- ciImage = [ciImage imageByApplyingTransform:transform];
- } else {
- CIFilter *filter = [CIFilter filterWithName:@"CIStraightenFilter"];
- [filter setValue:ciImage forKey:kCIInputImageKey];
- [filter setValue:@(angle) forKey:kCIInputAngleKey];
- ciImage = filter.outputImage;
- }
- #if SD_UIKIT || SD_WATCH
- UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- return image;
- }
- #endif
-
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = self.scale;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- CGContextSetShouldAntialias(context, true);
- CGContextSetAllowsAntialiasing(context, true);
- CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
- CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
- #if SD_UIKIT || SD_WATCH
- // Use UIKit coordinate system counterclockwise (⟲)
- CGContextRotateCTM(context, -angle);
- #else
- CGContextRotateCTM(context, angle);
- #endif
-
- [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)];
- }];
- return image;
- }
- - (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
- size_t width = self.size.width;
- size_t height = self.size.height;
- #if SD_UIKIT || SD_MAC
- // CIImage shortcut
- if (self.CIImage) {
- CGAffineTransform transform = CGAffineTransformIdentity;
- // Use UIKit coordinate system
- if (horizontal) {
- CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
- transform = CGAffineTransformConcat(transform, flipHorizontal);
- }
- if (vertical) {
- CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
- transform = CGAffineTransformConcat(transform, flipVertical);
- }
- CIImage *ciImage = [self.CIImage imageByApplyingTransform:transform];
- #if SD_UIKIT
- UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- return image;
- }
- #endif
-
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = self.scale;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- // Use UIKit coordinate system
- if (horizontal) {
- CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
- CGContextConcatCTM(context, flipHorizontal);
- }
- if (vertical) {
- CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
- CGContextConcatCTM(context, flipVertical);
- }
- [self drawInRect:CGRectMake(0, 0, width, height)];
- }];
- return image;
- }
- #pragma mark - Image Blending
- - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor {
- BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
- if (!hasTint) {
- return self;
- }
-
- #if SD_UIKIT || SD_MAC
- // CIImage shortcut
- if (self.CIImage) {
- CIImage *ciImage = self.CIImage;
- CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]];
- colorImage = [colorImage imageByCroppingToRect:ciImage.extent];
- CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"];
- [filter setValue:colorImage forKey:kCIInputImageKey];
- [filter setValue:ciImage forKey:kCIInputBackgroundImageKey];
- ciImage = filter.outputImage;
- #if SD_UIKIT
- UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- return image;
- }
- #endif
-
- CGSize size = self.size;
- CGRect rect = { CGPointZero, size };
- CGFloat scale = self.scale;
-
- // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
- CGBlendMode blendMode = kCGBlendModeSourceAtop;
-
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = scale;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- [self drawInRect:rect];
- CGContextSetBlendMode(context, blendMode);
- CGContextSetFillColorWithColor(context, tintColor.CGColor);
- CGContextFillRect(context, rect);
- }];
- return image;
- }
- - (nullable UIColor *)sd_colorAtPoint:(CGPoint)point {
- CGImageRef imageRef = NULL;
- // CIImage compatible
- #if SD_UIKIT || SD_MAC
- if (self.CIImage) {
- imageRef = SDCreateCGImageFromCIImage(self.CIImage);
- }
- #endif
- if (!imageRef) {
- imageRef = self.CGImage;
- CGImageRetain(imageRef);
- }
- if (!imageRef) {
- return nil;
- }
-
- // Check point
- CGFloat width = CGImageGetWidth(imageRef);
- CGFloat height = CGImageGetHeight(imageRef);
- if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) {
- CGImageRelease(imageRef);
- return nil;
- }
-
- // Get pixels
- CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
- if (!provider) {
- CGImageRelease(imageRef);
- return nil;
- }
- CFDataRef data = CGDataProviderCopyData(provider);
- if (!data) {
- CGImageRelease(imageRef);
- return nil;
- }
-
- // Get pixel at point
- size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
- size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
-
- CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, components);
- if (CFDataGetLength(data) < range.location + range.length) {
- CFRelease(data);
- CGImageRelease(imageRef);
- return nil;
- }
- // Get color space for transform
- CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
-
- // greyscale
- if (components == 2) {
- Pixel_88 pixel = {0};
- CFDataGetBytes(data, range, pixel);
- CFRelease(data);
- CGImageRelease(imageRef);
- // Convert to color
- return SDGetColorFromGrayscale(pixel, bitmapInfo, colorSpace);
- } else if (components == 3 || components == 4) {
- // RGB/RGBA
- Pixel_8888 pixel = {0};
- CFDataGetBytes(data, range, pixel);
- CFRelease(data);
- CGImageRelease(imageRef);
- // Convert to color
- return SDGetColorFromRGBA(pixel, bitmapInfo, colorSpace);
- } else {
- NSLog(@"Unsupported components: %zu", components);
- CFRelease(data);
- CGImageRelease(imageRef);
- return nil;
- }
- }
- - (nullable NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect {
- CGImageRef imageRef = NULL;
- // CIImage compatible
- #if SD_UIKIT || SD_MAC
- if (self.CIImage) {
- imageRef = SDCreateCGImageFromCIImage(self.CIImage);
- }
- #endif
- if (!imageRef) {
- imageRef = self.CGImage;
- CGImageRetain(imageRef);
- }
- if (!imageRef) {
- return nil;
- }
-
- // Check rect
- CGFloat width = CGImageGetWidth(imageRef);
- CGFloat height = CGImageGetHeight(imageRef);
- if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) {
- CGImageRelease(imageRef);
- return nil;
- }
-
- // Get pixels
- CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
- if (!provider) {
- CGImageRelease(imageRef);
- return nil;
- }
- CFDataRef data = CGDataProviderCopyData(provider);
- if (!data) {
- CGImageRelease(imageRef);
- return nil;
- }
-
- // Get pixels with rect
- size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
- size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
-
- size_t start = bytesPerRow * CGRectGetMinY(rect) + components * CGRectGetMinX(rect);
- size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect);
- if (CFDataGetLength(data) < (CFIndex)end) {
- CFRelease(data);
- CGImageRelease(imageRef);
- return nil;
- }
-
- const UInt8 *pixels = CFDataGetBytePtr(data);
- size_t row = CGRectGetMinY(rect);
- size_t col = CGRectGetMaxX(rect);
-
- // Convert to color
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
- NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)];
- // ColorSpace
- CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
- for (size_t index = start; index < end; index += components) {
- if (index >= row * bytesPerRow + col * components) {
- // Index beyond the end of current row, go next row
- row++;
- index = row * bytesPerRow + CGRectGetMinX(rect) * components;
- index -= components;
- continue;
- }
- UIColor *color;
- if (components == 2) {
- Pixel_88 pixel = {pixels[index], pixel[index+1]};
- color = SDGetColorFromGrayscale(pixel, bitmapInfo, colorSpace);
- } else {
- if (components == 3) {
- Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], 0};
- color = SDGetColorFromRGBA(pixel, bitmapInfo, colorSpace);
- } else if (components == 4) {
- Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]};
- color = SDGetColorFromRGBA(pixel, bitmapInfo, colorSpace);
- } else {
- NSLog(@"Unsupported components: %zu", components);
- }
- }
- if (color) {
- [colors addObject:color];
- }
- }
- CFRelease(data);
- CGImageRelease(imageRef);
-
- return [colors copy];
- }
- #pragma mark - Image Effect
- // We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIGaussianBlur`. For other blur effect, use any filter in `CICategoryBlur`
- - (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius {
- if (self.size.width < 1 || self.size.height < 1) {
- return nil;
- }
- BOOL hasBlur = blurRadius > __FLT_EPSILON__;
- if (!hasBlur) {
- return self;
- }
-
- CGFloat scale = self.scale;
- CGFloat inputRadius = blurRadius * scale;
- #if SD_UIKIT || SD_MAC
- if (self.CIImage) {
- CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
- [filter setValue:self.CIImage forKey:kCIInputImageKey];
- [filter setValue:@(inputRadius) forKey:kCIInputRadiusKey];
- CIImage *ciImage = filter.outputImage;
- ciImage = [ciImage imageByCroppingToRect:CGRectMake(0, 0, self.size.width, self.size.height)];
- #if SD_UIKIT
- UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- return image;
- }
- #endif
-
- CGImageRef imageRef = self.CGImage;
- if (!imageRef) {
- return nil;
- }
-
- vImage_Buffer effect = {}, scratch = {};
- vImage_Buffer *input = NULL, *output = NULL;
-
- vImage_CGImageFormat format = {
- .bitsPerComponent = 8,
- .bitsPerPixel = 32,
- .colorSpace = NULL,
- .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
- .version = 0,
- .decode = NULL,
- .renderingIntent = CGImageGetRenderingIntent(imageRef)
- };
-
- vImage_Error err;
- err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImageNoFlags); // vImage will convert to format we requests, no need `vImageConvert`
- if (err != kvImageNoError) {
- NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
- return nil;
- }
- err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
- if (err != kvImageNoError) {
- NSLog(@"UIImage+Transform error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
- return nil;
- }
-
- input = &effect;
- output = &scratch;
-
- // See: https://developer.apple.com/library/archive/samplecode/UIImageEffects/Introduction/Intro.html
- if (hasBlur) {
- // A description of how to compute the box kernel width from the Gaussian
- // radius (aka standard deviation) appears in the SVG spec:
- // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
- //
- // For larger values of 's' (s >= 2.0), an approximation can be used: Three
- // successive box-blurs build a piece-wise quadratic convolution kernel, which
- // approximates the Gaussian kernel to within roughly 3%.
- //
- // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
- //
- // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
- //
- if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
- uint32_t radius = floor(inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5);
- radius |= 1; // force radius to be odd so that the three box-blur methodology works.
- NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
- void *temp = malloc(tempSize);
- vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
- vImageBoxConvolve_ARGB8888(output, input, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
- vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
- free(temp);
-
- vImage_Buffer *tmp = input;
- input = output;
- output = tmp;
- }
-
- CGImageRef effectCGImage = NULL;
- effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoAllocate, NULL);
- if (effectCGImage == NULL) {
- effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
- free(input->data);
- }
- free(output->data);
- #if SD_UIKIT || SD_WATCH
- UIImage *outputImage = [UIImage imageWithCGImage:effectCGImage scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *outputImage = [[UIImage alloc] initWithCGImage:effectCGImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- CGImageRelease(effectCGImage);
-
- return outputImage;
- }
- #if SD_UIKIT || SD_MAC
- - (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter {
- CIImage *inputImage;
- if (self.CIImage) {
- inputImage = self.CIImage;
- } else {
- CGImageRef imageRef = self.CGImage;
- if (!imageRef) {
- return nil;
- }
- inputImage = [CIImage imageWithCGImage:imageRef];
- }
- if (!inputImage) return nil;
-
- CIContext *context = [CIContext context];
- [filter setValue:inputImage forKey:kCIInputImageKey];
- CIImage *outputImage = filter.outputImage;
- if (!outputImage) return nil;
-
- CGImageRef imageRef = [context createCGImage:outputImage fromRect:outputImage.extent];
- if (!imageRef) return nil;
-
- #if SD_UIKIT
- UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
- #else
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
- #endif
- CGImageRelease(imageRef);
-
- return image;
- }
- #endif
- @end
|