diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index b7d63015..86db61a2 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -21,45 +21,6 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * return [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; } -@interface UIColor (Additions) - -@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; - -@end - -@implementation UIColor (Additions) - -- (NSString *)sd_hexString { - CGFloat red, green, blue, alpha; -#if SD_UIKIT - if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#else - @try { - [self getRed:&red green:&green blue:&blue alpha:&alpha]; - } - @catch (NSException *exception) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#endif - - red = roundf(red * 255.f); - green = roundf(green * 255.f); - blue = roundf(blue * 255.f); - alpha = roundf(alpha * 255.f); - - uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); - - return [NSString stringWithFormat:@"0x%08x", hex]; -} - -@end - @interface SDWebImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index c042977d..6165c53d 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -26,6 +26,28 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { }; #endif +#pragma mark - Useful category + +@interface UIColor (Additions) + +/** + Convenience way to get hex string from color. The output should always be 32-bit hex string like `0x00000000` + */ +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + +#if SD_MAC +@interface NSBezierPath (Additions) + +/** + Convenience way to create a bezier path with the specify rouunding corners on macOS. Same as the one on `UIBezierPath`. + */ ++ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; + +@end +#endif + /** Provide some commen method for `UIImage`. Image process is based on Core Graphics and vImage. @@ -101,6 +123,14 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { */ - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; +/** + Return the color at specify pixel. The postion is from the top-left to the bottom-right. And the color is always be RGBA format. + + @param point The position of pixel + @return The color for specify pixel, or nil if any error occur + */ +- (nullable UIColor *)sd_colorAtPoint:(CGPoint)point; + #pragma mark - Image Effect /** diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 543ed2b0..430fe79b 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -35,9 +35,6 @@ static CGContextRef SDCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, static void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContextWithOptions(size, opaque, scale); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(context, 0, -size.height); - CGContextScaleCTM(context, scale, -scale); #else CGContextRef context = SDCGContextCreateARGBBitmapContext(size, opaque, scale); if (!context) { @@ -148,13 +145,41 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod return rect; } -#if SD_MAC -@interface NSBezierPath (Additions) +@implementation UIColor (Additions) -+ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; +- (NSString *)sd_hexString { + CGFloat red, green, blue, alpha; +#if SD_UIKIT + if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#else + @try { + [self getRed:&red green:&green blue:&blue alpha:&alpha]; + } + @catch (NSException *exception) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#endif + + red = roundf(red * 255.f); + green = roundf(green * 255.f); + blue = roundf(blue * 255.f); + alpha = roundf(alpha * 255.f); + + uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); + + return [NSString stringWithFormat:@"0x%08x", hex]; +} @end +#if SD_MAC + @implementation NSBezierPath (Additions) + (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { @@ -383,8 +408,146 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod return image; } +- (UIColor *)sd_colorAtPoint:(CGPoint)point { + if (!self) { + return nil; + } + CGImageRef imageRef = self.CGImage; + 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) { + return nil; + } + + // Get pixels + CGDataProviderRef provider = CGImageGetDataProvider(imageRef); + if (!provider) { + return nil; + } + CFDataRef data = CGDataProviderCopyData(provider); + if (!data) { + return nil; + } + + // Get pixel at point + size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); // Actually should be ARGB8888, equal to width * 4(alpha) or 3(non-alpha) + size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); + + CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4); + if (CFDataGetLength(data) < range.location + range.length) { + return nil; + } + UInt8 pixel[4] = {0}; + CFDataGetBytes(data, range, pixel); + + // Convert to color + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef); + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + CGFloat r = 0, g = 0, b = 0, a = 0; + + BOOL byteOrderNormal = NO; + switch (bitmapInfo & kCGBitmapByteOrderMask) { + case kCGBitmapByteOrderDefault: { + byteOrderNormal = YES; + } break; + case kCGBitmapByteOrder32Little: { + } break; + case kCGBitmapByteOrder32Big: { + byteOrderNormal = YES; + } break; + default: break; + } + switch (alphaInfo) { + case kCGImageAlphaPremultipliedFirst: { + 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: { + 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; + // iOS does not supports non-premultiplied alpha, so no these cases :) + default: + break; + } + + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} + #pragma mark - Image Effect +// We use vImage to do box convolve for performance. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur` - (UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius { if (self.size.width < 1 || self.size.height < 1) { return nil;