diff --git a/SDWebImage/Core/UIImage+Transform.h b/SDWebImage/Core/UIImage+Transform.h index 60b488ae..bbacc145 100644 --- a/SDWebImage/Core/UIImage+Transform.h +++ b/SDWebImage/Core/UIImage+Transform.h @@ -98,13 +98,23 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { #pragma mark - Image Blending /** - Return a tinted image with the given color. This actually use alpha blending of current image and the tint color. + Return a tinted image with the given color. This actually use `sourceAtop` blend mode. @param tintColor The tint color. @return The new image with the tint color. */ - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; +/** + Return a tinted image with the given color and blend mode. + @note The blend mode treat `self` as background image (destination), treat `tintColor` as input image (source). So mostly you need `source` variant blend mode (use `sourceAtop` not `destinationAtop`). + + @param tintColor The tint color. + @param blendMode The blend mode. + @return The new image with the tint color. + */ +- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor blendMode:(CGBlendMode)blendMode; + /** Return the pixel color at specify position. The point is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. @note The point's x/y will be converted into integer. diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index 65c7d638..c40e4cfb 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -536,28 +536,173 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull #pragma mark - Image Blending +static NSString * _Nullable SDGetCIFilterNameFromBlendMode(CGBlendMode blendMode) { + // CGBlendMode: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_images/dq_images.html#//apple_ref/doc/uid/TP30001066-CH212-CJBIJEFG + // CIFilter: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/uid/TP30000136-SW71 + NSString *filterName; + switch (blendMode) { + case kCGBlendModeMultiply: + filterName = @"CIMultiplyBlendMode"; + break; + case kCGBlendModeScreen: + filterName = @"CIScreenBlendMode"; + break; + case kCGBlendModeOverlay: + filterName = @"CIOverlayBlendMode"; + break; + case kCGBlendModeDarken: + filterName = @"CIDarkenBlendMode"; + break; + case kCGBlendModeLighten: + filterName = @"CILightenBlendMode"; + break; + case kCGBlendModeColorDodge: + filterName = @"CIColorDodgeBlendMode"; + break; + case kCGBlendModeColorBurn: + filterName = @"CIColorBurnBlendMode"; + break; + case kCGBlendModeSoftLight: + filterName = @"CISoftLightBlendMode"; + break; + case kCGBlendModeHardLight: + filterName = @"CIHardLightBlendMode"; + break; + case kCGBlendModeDifference: + filterName = @"CIDifferenceBlendMode"; + break; + case kCGBlendModeExclusion: + filterName = @"CIExclusionBlendMode"; + break; + case kCGBlendModeHue: + filterName = @"CIHueBlendMode"; + break; + case kCGBlendModeSaturation: + filterName = @"CISaturationBlendMode"; + break; + case kCGBlendModeColor: + // Color blend mode uses the luminance values of the background with the hue and saturation values of the source image. + filterName = @"CIColorBlendMode"; + break; + case kCGBlendModeLuminosity: + filterName = @"CILuminosityBlendMode"; + break; + + // macOS 10.5+ + case kCGBlendModeSourceAtop: + case kCGBlendModeDestinationAtop: + filterName = @"CISourceAtopCompositing"; + break; + case kCGBlendModeSourceIn: + case kCGBlendModeDestinationIn: + filterName = @"CISourceInCompositing"; + break; + case kCGBlendModeSourceOut: + case kCGBlendModeDestinationOut: + filterName = @"CISourceOutCompositing"; + break; + case kCGBlendModeNormal: // SourceOver + case kCGBlendModeDestinationOver: + filterName = @"CISourceOverCompositing"; + break; + + // need special handling + case kCGBlendModeClear: + // use clear color instead + break; + case kCGBlendModeCopy: + // use input color instead + break; + case kCGBlendModeXOR: + // unsupported + break; + case kCGBlendModePlusDarker: + // chain filters + break; + case kCGBlendModePlusLighter: + // chain filters + break; + } + return filterName; +} + - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor { + return [self sd_tintedImageWithColor:tintColor blendMode:kCGBlendModeSourceAtop]; +} + +- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor blendMode:(CGBlendMode)blendMode { BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__; if (!hasTint) { return self; } + // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing #if SD_UIKIT || SD_MAC // CIImage shortcut - if (self.CIImage) { - CIImage *ciImage = self.CIImage; + CIImage *ciImage = self.CIImage; + if (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; + NSString *filterName = SDGetCIFilterNameFromBlendMode(blendMode); + // Some blend mode is not nativelly supported + if (filterName) { + CIFilter *filter = [CIFilter filterWithName:filterName]; + [filter setValue:colorImage forKey:kCIInputImageKey]; + [filter setValue:ciImage forKey:kCIInputBackgroundImageKey]; + ciImage = filter.outputImage; + } else { + if (blendMode == kCGBlendModeClear) { + // R = 0 + CIColor *clearColor; + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)) { + clearColor = CIColor.clearColor; + } else { + clearColor = [[CIColor alloc] initWithColor:UIColor.clearColor]; + } + ciImage = [CIImage imageWithColor:clearColor]; + } else if (blendMode == kCGBlendModeCopy) { + // R = S + ciImage = colorImage; + } else if (blendMode == kCGBlendModePlusLighter) { + // R = MIN(1, S + D) + // S + D + CIFilter *filter = [CIFilter filterWithName:@"CIAdditionCompositing"]; + [filter setValue:colorImage forKey:kCIInputImageKey]; + [filter setValue:ciImage forKey:kCIInputBackgroundImageKey]; + ciImage = filter.outputImage; + // MIN + ciImage = [ciImage imageByApplyingFilter:@"CIColorClamp" withInputParameters:nil]; + } else if (blendMode == kCGBlendModePlusDarker) { + // R = MAX(0, (1 - D) + (1 - S)) + // (1 - D) + CIFilter *filter1 = [CIFilter filterWithName:@"CIColorInvert"]; + [filter1 setValue:ciImage forKey:kCIInputImageKey]; + ciImage = filter1.outputImage; + // (1 - S) + CIFilter *filter2 = [CIFilter filterWithName:@"CIColorInvert"]; + [filter2 setValue:colorImage forKey:kCIInputImageKey]; + colorImage = filter2.outputImage; + // + + CIFilter *filter = [CIFilter filterWithName:@"CIAdditionCompositing"]; + [filter setValue:colorImage forKey:kCIInputImageKey]; + [filter setValue:ciImage forKey:kCIInputBackgroundImageKey]; + ciImage = filter.outputImage; + // MAX + ciImage = [ciImage imageByApplyingFilter:@"CIColorClamp" withInputParameters:nil]; + } else { + SD_LOG("UIImage+Transform error: Unsupported blend mode: %zu", blendMode); + ciImage = nil; + } + } + + if (ciImage) { #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 @@ -565,9 +710,6 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull 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];