How to apply HSB color filters to UIImage

2019-05-25 05:38发布

问题:

I've been struggling for a few days for a project on UIImage colorization. The idea is that the app will embark a set of images that I will have to colorize with values retrieved from a webservice. Some sort of themes if you wish.

The designer I work with gave me a background image on all of his Photoshop values.

The first problem is that Photoshop uses HSL and iOS uses HSB. So the first challenge was to translate the values from Photoshop.

Photoshop HSL: -28 (range -180 => +180), 100 (range -100 => +100), 25 (range -100 => +100).

Luckily I found some code online, here it is.

//adapted from https://gist.github.com/peteroupc/4085710
- (void)convertLightnessToBrightness:(CGFloat)lightness withSaturation:(CGFloat)saturation completion:(void (^)(CGFloat, CGFloat))completion
{
    if (!completion)
        return; //What's the point of calling this method without a completion block!

    CGFloat brightness = 0.0f;
    CGFloat saturationOut = 0.0f;

    if (lightness > 0.0f)
    {
        CGFloat lumScale = (1.0f - MAX((lightness - 0.5f), 0.0f) * 2.0f);
        lumScale = ((lumScale == 0) ? 0 : (1.0f / lumScale));

        CGFloat lumStart = MAX(0.0f, (lumScale - 0.5f));

        CGFloat lumDiv = (lumScale - lumStart);
        lumDiv = (lumStart + (saturation * lumDiv));

        saturationOut = ((lumDiv == 0) ? 0.0f : (saturation / lumDiv));
        brightness = (lightness + (1.0f - lightness) * saturation);
    }

    NSLog(@"saturation: %0.2f - brightness: %0.2f", saturationOut, brightness);

    completion(saturationOut, brightness);
}

Using an online converter I verified that this method returns the good values.
I needed to change the ranges (H: 0->360, S: 0->100, L: 0->100)

So HSL 152, 100, 62 gives HSB 152, 76, 100. And the method returns 75 for saturation and 100 for brightness so we are good.

Next I needed to apply those values to the image, so here is the code to change...

The HUE:

#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0f * M_PI)

- (void)colorize:(UIImage *)input hue:(CGFloat)hueDegrees completion:(void(^)(UIImage *outputHue))completion
{
    if (!completion)
        return; //What's the point of calling this method without a completion block!

    CGFloat hue = DEGREES_TO_RADIANS(hueDegrees);

    NSLog(@"degress: %0.2f | radian: %0.2f", hueDegrees, hue);

    CIImage *inputImage = [CIImage imageWithCGImage:input.CGImage];

    //---
    CIFilter *hueFilter = [CIFilter filterWithName:@"CIHueAdjust" keysAndValues:kCIInputImageKey, inputImage, nil];
    [hueFilter setDefaults];
    [hueFilter setValue:[NSNumber numberWithFloat:hue] forKey:kCIInputAngleKey];
    //---

    CIImage *outputImage = [hueFilter outputImage];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef cgimg = [context createCGImage:outputImage fromRect:[outputImage extent]];
    UIImage *outputUIImage = [UIImage imageWithCGImage:cgimg];
    CGImageRelease(cgimg);

    completion(outputUIImage);
}

The SATURATION:

- (void)colorize:(UIImage *)input saturation:(CGFloat)saturation completion:(void(^)(UIImage *outputSaturation))completion
{
    if (!completion)
        return; //What's the point of calling this method without a completion block!

    NSLog(@"saturation: %0.2f", saturation);

    CIImage *inputImage = [CIImage imageWithCGImage:input.CGImage];

    //---
    CIFilter *saturationFilter = [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputImageKey, inputImage, nil];
    [saturationFilter setDefaults];
    [saturationFilter setValue:[NSNumber numberWithFloat:saturation] forKey:@"inputSaturation"];
    //---

    CIImage *outputImage = [saturationFilter outputImage];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef cgimg = [context createCGImage:outputImage fromRect:[outputImage extent]];
    UIImage *outputUIImage = [UIImage imageWithCGImage:cgimg];
    CGImageRelease(cgimg);

    completion(outputUIImage);
}

The BRIGHTNESS:

- (void)colorize:(UIImage *)input brightness:(CGFloat)brightness completion:(void(^)(UIImage *outputBrightness))completion
{
    if (!completion)
        return; //What's the point of calling this method without a completion block!

    NSLog(@"brightness: %0.2f", brightness);

    CIImage *inputImage = [CIImage imageWithCGImage:input.CGImage];

    //---
    CIFilter *brightnessFilter = [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputImageKey, inputImage, nil];
    [brightnessFilter setDefaults];
    [brightnessFilter setValue:[NSNumber numberWithFloat:brightness] forKey:@"inputBrightness"];
    //---

    CIImage *outputImage = [brightnessFilter outputImage];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef cgimg = [context createCGImage:outputImage fromRect:[outputImage extent]];
    UIImage *outputUIImage = [UIImage imageWithCGImage:cgimg];
    CGImageRelease(cgimg);

    completion(outputUIImage);
}

And everything put together:

CGFloat hue = -28.0f; //152 in 360° range (180 - 28 = 152)
CGFloat saturation = 1.0f; //((100 + 100.0f) / 200.0f)
CGFloat lightness = 0.625f; //((25 + 100.0f) / 200.0f)

[self convertLightnessToBrightness:ligthness withSaturation:saturation completion:^(CGFloat saturationOut, CGFloat brightness) {

    //saturarationOut = 0.75f and brigthness = 1.0f
    [self colorize:input hue:hue completion:^(UIImage *outputHue) {

        [self colorize:outputHue saturation:saturationOut completion:^(UIImage *outputSaturation) {

            [self colorize:outputSaturation brightness:brightness completion:completion];

        }];

    }];

}];

The last completion block simply applies the output image to the image view. Now here are the results:

Base image

Colorize (hue only)

Colorize (hue and saturation)

Colorize (hue, saturation and brightness)

Expected result

As you can see, the final image is completely white (brigthness is 100%).

I'm completely lost here, I've tried many combination (applying H, S and B in every order), I've tried others libs such as iOS-Image-Filters, without any success. I've also read a lot of question here on Stack Overflow.

Links:

  • Core Image Filter Reference
  • CIHueAdjust core image filter setup
  • How to programmatically change the hue of UIImage?
  • iOS: Values for CIFilter (Hue) from Photoshop

As anyone succeeded to apply HSL/HSB value to UIImages?

回答1:

Well finally, we decided to change techniques. I'm now using a semi-transparent image to wich I apply a blend mode with the desired color.

Following this post I made a category on UIImage.

- (UIImage *)tintedBackgroundImageWithColor:(UIColor *)tintColor
{
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
    [tintColor setFill];
    CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
    UIRectFill(bounds);

    [self drawInRect:bounds blendMode:kCGBlendModeSourceAtop alpha:1.0f];

    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return tintedImage;
}