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.