Home > Net >  Is there a way, to prevent from precision lossing from converting from RGB, to HSV then back to RGB?
Is there a way, to prevent from precision lossing from converting from RGB, to HSV then back to RGB?

Time:02-11

I try to convert color from RGB, to HSV, then back to RHB by using UIColor.

However, I notice there is precision losing by doing so.

This only happen in some colors.

For instance

import UIKit

extension UIColor {
    convenience init(red: Int, green: Int, blue: Int, a: CGFloat = 1.0) {
        self.init(
            red: CGFloat(red) / 255.0,
            green: CGFloat(green) / 255.0,
            blue: CGFloat(blue) / 255.0,
            alpha: a
        )
    }
    
    func red() -> Int {
        var r: CGFloat = 0.0
        getRed(&r, green: nil, blue: nil, alpha: nil)
        
        let red = Int(r*0xff) & 0xff
        
        return red
    }
    
    func green() -> Int {
        var g: CGFloat = 0.0
        getRed(nil, green: &g, blue: nil, alpha: nil)
        
        let green = Int(g*0xff) & 0xff
        
        return green
    }
    
    func blue() -> Int {
        var b: CGFloat = 0.0
        getRed(nil, green: nil, blue: &b, alpha: nil)
        
        let blue = Int(b*0xff) & 0xff
        
        return blue
    }
    
    func alpha() -> Int {
        var a: CGFloat = 0.0
        getRed(nil, green: nil, blue: nil, alpha: &a)
        
        let alpha = Int(a*0xff) & 0xff
        
        return alpha
    }
}


let color = UIColor(red: 18, green: 52, blue: 86)
print("red \(color.red())")
print("green \(color.green())")
print("blue \(color.blue())")
print("alpha \(color.alpha())\n")

var h: CGFloat = 0.0
var s: CGFloat = 0.0
var v: CGFloat = 0.0
var a: CGFloat = 0.0
color.getHue(&h, saturation: &s, brightness: &v, alpha: &a)
let newColor = UIColor(hue: h, saturation: s, brightness: v, alpha: a)

print("new red \(newColor.red())")
print("new green \(newColor.green())")
print("new blue \(newColor.blue())")
print("new alpha \(newColor.alpha())")

I am getting output

red 18
green 52
blue 86
alpha 255

new red 17
new green 52
new blue 86
new alpha 255

As you can see, instead of getting original color when converting back from HSV to RGB, we are losing precision in red component color.

May I know why is it so? How can I solve such precision losing issue? Thank you.

CodePudding user response:

Rounding errors can not be avoided when doing roundtrip color space conversions, since not all intermediate results can be represented exactly as a binary floating point number.

The problem is that with your Int -> CGFloat and CGFloat -> Int conversions, even a tiny error in the floating point value can result in an integer result which is different from the original.

In your example, the red component changes from 0.07058823529411765 to 0.07058823529411763, which is smaller by 0.00000000000000002. That is not much, but since you truncate in the conversion from floating point values to integers, the final result is one less than the original integer.

This can be demonstrated with a small stand-alone program:

let red = 18
var r = CGFloat(red) / 255.0
r -= 2.0E-17 // Simulate rounding error in the color calculations
let newRed = Int(r * 255)
print(newRed) // 17

In order to get the original integer values even after small rounding errors in the floating point calculations, it is therefore better to round in the conversion, in your case

red = Int((r * 255).rounded())

and similarly for the other color components.

  • Related