Add blend mode to UIImage+Transform tint color API

Also works for CIFilter variant
This commit is contained in:
DreamPiggy 2024-09-03 17:09:48 +08:00
parent c8f74d2de0
commit 099371823e
2 changed files with 162 additions and 10 deletions

View File

@ -98,13 +98,23 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) {
#pragma mark - Image Blending #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. @param tintColor The tint color.
@return The new image with the tint color. @return The new image with the tint color.
*/ */
- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; - (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. 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. @note The point's x/y will be converted into integer.

View File

@ -536,28 +536,173 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
#pragma mark - Image Blending #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 { - (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__; BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
if (!hasTint) { if (!hasTint) {
return self; return self;
} }
// blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
#if SD_UIKIT || SD_MAC #if SD_UIKIT || SD_MAC
// CIImage shortcut // CIImage shortcut
if (self.CIImage) { CIImage *ciImage = self.CIImage;
CIImage *ciImage = self.CIImage; if (ciImage) {
CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]]; CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]];
colorImage = [colorImage imageByCroppingToRect:ciImage.extent]; colorImage = [colorImage imageByCroppingToRect:ciImage.extent];
CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"]; NSString *filterName = SDGetCIFilterNameFromBlendMode(blendMode);
[filter setValue:colorImage forKey:kCIInputImageKey]; // Some blend mode is not nativelly supported
[filter setValue:ciImage forKey:kCIInputBackgroundImageKey]; if (filterName) {
ciImage = filter.outputImage; 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 #if SD_UIKIT
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else #else
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif #endif
return image; return image;
}
} }
#endif #endif
@ -565,9 +710,6 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
CGRect rect = { CGPointZero, size }; CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale; CGFloat scale = self.scale;
// blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
CGBlendMode blendMode = kCGBlendModeSourceAtop;
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = scale; format.scale = scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];