Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tint only one part of the UIImage with alpha channel - PNG (replacing color)?

I have this transparent image:

enter image description here

My goal is to change the "ME!" parts color. Either tint only the last 3rd of the image, or replace the blue color with the new color.

Expected result after color change:

enter image description here

Unfortunately neither worked for me. To change the specific color I tried this: LINK, but as the documentation says, this works only without alpha channel!

Then I tried this one: LINK, but this actually does nothing, no tint or anything.

Is there any other way to tint only one part of the color or just replace a specific color?

I know I could slice the image in two parts, but I hope there is another way.

like image 690
Dejan Skledar Avatar asked Mar 14 '23 15:03

Dejan Skledar


2 Answers

It turns out to be surprisingly complicated—you’d think you could do it in one pass with CoreGraphics blend modes, but from pretty extensive experimentation I haven’t found such a way that doesn’t mangle the alpha channel or the coloration. The solution I landed on is this:

  1. Start with a grayscale/alpha version of your image rather than a colored one: black in the areas you don’t want tinted, white in the areas you do
  2. Create an image context with your image’s dimensions
  3. Fill that context with black
  4. Draw the image into the context
  5. Get a new image (let’s call it “the-image-over-black”) from that context
  6. Clear the context (so you can use it again)
  7. Fill the context with the color you want the tinted part of your image to be
  8. Draw the-image-over-black into the context with the “multiply” blend mode
  9. Draw the original image into the context with the “destination in” blend mode
  10. Get your final image from the context

The reason this works is because of the combination of blend modes. What you’re doing is creating a fully-opaque black-and-white image (step 5), then multiplying it by your final color (step 8), which gives you a fully opaque black-and-your-final-color image. Then, you take the original image, which still has its alpha channel, and draw it with the “destination in” blend mode which takes the color from the black-and-your-color image and the alpha channel from the original image. The result is a tinted image with the original brightness values and alpha channel.

Objective-C

- (UIImage *)createTintedImageFromImage:(UIImage *)originalImage color:(UIColor *)desiredColor {
    CGSize imageSize = originalImage.size;
    CGFloat imageScale = originalImage.scale;
    CGRect contextBounds = CGRectMake(0, 0, imageSize.width, imageSize.height);

    UIGraphicsBeginImageContextWithOptions(imageSize, NO /* not opaque */, imageScale); // 2
    [[UIColor blackColor] setFill]; // 3a
    UIRectFill(contextBounds); // 3b
    [originalImage drawAtPoint:CGPointZero]; // 4
    UIImage *imageOverBlack = UIGraphicsGetImageFromCurrentImageContext(); // 5
    CGContextClear(UIGraphicsGetCurrentImageContext()); // 6
    [desiredColor setFill]; // 7a
    UIRectFill(contextBounds); // 7b
    [imageOverBlack drawAtPoint:CGPointZero blendMode:kCGBlendModeMultiply alpha:1]; // 8
    [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeDestinationIn alpha:1]; // 9
    finalImage = UIGraphicsGetImageFromCurrentContext(); // 10
    UIGraphicsEndImageContext();

    return finalImage;
}

Swift 4

func createTintedImageFromImage(originalImage: UIImage, desiredColor: UIColor) -> UIImage {
    let imageSize = originalImage.size
    let imageScale = originalImage.scale
    let contextBounds = CGRect(origin: .zero, size: imageSize)

    UIGraphicsBeginImageContextWithOptions(imageSize, false /* not opaque */, imageScale) // 2

    defer { UIGraphicsEndImageContext() }

    UIColor.black.setFill() // 3a
    UIRectFill(contextBounds) // 3b
    originalImage.draw(at: .zero) // 4
    guard let imageOverBlack = UIGraphicsGetImageFromCurrentImageContext() else { return originalImage } // 5
    desiredColor.setFill() // 7a
    UIRectFill(contextBounds) // 7b

    imageOverBlack.draw(at: .zero, blendMode: .multiply, alpha: 1) // 8
    originalImage.draw(at: .zero, blendMode: .destinationIn, alpha: 1) // 9

    guard let finalImage = UIGraphicsGetImageFromCurrentImageContext() else { return originalImage } // 10

    return finalImage
}
like image 156
Noah Witherspoon Avatar answered Apr 09 '23 11:04

Noah Witherspoon


There are lots of ways to do this.

Core image filters come to mind as a good way to go. Since the part you want to change is a unique color, you could use the Core image CIHueAdjust filter to shift the hue from blue to red. Only the word you want to change has any color to it, so that's all it would change.

If you had an image with various colors in it and still wanted to replace ALL The colors in the image you could use CIColorCube to map the blue pixels to red without affecting other colors. There was a thread on this board last week with sample code using CIColorCube to force one color to another. Search on CIColorCube and look for the most recent post and you should be able to find it.

If you wanted to limit the change to a specific area of the screen you could probably come up with a sequence of core image filters that would limit your changes to just the target area.

You could also slice out the part you want to change, color edit it using a any of variety of techniques, and then composite it back together.

like image 42
Duncan C Avatar answered Apr 09 '23 10:04

Duncan C