Replace black and white color of image with color cube

Hi guys,


I want to replace colors of an image using color cube. The methods below work pretty well for any color besides black and white, which means, I can't replace black and white color. Can any one help me why this the case?

func render() {
        let centerHueAngle: Float = defaultHue/360.0
        let destCenterHueAngle: Float = replaceColorSlider.value
        let minHueAngle: Float = (defaultHue - toleranceSlider.value / 2.0) / 360
        let maxHueAngle: Float = (defaultHue + toleranceSlider.value / 2.0) / 360
        let hueAdjustment = centerHueAngle - destCenterHueAngle
      
        let size = 64
        var cubeData = [Float](repeating: 0, count: size * size * size * 4)
        var rgb: [Float] = [0, 0, 0]
        var hsv: (h : Float, s : Float, v : Float)
        var newRGB: (r : Float, g : Float, b : Float)
        var offset = 0
        for z in 0 ..< size {
            rgb[2] = Float(z) / Float(size) // blue value
            for y in 0 ..< size {
                rgb[1] = Float(y) / Float(size) // green value
                for x in 0 ..< size {
                    rgb[0] = Float(x) / Float(size) // red value
                    hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2])
                    if hsv.h < minHueAngle || hsv.h > maxHueAngle {
                        newRGB.r = rgb[0]
                        newRGB.g = rgb[1]
                        newRGB.b = rgb[2]
                    } else {
                        hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
                        newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
                    }
                    cubeData[offset] = newRGB.r
                    cubeData[offset+1] = newRGB.g
                    cubeData[offset+2] = newRGB.b
                    cubeData[offset+3] = 1.0
                    offset += 4
                }
            }
        }


        let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) }
        let data = b as NSData
        let colorCube = CIFilter(name: "CIColorCube")!
        colorCube.setValue(size, forKey: "inputCubeDimension")
        colorCube.setValue(data, forKey: "inputCubeData")
        colorCube.setValue(ciImage, forKey: kCIInputImageKey)
          if let outImage = colorCube.outputImage {
            let context = CIContext(options: nil)
            let outputImageRef = context.createCGImage(outImage, from: outImage.extent)
            let image = UIImage(cgImage: outputImageRef!, scale: 1.0, orientation: .up)
            
            cropImageView.image = image
        }


}

func RGBtoHSV(_ r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
        var h : CGFloat = 0
        var s : CGFloat = 0
        var v : CGFloat = 0
        let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
        col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
        return (Float(h), Float(s), Float(v))
}


func HSVtoRGB(_ h : Float, s : Float, v : Float) -> (r : Float, g : Float, b : Float) {
        var r : Float = 0
        var g : Float = 0
        var b : Float = 0
        let C = s * v
        let HS = h * 6.0
        let X = C * (1.0 - fabsf(fmodf(HS, 2.0) - 1.0))
        if (HS >= 0 && HS < 1) {
            r = C
            g = X
            b = 0
        } else if (HS >= 1 && HS < 2) {
            r = X
            g = C
            b = 0
        } else if (HS >= 2 && HS < 3) {
            r = 0
            g = C
            b = X
        } else if (HS >= 3 && HS < 4) {
            r = 0
            g = X
            b = C
        } else if (HS >= 4 && HS < 5) {
            r = X
            g = 0
            b = C
        } else if (HS >= 5 && HS < 6) {
            r = C
            g = 0
            b = X
        }
        let m = v - C
        r += m
        g += m
        b += m
        return (r, g, b)
    }

Thanks a lot in advance!
I have been struggling a lot with this issue.

Best regards,

Nazar

Replies

Sorry, the question is not clear for me.


I want to replace colors of an image using color cube

What do you mean by color cube ?


What do you convert to what ?

hsv -> something else ?

rgb-> something else ?


What does cubeData represent ?

Hi Claude,


thanks for the fast reply. Updated my code above.

I can tell you my use case.


I am loading an image and display it on an image view. Now, I want to replace certain colors with others. This works pretty good except for colors such as black and white or dark brown. Means, I can't change the original black color of an image with another one. My code above doesn't have any impact when the source color is black or white.


Can you follow me?

Thanks, but still not clear.


Where is the input image in code ?

Where is the output image ?

Where is the transformation performed ? render() ?

What is exactly colorData purpose ? What is its content ?

What color do you expect for input colors such as white or black?


I guess your `colorData` is intended to modify the Hue value of HSV color space, but it does not affect the represented color when Saturation is 0. (Such as white, black or grays ... so called achromatic.)


You may need to modify your way of creating `colorData` to represent the exact mapping you expect.


(By the way, `Float(size)` in line 15, 17 and 19 should be `Float(size-1)`, I think.)

Hi OOPer,


I believe we are very close to the goal!!. You understood my problem with not affecting color when saturation is 0.

But to be honest, I don't know what to do now. Do you have any specific example, I can look at?


Thanks.

I don't know what you expect, so I cannot show any examples.


Anyway, you may need to modify your code where creating `colorData`, especially line 21. to 28., to generate colors you expect when saturation is 0.

Hi OOper,


I am sorry but I am still struggling with this issue. What I didn't understand is, what you meant with generate colors when saturation is 0. Colors will be generated in any case or? Because for generating the colors, I have three for-loops.
What I have done so far:

for z in 0 ..< size {
            rgb[2] = Float(z-1) / Float(size) // blue value
            for y in 0 ..< size {
                rgb[1] = Float(y-1) / Float(size) // green value
                for x in 0 ..< size {
                    rgb[0] = Float(x-1) / Float(size) // red value
                    hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2])
                    
                    let selectedColorRedValue:Float = Float(COLOR_TO_BE_RELACED.rgb()!.red)
                    let selectedColorGreenValue:Float = Float(COLOR_TO_BE_RELACED.rgb()!.green)
                    let selectedColorBlueValue:Float = Float(COLOR_TO_BE_RELACED.rgb()!.blue)
                    
                    let selectedColorHSV = RGBtoHSV(selectedColorRedValue, g: selectedColorGreenValue, b: selectedColorBlueValue)
                    
                    if (selectedColorHSV.s == 0 && selectedColorHSV.h < 0.02) {
                        
                        hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
                        newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
                        
                    }
                    
                    if hsv.h < minHueAngle || hsv.h > maxHueAngle {
                        newRGB.r = rgb[0]
                        newRGB.g = rgb[1]
                        newRGB.b = rgb[2]
                    }
                        
                    else {
                        hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
                        newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
                    }
                       
                    cubeData[offset] = newRGB.r
                    cubeData[offset+1] = newRGB.g
                    cubeData[offset+2] = newRGB.b
                    cubeData[offset+3] = 1.0
                    offset += 4
                    
                }
            }
        }

At least, I can check, if the color which should be replaced is achromatic as you mentioned. But if it is achromatic, I don't know how to continue 😟 I debugged it couple of times but I couldn't understand what to do exactly.

It's hard to find what sort of terms are appropriate for you.


With Hue shifting like in your code, achromatic colors would never change.

Achromatic colors exist in the center of the color space, and Hue shifting rotates the angle in the color space which does not move the center.


You may need to generate colors for achromatic colors if your current output is not preferred.

                    if abs(hsv.s) < 1.0/Float(size*2) {
                        //Do something special for achromatic colors...
                        //...
                    } else if hsv.h < minHueAngle || hsv.h > maxHueAngle {
                        newRGB.r = rgb[0]
                        newRGB.g = rgb[1]
                        newRGB.b = rgb[2]
                    } else {
                        hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
                        newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
                    }

Hi OOPer,


thanks again for your help. You supported a lot. If I become a successful entrepreneur one day, I will hire you as a developer 🙂 I was able to generate colors within your if clause. Maybe one last thing that remains:


First, I tried the solution on just a complete black screen image. Changing colors worked wonderful. Then I tried to use an image with a black/gray car and here I noticed, that not everything was replaced as it should be. I guess the problem is the range of the tolerance to the black color. How can I use also a tolerance value here? Let's say, I want to be able to include also dark brown, dark gray as "black" colors and they also should be replaced.


Thanks in advance!


Best regards,

Kaan

Hi everyone, can anyone help me with my last question? I am still struggling with it 😟 Thanks in advance!

Black is rgb = (0, 0, 0)


So, dark is r < epsilon and g < epsilon and b < epslion.


Up to you to set the epsilon and transform accordingly.

As I said before, in a color conversion based on Hue-shifting (or Hue-rotating, some docs say), achromatic colors are not changed.

That is the right behavior of the cube you have made.


Colors generated by the code `if abs(hsv.s) < 1.0/Float(size*2) {` are sort of anomallies in the cube and would show very odd behavior when you convert images with colors very near to the anomallies.


You may need to find another cube generating algorithm than a Hue-shifting + anomallies.