| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
# OKHSL and OKHSV converter helper Copyright (c) 2021 Björn Ottosson, see LICENSE.OK |
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Graphics::Toolkit::Color::Space::Instance::Helper::OK; |
|
5
|
17
|
|
|
17
|
|
217
|
use v5.12; |
|
|
17
|
|
|
|
|
52
|
|
|
6
|
17
|
|
|
17
|
|
66
|
use warnings; |
|
|
17
|
|
|
|
|
22
|
|
|
|
17
|
|
|
|
|
820
|
|
|
7
|
17
|
|
|
17
|
|
96
|
use Exporter 'import'; |
|
|
17
|
|
|
|
|
275
|
|
|
|
17
|
|
|
|
|
705
|
|
|
8
|
17
|
|
|
17
|
|
522
|
use Graphics::Toolkit::Color::Space qw/min max spow mult_matrix_vector_3/; |
|
|
17
|
|
|
|
|
25
|
|
|
|
17
|
|
|
|
|
24477
|
|
|
9
|
|
|
|
|
|
|
our @EXPORT_OK = qw/toe toe_inv find_cusp to_ST get_Cs oklab_to_linear_srgb linear_srgb_to_oklab/; |
|
10
|
|
|
|
|
|
|
our %EXPORT_TAGS = (all => [@EXPORT_OK]); |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
my $toe_k1 = 0.206; |
|
13
|
|
|
|
|
|
|
my $toe_k2 = 0.03; |
|
14
|
|
|
|
|
|
|
my $toe_k3 = (1 + $toe_k1) / (1 + $toe_k2); |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
sub linear_srgb_to_oklab { |
|
17
|
25
|
|
|
25
|
0
|
34
|
my ($rgb) = @_; |
|
18
|
25
|
|
|
|
|
106
|
my @lms = mult_matrix_vector_3([[ 0.4122214708, 0.5363325363, 0.0514459929], |
|
19
|
|
|
|
|
|
|
[ 0.2119034982, 0.6806995451, 0.1073969566], |
|
20
|
|
|
|
|
|
|
[ 0.0883024619, 0.2817188376, 0.6299787005]], @$rgb); |
|
21
|
25
|
|
|
|
|
77
|
@lms = map {spow($_, (1/3))} @lms; |
|
|
75
|
|
|
|
|
101
|
|
|
22
|
25
|
|
|
|
|
75
|
my @lab = mult_matrix_vector_3([[ 0.2104542553, 0.7936177850, -0.0040720468], |
|
23
|
|
|
|
|
|
|
[ 1.9779984951, -2.4285922050, 0.4505937099], |
|
24
|
|
|
|
|
|
|
[ 0.0259040371, 0.7827717662, -0.8086757660]], @lms); |
|
25
|
25
|
|
|
|
|
60
|
return \@lab; |
|
26
|
|
|
|
|
|
|
} |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub oklab_to_linear_srgb { |
|
29
|
79
|
|
|
79
|
0
|
93
|
my ($lab) = @_; |
|
30
|
79
|
|
|
|
|
205
|
my @lms = mult_matrix_vector_3([[ 1, 0.3963377774, 0.2158037573], |
|
31
|
|
|
|
|
|
|
[ 1, -0.1055613458, -0.0638541728], |
|
32
|
|
|
|
|
|
|
[ 1, -0.0894841775, -1.2914855480]], @$lab); |
|
33
|
79
|
|
|
|
|
151
|
@lms = map {spow($_, 3)} @lms; |
|
|
237
|
|
|
|
|
281
|
|
|
34
|
79
|
|
|
|
|
160
|
my @rgb = mult_matrix_vector_3([[ 4.0767416621, -3.3077115913, 0.2309699292], |
|
35
|
|
|
|
|
|
|
[ -1.2684380046, 2.6097574011, -0.3413193965], |
|
36
|
|
|
|
|
|
|
[ -0.0041960863, -0.7034186147, 1.7076147010]], @lms); |
|
37
|
79
|
|
|
|
|
182
|
return \@rgb; |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub compute_max_saturation { |
|
41
|
44
|
|
|
44
|
0
|
53
|
my ($a, $b) = @_; |
|
42
|
44
|
|
|
|
|
77
|
my (@k, @w); # @k = Polynom-Koeffizienten, @w = oklab->lms Zeile fuer Halley |
|
43
|
44
|
100
|
|
|
|
118
|
if (-1.88170328 * $a - 0.80936493 * $b > 1) { # Rot-Kanal zuerst |
|
|
|
100
|
|
|
|
|
|
|
44
|
9
|
|
|
|
|
15
|
@k = ( 1.19086277, 1.76576728, 0.59662641, 0.75515197, 0.56771245); |
|
45
|
9
|
|
|
|
|
14
|
@w = ( 4.0767416621, -3.3077115913, 0.2309699292); |
|
46
|
|
|
|
|
|
|
} elsif (1.81444104 * $a - 1.19445276 * $b > 1) { # Gruen-Kanal zuerst |
|
47
|
20
|
|
|
|
|
41
|
@k = ( 0.73956515, -0.45954404, 0.08285427, 0.12541070, 0.14503204); |
|
48
|
20
|
|
|
|
|
59
|
@w = (-1.2684380046, 2.6097574011, -0.3413193965); |
|
49
|
|
|
|
|
|
|
} else { # Blau-Kanal zuerst |
|
50
|
15
|
|
|
|
|
29
|
@k = ( 1.35733652, -0.00915799, -1.15130210, -0.50559606, 0.00692167); |
|
51
|
15
|
|
|
|
|
21
|
@w = (-0.0041960863, -0.7034186147, 1.7076147010); |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Stufe 1: Polynom-Schaetzung von S |
|
55
|
44
|
|
|
|
|
120
|
my $S = $k[0] + $k[1]*$a + $k[2]*$b + $k[3]*$a*$a + $k[4]*$a*$b; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Stufe 2: ein Halley-Schritt zur Verfeinerung |
|
58
|
44
|
|
|
|
|
101
|
my @k_lms = (0.3963377774 * $a + 0.2158037573 * $b, |
|
59
|
|
|
|
|
|
|
-0.1055613458 * $a - 0.0638541728 * $b, |
|
60
|
|
|
|
|
|
|
-0.0894841775 * $a - 1.2914855480 * $b); |
|
61
|
|
|
|
|
|
|
|
|
62
|
44
|
|
|
|
|
74
|
my @lms = map {1 + $S * $_} @k_lms; |
|
|
132
|
|
|
|
|
173
|
|
|
63
|
44
|
|
|
|
|
68
|
my @dS = map {3 * $k_lms[$_] * $lms[$_] * $lms[$_]} 0 .. 2; |
|
|
132
|
|
|
|
|
201
|
|
|
64
|
44
|
|
|
|
|
68
|
my @dS2 = map {6 * $k_lms[$_] * $k_lms[$_] * $lms[$_]} 0 .. 2; |
|
|
132
|
|
|
|
|
179
|
|
|
65
|
44
|
|
|
|
|
52
|
@lms = map {spow($_, 3)} @lms; |
|
|
132
|
|
|
|
|
195
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
44
|
|
|
|
|
65
|
my $f = $w[0]*$lms[0] + $w[1]*$lms[1] + $w[2]*$lms[2]; |
|
68
|
44
|
|
|
|
|
63
|
my $f1 = $w[0]*$dS[0] + $w[1]*$dS[1] + $w[2]*$dS[2]; |
|
69
|
44
|
|
|
|
|
55
|
my $f2 = $w[0]*$dS2[0] + $w[1]*$dS2[1] + $w[2]*$dS2[2]; |
|
70
|
|
|
|
|
|
|
|
|
71
|
44
|
|
|
|
|
65
|
$S = $S - $f * $f1 / ($f1 * $f1 - 0.5 * $f * $f2); |
|
72
|
|
|
|
|
|
|
|
|
73
|
44
|
|
|
|
|
80
|
return $S; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub find_cusp { |
|
77
|
44
|
|
|
44
|
0
|
61
|
my ($a, $b) = @_; # normalisiert: a**2 + b**2 == 1 |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# buntester Punkt dieses Farbtons als Saettigung S = C/L |
|
80
|
44
|
|
|
|
|
81
|
my $S_cusp = compute_max_saturation($a, $b); |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# diese Oklab-Richtung nach linearem RGB, dann so skalieren, |
|
83
|
|
|
|
|
|
|
# dass der groesste Kanal genau 1 erreicht -> Gamut-Wand |
|
84
|
44
|
|
|
|
|
96
|
my $rgb = oklab_to_linear_srgb([1, $S_cusp * $a, $S_cusp * $b]); |
|
85
|
44
|
|
|
|
|
82
|
my $max_rgb = max(@$rgb); |
|
86
|
44
|
|
|
|
|
93
|
my $L_cusp = spow(1 / $max_rgb, 1/3); |
|
87
|
44
|
|
|
|
|
51
|
my $C_cusp = $L_cusp * $S_cusp; |
|
88
|
|
|
|
|
|
|
|
|
89
|
44
|
|
|
|
|
82
|
return ($L_cusp, $C_cusp); |
|
90
|
|
|
|
|
|
|
} |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub get_ST_mid { |
|
93
|
30
|
|
|
30
|
0
|
41
|
my ($a, $b) = @_; # normalisiert: a**2 + b**2 == 1 |
|
94
|
|
|
|
|
|
|
|
|
95
|
30
|
|
|
|
|
66
|
my $S = 0.11516993 + 1 / ( |
|
96
|
|
|
|
|
|
|
+ 7.44778970 + 4.15901240 * $b |
|
97
|
|
|
|
|
|
|
+ $a * (-2.19557347 + 1.75198401 * $b |
|
98
|
|
|
|
|
|
|
+ $a * (-2.13704948 - 10.02301043 * $b |
|
99
|
|
|
|
|
|
|
+ $a * (-4.24894561 + 5.38770819 * $b + 4.69891013 * $a)))); |
|
100
|
|
|
|
|
|
|
|
|
101
|
30
|
|
|
|
|
59
|
my $T = 0.11239642 + 1 / ( |
|
102
|
|
|
|
|
|
|
+ 1.61320320 - 0.68124379 * $b |
|
103
|
|
|
|
|
|
|
+ $a * ( 0.40370612 + 0.90148123 * $b |
|
104
|
|
|
|
|
|
|
+ $a * (-0.27087943 + 0.61223990 * $b |
|
105
|
|
|
|
|
|
|
+ $a * ( 0.00299215 - 0.45399568 * $b - 0.14661872 * $a)))); |
|
106
|
|
|
|
|
|
|
|
|
107
|
30
|
|
|
|
|
40
|
return ($S, $T); |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub to_ST { |
|
111
|
44
|
|
|
44
|
0
|
62
|
my ($L, $C) = @_; |
|
112
|
44
|
|
|
|
|
86
|
return ($C / $L, $C / (1 - $L)); |
|
113
|
|
|
|
|
|
|
} |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub find_gamut_intersection { |
|
116
|
30
|
|
|
30
|
0
|
52
|
my ($a, $b, $L1, $C1, $L0, $cusp_L, $cusp_C) = @_; |
|
117
|
30
|
|
|
|
|
29
|
my $t; |
|
118
|
|
|
|
|
|
|
|
|
119
|
30
|
100
|
|
|
|
72
|
if (($L1 - $L0) * $cusp_C - ($cusp_L - $L0) * $C1 <= 0) { |
|
120
|
|
|
|
|
|
|
# unterer (gerader) Schenkel: direkter Schnitt, keine Verfeinerung noetig |
|
121
|
13
|
|
|
|
|
21
|
$t = $cusp_C * $L0 / ($C1 * $cusp_L + $cusp_C * ($L0 - $L1)); |
|
122
|
|
|
|
|
|
|
} else { |
|
123
|
|
|
|
|
|
|
# oberer (gekruemmter) Schenkel: erst grober Dreiecks-Schnitt ... |
|
124
|
17
|
|
|
|
|
30
|
$t = $cusp_C * ($L0 - 1) / ($C1 * ($cusp_L - 1) + $cusp_C * ($L0 - $L1)); |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# ... dann ein Halley-Schritt auf den echten Gamutrand |
|
127
|
17
|
|
|
|
|
19
|
my $dL = $L1 - $L0; |
|
128
|
17
|
|
|
|
|
19
|
my $dC = $C1; |
|
129
|
17
|
|
|
|
|
35
|
my @k_lms = ( 0.3963377774 * $a + 0.2158037573 * $b, |
|
130
|
|
|
|
|
|
|
-0.1055613458 * $a - 0.0638541728 * $b, |
|
131
|
|
|
|
|
|
|
-0.0894841775 * $a - 1.2914855480 * $b); |
|
132
|
|
|
|
|
|
|
|
|
133
|
17
|
|
|
|
|
20
|
my @lms_dt = map { $dL + $dC * $_ } @k_lms; |
|
|
51
|
|
|
|
|
69
|
|
|
134
|
17
|
|
|
|
|
45
|
my $L = $L0 * (1 - $t) + $t * $L1; |
|
135
|
17
|
|
|
|
|
23
|
my $C = $t * $C1; |
|
136
|
17
|
|
|
|
|
20
|
my @lms = map { $L + $C * $_ } @k_lms; # l_, m_, s_ |
|
|
51
|
|
|
|
|
52
|
|
|
137
|
17
|
|
|
|
|
22
|
my @ldt = map { 3 * $lms_dt[$_] * $lms[$_] * $lms[$_] } 0 .. 2; |
|
|
51
|
|
|
|
|
84
|
|
|
138
|
17
|
|
|
|
|
24
|
my @ldt2 = map { 6 * $lms_dt[$_] * $lms_dt[$_] * $lms[$_] } 0 .. 2; |
|
|
51
|
|
|
|
|
60
|
|
|
139
|
17
|
|
|
|
|
19
|
@lms = map { $_ * $_ * $_ } @lms; # kubiert |
|
|
51
|
|
|
|
|
81
|
|
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
# Matrix = oklab_to_linear_srgb (LMS -> linear sRGB), Rand bei Kanal == 1 |
|
142
|
17
|
|
|
|
|
54
|
my $M = [[ 4.0767416621, -3.3077115913, 0.2309699292], |
|
143
|
|
|
|
|
|
|
[-1.2684380046, 2.6097574011, -0.3413193965], |
|
144
|
|
|
|
|
|
|
[-0.0041960863, -0.7034186147, 1.7076147010]]; |
|
145
|
|
|
|
|
|
|
|
|
146
|
17
|
|
|
|
|
31
|
my @res = map { $_ - 1 } mult_matrix_vector_3($M, @lms); |
|
|
51
|
|
|
|
|
64
|
|
|
147
|
17
|
|
|
|
|
30
|
my @res1 = mult_matrix_vector_3($M, @ldt); |
|
148
|
17
|
|
|
|
|
25
|
my @res2 = mult_matrix_vector_3($M, @ldt2); |
|
149
|
17
|
|
|
|
|
18
|
my $huge = 9**9**9; # ersetzt FLT_MAX |
|
150
|
|
|
|
|
|
|
my @t_ch = map { |
|
151
|
17
|
|
|
|
|
42
|
my $u = $res1[$_] / ($res1[$_] * $res1[$_] - 0.5 * $res[$_] * $res2[$_]); |
|
|
51
|
|
|
|
|
65
|
|
|
152
|
51
|
100
|
|
|
|
79
|
$u >= 0 ? -$res[$_] * $u : $huge; |
|
153
|
|
|
|
|
|
|
} 0 .. 2; |
|
154
|
|
|
|
|
|
|
|
|
155
|
17
|
|
|
|
|
33
|
$t += min(@t_ch); |
|
156
|
|
|
|
|
|
|
} |
|
157
|
30
|
|
|
|
|
41
|
return $t; |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub get_Cs { # Chroma min mid max |
|
162
|
30
|
|
|
30
|
0
|
45
|
my ($L, $a, $b) = @_; # a, b normalisiert |
|
163
|
|
|
|
|
|
|
|
|
164
|
30
|
|
|
|
|
59
|
my ($L_cusp, $C_cusp) = find_cusp($a, $b); |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# C_max: exakter Gamutrand bei dieser Helligkeit |
|
167
|
30
|
|
|
|
|
62
|
my $C_max = find_gamut_intersection($a, $b, $L, 1, $L, $L_cusp, $C_cusp); |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Cusp als Steigungen S = C/L (von Schwarz) und T = C/(1-L) (von Weiss) |
|
170
|
30
|
|
|
|
|
46
|
my ($S_max, $T_max) = to_ST($L_cusp, $C_cusp); |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# Skalierung der gekruemmten Gamut-Form |
|
173
|
30
|
|
|
|
|
74
|
my $k = $C_max / min($L * $S_max, (1 - $L) * $T_max); |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# C_mid: glatte mittlere Chroma aus der genaeherten Cusp-Lage |
|
176
|
30
|
|
|
|
|
52
|
my ($S_mid, $T_mid) = get_ST_mid($a, $b); |
|
177
|
30
|
|
|
|
|
54
|
my $C_a = $L * $S_mid; |
|
178
|
30
|
|
|
|
|
36
|
my $C_b = (1 - $L) * $T_mid; |
|
179
|
30
|
|
|
|
|
127
|
my $C_mid = 0.9 * $k * spow(1 / (1/$C_a**4 + 1/$C_b**4), 1/4); |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# C_0: innere Referenz, hue-unabhaengige Anker |
|
182
|
30
|
|
|
|
|
34
|
$C_a = $L * 0.4; |
|
183
|
30
|
|
|
|
|
35
|
$C_b = (1 - $L) * 0.8; |
|
184
|
30
|
|
|
|
|
62
|
my $C_0 = spow(1 / (1/$C_a**2 + 1/$C_b**2), 1/2); |
|
185
|
|
|
|
|
|
|
|
|
186
|
30
|
|
|
|
|
70
|
return ($C_0, $C_mid, $C_max); |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
sub toe { # korrigiert die Helligkeit |
|
190
|
32
|
|
|
32
|
0
|
39
|
my ($L) = @_; |
|
191
|
32
|
|
|
|
|
84
|
return 0.5 * ($toe_k3 * $L - $toe_k1 |
|
192
|
|
|
|
|
|
|
+ spow(($toe_k3 * $L - $toe_k1)**2 + 4 * $toe_k2 * $toe_k3 * $L, 1/2)); |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub toe_inv { |
|
196
|
35
|
|
|
35
|
0
|
45
|
my ($L) = @_; |
|
197
|
35
|
|
|
|
|
75
|
return ($L*$L + $toe_k1 * $L) / ($toe_k3 * ($L + $toe_k2)); |
|
198
|
|
|
|
|
|
|
} |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
1; |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
__END__ |