Skip to content

Commit 9710272

Browse files
authored
Use Float32 for the matrix coefficients for low precision colors (#482)
This also changes the definition of the sRGB transform matrix slightly.
1 parent 9f27da0 commit 9710272

File tree

6 files changed

+90
-60
lines changed

6 files changed

+90
-60
lines changed

docs/src/colordifferences.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ The [`colordiff`](@ref) function gives an approximate value for the difference b
44

55
```jldoctest example; setup = :(using Colors)
66
julia> colordiff(colorant"red", colorant"darkred")
7-
23.754149450423807
7+
23.75414245117878
88
99
julia> colordiff(colorant"red", colorant"blue")
10-
52.88136496280975
10+
52.88135569435472
1111
1212
julia> colordiff(HSV(0, 0.75, 0.5), HSL(0, 0.75, 0.5))
13-
19.48591066257134
13+
19.485908737785326
1414
```
1515

1616
```julia

src/algorithms.jl

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,8 @@ function MSC(h)
206206
# un & vn are calculated based on reference white (D65)
207207
un, vn = xyz_to_uv(WP_DEFAULT)
208208

209-
#sRGB matrix
210-
M = [0.4124564 0.3575761 0.1804375;
211-
0.2126729 0.7151522 0.0721750;
212-
0.0193339 0.1191920 0.9503041]
213-
214-
m_tx, m_ty, m_tz = @view M[:, t]
215-
m_px, m_py, m_pz = @view M[:, p]
209+
m_tx, m_ty, m_tz = @view M_RGB2XYZ[:, t]
210+
m_px, m_py, m_pz = @view M_RGB2XYZ[:, p]
216211

217212
f1 = 4alpha*m_px+9beta*m_py
218213
a1 = 4alpha*m_tx+9beta*m_ty

src/conversions.jl

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,6 @@ function ColorTypes._convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, g) wher
6969
cnvt(Cdest, convert(RGB{eltype(Cdest)}, g))
7070
end
7171

72-
macro mul3x3(T, M, c1, c2, c3)
73-
esc(quote
74-
@inbounds ret = $T($M[1,1]*$c1 + $M[1,2]*$c2 + $M[1,3]*$c3,
75-
$M[2,1]*$c1 + $M[2,2]*$c2 + $M[2,3]*$c3,
76-
$M[3,1]*$c1 + $M[3,2]*$c2 + $M[3,3]*$c3)
77-
ret
78-
end)
79-
end
80-
8172
# Everything to RGB
8273
# -----------------
8374

@@ -88,7 +79,7 @@ correct_gamut(c::CV) where {CV<:TransparentRGB} =
8879
CV(clamp01(red(c)), clamp01(green(c)), clamp01(blue(c)), clamp01(alpha(c))) # for `hex`
8980

9081
@inline function srgb_compand(v)
91-
F = typeof(0.5 * v)
82+
F = typeof(0.5f0v) === Float32 ? Float32 : promote_type(Float64, typeof(v))
9283
vf = F(v)
9384
# `pow5_12` is an optimized function to get `v^(1/2.4)`
9485
vf > F(0.0031308) ? muladd(F(1.055), F(pow5_12(vf)), F(-0.055)) : F(12.92) * vf
@@ -168,11 +159,12 @@ function cnvt(::Type{CV}, c::HSI) where {T, CV<:AbstractRGB{T}}
168159
T <: FixedPoint && typemax(T) >= 1 ? CV(r % T, g % T, b % T) : CV(r, g, b)
169160
end
170161

162+
# the following matrix is based on the sRGB color primaries in `xy` and D65 whitepoint in `XYZ`
163+
const M_XYZ2RGB = Mat3x3([ 3.2404541621141054 -1.5371385127977166 -0.4985314095560162
164+
-0.9692660305051868 1.8760108454466942 0.04155601753034984
165+
0.05564343095911469 -0.20402591351675387 1.0572251882231791 ])
171166
function xyz_to_linear_rgb(c::XYZ)
172-
r = 3.2404542*c.x - 1.5371385*c.y - 0.4985314*c.z
173-
g = -0.9692660*c.x + 1.8760108*c.y + 0.0415560*c.z
174-
b = 0.0556434*c.x - 0.2040259*c.y + 1.0572252*c.z
175-
RGB(r, g, b)
167+
@mul3x3 RGB M_XYZ2RGB c.x c.y c.z
176168
end
177169
function cnvt(::Type{CV}, c::XYZ) where CV<:AbstractRGB
178170
rgb = xyz_to_linear_rgb(c)
@@ -181,13 +173,16 @@ function cnvt(::Type{CV}, c::XYZ) where CV<:AbstractRGB
181173
clamp01(srgb_compand(rgb.b)))
182174
end
183175

176+
const M_YIQ2RGB = Mat3x3([1.0 0.9563 0.621
177+
1.0 -0.2721 -0.6474
178+
1.0 -1.107 1.7046 ])
184179
function cnvt(::Type{CV}, c::YIQ) where CV<:AbstractRGB
185180
cc = correct_gamut(c)
186-
CV(clamp01(cc.y+0.9563*cc.i+0.6210*cc.q),
187-
clamp01(cc.y-0.2721*cc.i-0.6474*cc.q),
188-
clamp01(cc.y-1.1070*cc.i+1.7046*cc.q))
181+
rgb = @mul3x3 RGB M_YIQ2RGB cc.y cc.i cc.q
182+
CV(clamp01(rgb.r), clamp01(rgb.g), clamp01(rgb.b))
189183
end
190184

185+
# FIXME
191186
function cnvt(::Type{CV}, c::YCbCr) where CV<:AbstractRGB
192187
cc = correct_gamut(c)
193188
ny = cc.y - 16
@@ -285,41 +280,43 @@ cnvt(::Type{HSI{T}}, c::Color) where {T} = cnvt(HSI{T}, convert(RGB{T}, c))
285280
# -----------------
286281

287282
@inline function invert_srgb_compand(v)
288-
F = typeof(0.5 * v)
283+
F = typeof(0.5f0v) === Float32 ? Float32 : promote_type(Float64, typeof(v))
289284
vf = F(v)
290285
# `pow12_5` is an optimized function to get `x^2.4`
291286
vf > F(0.04045) ? pow12_5(muladd(F(1000/1055), vf, F(55/1055))) : F(100/1292) * vf
292287
end
293288

294289
# lookup table for `N0f8` (the extra two elements are for `Float32` splines)
295-
const invert_srgb_compand_n0f8 = [invert_srgb_compand(v/255.0) for v = 0:257]
290+
const invert_srgb_compand_n0f8 = Float32[invert_srgb_compand(v/255.0) for v = 0:257]
296291

297292
function invert_srgb_compand(v::N0f8)
298-
@inbounds invert_srgb_compand_n0f8[reinterpret(UInt8, v) + 1]
293+
@inbounds invert_srgb_compand_n0f8[reinterpret(v) + 1]
299294
end
300295

301296
function invert_srgb_compand(v::Float32)
302297
i = unsafe_trunc(Int32, v * 255)
303-
(i < 13 || i > 255) && return invert_srgb_compand(Float64(v))
298+
(i < 13 || i > 255) && return Float32(invert_srgb_compand(Float64(v)))
304299
@inbounds y = view(invert_srgb_compand_n0f8, i:i+3)
305-
dv = v * 255.0 - i
306-
dv == 0.0 && @inbounds return y[2]
300+
dv = v * 255.0f0 - i
301+
dv == 0.0f0 && @inbounds return y[2]
307302
if v < 0.38857287f0
308-
return @fastmath(y[2]+0.5*dv*((-2/3*y[1]- y[2])+(2y[3]-1/3*y[4])+
309-
dv*(( y[1]-2y[2])+ y[3]-
310-
dv*(( 1/3*y[1]- y[2])+( y[3]-1/3*y[4]) ))))
303+
return @fastmath(y[2]+0.5f0*dv*((-2/3f0*y[1]- y[2])+(2y[3]-1/3f0*y[4])+
304+
dv*(( y[1]-2y[2])+ y[3]-
305+
dv*(( 1/3f0*y[1]- y[2])+( y[3]-1/3f0*y[4]) ))))
311306
else
312-
return @fastmath(y[2]+0.5*dv*((4y[3]-3y[2])-y[4]+dv*((y[4]-y[3])+(y[2]-y[3]))))
307+
return @fastmath(y[2]+0.5f0*dv*((4y[3]-3y[2])-y[4]+dv*((y[4]-y[3])+(y[2]-y[3]))))
313308
end
314309
end
315310

311+
# the following matrix is based on the sRGB color primaries in `xy` and D65 whitepoint in `XYZ`
312+
const M_RGB2XYZ = Mat3x3([0.4124564390896921 0.357576077643909 0.18043748326639894
313+
0.21267285140562248 0.715152155287818 0.07217499330655958
314+
0.019333895582329317 0.119192025881303 0.9503040785363677 ])
316315
function cnvt(::Type{XYZ{T}}, c::AbstractRGB) where T
317316
r = invert_srgb_compand(red(c))
318317
g = invert_srgb_compand(green(c))
319318
b = invert_srgb_compand(blue(c))
320-
XYZ{T}(0.4124564*r + 0.3575761*g + 0.1804375*b,
321-
0.2126729*r + 0.7151522*g + 0.0721750*b,
322-
0.0193339*r + 0.1191920*g + 0.9503041*b)
319+
return @mul3x3 XYZ{T} M_RGB2XYZ r g b
323320
end
324321

325322

@@ -702,18 +699,18 @@ cnvt(::Type{DIN99o{T}}, c::Color) where {T} = cnvt(DIN99o{T}, convert(Lab{T}, c)
702699
# -----------------
703700

704701
# Chromatic adaptation from CIECAM97s
705-
const CAT97s = [ 0.8562 0.3372 -0.1934
706-
-0.8360 1.8327 0.0033
707-
0.0357 -0.0469 1.0112 ]
702+
const CAT97s = Mat3x3([ 0.8562 0.3372 -0.1934
703+
-0.8360 1.8327 0.0033
704+
0.0357 -0.0469 1.0112 ])
708705

709-
const CAT97s_INV = inv(CAT97s)
706+
const CAT97s_INV = Mat3x3(inv(Float64.(CAT97s)))
710707

711708
# Chromatic adaptation from CIECAM02
712-
const CAT02 = [ 0.7328 0.4296 -0.1624
713-
-0.7036 1.6975 0.0061
714-
0.0030 0.0136 0.9834 ]
709+
const CAT02 = Mat3x3([ 0.7328 0.4296 -0.1624
710+
-0.7036 1.6975 0.0061
711+
0.0030 0.0136 0.9834 ])
715712

716-
const CAT02_INV = inv(CAT02)
713+
const CAT02_INV = Mat3x3(inv(Float64.(CAT02)))
717714

718715

719716
function cnvt(::Type{LMS{T}}, c::XYZ) where T
@@ -727,26 +724,27 @@ cnvt(::Type{LMS{T}}, c::Color) where {T} = cnvt(LMS{T}, convert(XYZ{T}, c))
727724
# -----------------
728725

729726
correct_gamut(c::YIQ{T}) where {T} = YIQ{T}(clamp(c.y, zero(T), one(T)),
730-
clamp(c.i, convert(T,-0.5957), convert(T,0.5957)),
731-
clamp(c.q, convert(T,-0.5226), convert(T,0.5226)))
727+
clamp(c.i, convert(T,-0.595716), convert(T,0.595716)),
728+
clamp(c.q, convert(T,-0.522591), convert(T,0.522591)))
732729

730+
const M_RGB2YIQ = Mat3x3([0.299 0.587 0.114
731+
0.595716 -0.274453 -0.321263
732+
0.211456 -0.522591 0.311135 ])
733733
function cnvt(::Type{YIQ{T}}, c::AbstractRGB) where T
734734
rgb = correct_gamut(c)
735-
YIQ{T}(0.299*red(rgb)+0.587*green(rgb)+0.114*blue(rgb),
736-
0.595716*red(rgb)-0.274453*green(rgb)-0.321263*blue(rgb),
737-
0.211456*red(rgb)-0.522591*green(rgb)+0.311135*blue(rgb))
735+
@mul3x3 YIQ{T} M_RGB2YIQ red(rgb) green(rgb) blue(rgb)
738736
end
739737

740738
cnvt(::Type{YIQ{T}}, c::Color) where {T} = cnvt(YIQ{T}, convert(RGB{T}, c))
741739

742740

743741
# Everything to YCbCr
744742
# -------------------
745-
743+
# FIXME
746744
correct_gamut(c::YCbCr{T}) where {T} = YCbCr{T}(clamp(c.y, convert(T,16), convert(T,235)),
747745
clamp(c.cb, convert(T,16), convert(T,240)),
748746
clamp(c.cr, convert(T,16), convert(T,240)))
749-
747+
# FIXME
750748
function cnvt(::Type{YCbCr{T}}, c::AbstractRGB) where T
751749
rgb = correct_gamut(c)
752750
YCbCr{T}(16+65.481*red(rgb)+128.553*green(rgb)+24.966*blue(rgb),

src/utilities.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
# Helper data for CIE observer functions
22
include("cie_data.jl")
33

4+
# readonly static 3x3 matrix
5+
struct Mat3x3{T} <: AbstractMatrix{T}
6+
e::NTuple{9, T}
7+
end
8+
Mat3x3(mat::Matrix{T}) where {T} = Mat3x3{T}(Tuple(mat))
9+
Base.size(::Mat3x3) = (3, 3)
10+
Base.getindex(M::Mat3x3{T}, i) where {T} = M.e[i]
11+
Base.getindex(M::Mat3x3{T}, i::Integer, j::Integer) where {T} = M.e[3j + i - 3]
12+
Base.getindex(M::Mat3x3, I::CartesianIndex{2}) = getindex(M, I.I...)
13+
14+
macro mul3x3(C, M, c1, c2, c3)
15+
esc(quote
16+
F = typeof(0.5f0 * $c1) === Float32 ? Float32 : eltype($M)
17+
if $c1 isa N0f8 && $c2 isa N0f8 && $c3 isa N0f8
18+
s = 0xffffff
19+
c1x = Int32(reinterpret($c1)) * 0x10101
20+
c2x = Int32(reinterpret($c2)) * 0x10101
21+
c3x = Int32(reinterpret($c3)) * 0x10101
22+
else
23+
s = true
24+
c1x, c2x, c3x = $c1, $c2, $c3
25+
end
26+
@inbounds ret = $C(
27+
muladd(F($M[1,1] / s), c1x, muladd(F($M[1,2] / s), c2x, F($M[1,3] / s) * c3x)),
28+
muladd(F($M[2,1] / s), c1x, muladd(F($M[2,2] / s), c2x, F($M[2,3] / s) * c3x)),
29+
muladd(F($M[3,1] / s), c1x, muladd(F($M[3,2] / s), c2x, F($M[3,3] / s) * c3x)))
30+
ret
31+
end)
32+
end
33+
434
# for optimization
535
div60(x) = x / 60
636
_div60(x::T) where T = muladd(x, T(1/960), x * T(0x1p-6))

test/conversion.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ end
136136
@test convert(HSL, RGB{N0f8}(0.678, 0.847, 0.902)) HSL{Float32}(194.73685f0,0.5327105f0,0.7901961f0) atol=100eps(Float32)
137137

138138
# YIQ
139-
@test convert(YIQ, RGB(1,0,0)) == YIQ{Float32}(0.299, 0.595716, 0.211456)
140-
@test convert(YIQ, RGB(0,1,0)) == YIQ{Float32}(0.587, -0.274453, -0.522591)
141-
@test convert(YIQ, RGB(0,0,1)) == YIQ{Float32}(0.114, -0.321263, 0.311135)
139+
@test convert(YIQ, RGB(1, 0, 0)) YIQ{Float32}(0.299, 0.595716, 0.211456) atol=1e-7
140+
@test convert(YIQ, RGB(0, 1, 0)) YIQ{Float32}(0.587, -0.274453, -0.522591) atol=1e-7
141+
@test convert(YIQ, RGB(0, 0, 1)) YIQ{Float32}(0.114, -0.321263, 0.311135) atol=1e-7
142142
@test convert(RGB, YIQ(1.0,0.0,0.0)) == RGB(1,1,1)
143-
v = 0.5957
143+
v = 0.595716
144144
@test convert(RGB, YIQ(0.0,1.0,0.0)) == RGB(0.9563*v,0,0)
145-
v = -0.5226
145+
v = -0.522591
146146
@test convert(RGB, YIQ(0.0,0.0,-1.0)) == RGB(0,-0.6474*v,0)
147147

148148
# Gray

test/utilities.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ using Colors, FixedPointNumbers, Test
22
using InteractiveUtils # for `subtypes`
33

44
@testset "Utilities" begin
5+
@testset "Mat3x3" begin
6+
M3x3 = Colors.Mat3x3([1 2 3; 4 5 6; 7 8 9])
7+
@test @inferred(M3x3[4]) === 2
8+
@test @inferred(M3x3[2,3]) === 6
9+
@test @inferred(M3x3[CartesianIndex(3, 2)]) === 8
10+
end
11+
512
@test Colors.cbrt01(0.6) cbrt(big"0.6") atol=eps(1.0)
613
@test Colors.cbrt01(0.6f0) === cbrt(0.6f0)
714

0 commit comments

Comments
 (0)