@texel/color 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/gamut.js +4 -2
- package/test/almost-equal.js +0 -15
- package/test/banner.png +0 -0
- package/test/bench-colorjs.js +0 -227
- package/test/bench-culori.js +0 -148
- package/test/bench-node.js +0 -51
- package/test/bench-size.js +0 -12
- package/test/canvas-graph.js +0 -212
- package/test/check-gamut-epsilon.js +0 -136
- package/test/colorjs-fn.js +0 -34
- package/test/example-interpolation.js +0 -107
- package/test/logo.js +0 -112
- package/test/logo.png +0 -0
- package/test/profiles/DisplayP3.icc +0 -0
- package/test/spaces/hsl.js +0 -87
- package/test/spaces/lab.js +0 -60
- package/test/test-colorjs.js +0 -86
- package/test/test-other-spaces.js +0 -115
- package/test/test.js +0 -451
- package/tools/__pycache__/calc_oklab_matrices.cpython-311.pyc +0 -0
- package/tools/calc_oklab_matrices.py +0 -233
- package/tools/print_matrices.py +0 -509
package/tools/print_matrices.py
DELETED
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
Copyright (c) 2021 Björn Ottosson
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
-
this software and associated documentation files (the "Software"), to deal in
|
|
8
|
-
the Software without restriction, including without limitation the rights to
|
|
9
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
-
of the Software, and to permit persons to whom the Software is furnished to do
|
|
11
|
-
so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
|
|
24
|
-
**Overview**
|
|
25
|
-
|
|
26
|
-
This script is pulled from Coloraide, and modified slightly by @mattdesl for JS output.
|
|
27
|
-
https://github.com/facelessuser/coloraide
|
|
28
|
-
|
|
29
|
-
The gamut approximation code was originally developed by Björn Ottosson.
|
|
30
|
-
|
|
31
|
-
Original file is located at
|
|
32
|
-
https://colab.research.google.com/drive/1JdXHhEyjjEE--19ZPH1bZV_LiGQBndzs
|
|
33
|
-
|
|
34
|
-
This notebook was used to compute coefficients for the compute_max_saturation function in this blog post:
|
|
35
|
-
http://bottosson.github.io/posts/gamutclipping/
|
|
36
|
-
|
|
37
|
-
The code is available for reference, since it could be useful to derive coefficients for other color spaces. It was
|
|
38
|
-
written quickly to derive the values and both structure and documentation is poor.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
# Commented out `IPython` magic to ensure Python compatibility.
|
|
42
|
-
import numpy as np
|
|
43
|
-
import scipy.optimize
|
|
44
|
-
import matplotlib.pyplot as plt
|
|
45
|
-
import sys
|
|
46
|
-
import os
|
|
47
|
-
import json
|
|
48
|
-
from coloraide import algebra as alg
|
|
49
|
-
sys.path.insert(0, os.getcwd())
|
|
50
|
-
|
|
51
|
-
# Use higher precision Oklab conversion matrix along with LMS matrix with our exact white point
|
|
52
|
-
from tools.calc_oklab_matrices import xyzt_white_d65, xyzt_white_d50, xyzt_get_matrix, SRGBL_TO_LMS, LMS_TO_SRGBL, LMS3_TO_OKLAB, OKLAB_TO_LMS3, LMS_TO_XYZD50, XYZD50_TO_LMS # noqa: E402
|
|
53
|
-
|
|
54
|
-
PRINT_DIAGS = False
|
|
55
|
-
|
|
56
|
-
# Recalculated for consistent reference white
|
|
57
|
-
# see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484
|
|
58
|
-
XYZ_TO_LMS = [
|
|
59
|
-
[ 0.8190224379967030, 0.3619062600528904, -0.1288737815209879 ],
|
|
60
|
-
[ 0.0329836539323885, 0.9292868615863434, 0.0361446663506424 ],
|
|
61
|
-
[ 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 ],
|
|
62
|
-
]
|
|
63
|
-
# inverse of XYZtoLMS_M
|
|
64
|
-
LMS_TO_XYZ = [
|
|
65
|
-
[ 1.2268798758459243, -0.5578149944602171, 0.2813910456659647 ],
|
|
66
|
-
[ -0.0405757452148008, 1.1122868032803170, -0.0717110580655164 ],
|
|
67
|
-
[ -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 ],
|
|
68
|
-
]
|
|
69
|
-
LMS3_TO_OKLAB = [
|
|
70
|
-
[ 0.2104542683093140, 0.7936177747023054, -0.0040720430116193 ],
|
|
71
|
-
[ 1.9779985324311684, -2.4285922420485799, 0.4505937096174110 ],
|
|
72
|
-
[ 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 ],
|
|
73
|
-
]
|
|
74
|
-
# LMStoIab_M inverted
|
|
75
|
-
OKLAB_TO_LMS3 = [
|
|
76
|
-
[ 1.0000000000000000, 0.3963377773761749, 0.2158037573099136 ],
|
|
77
|
-
[ 1.0000000000000000, -0.1055613458156586, -0.0638541728258133 ],
|
|
78
|
-
[ 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 ],
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def print_matrix (a, b, arr):
|
|
83
|
-
data = json.dumps(arr.tolist(), indent=2, separators=(',', ': '))
|
|
84
|
-
suffix = '_M'
|
|
85
|
-
print(f'export const {a}_to_{b}{suffix} = {data};\n')
|
|
86
|
-
|
|
87
|
-
def print_rational (a, b, rstr):
|
|
88
|
-
suffix = '_M'
|
|
89
|
-
print(f'export const {a}_to_{b}{suffix} = {eval(rstr)};\n')
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def print_json (label, data):
|
|
93
|
-
str = json.dumps(data, indent=2, separators=(',', ': '))
|
|
94
|
-
print(f'export const {label} = {str};\n')
|
|
95
|
-
|
|
96
|
-
def do_calc(GAMUT = 'srgb'):
|
|
97
|
-
global SRGBL_TO_LMS, LMS_TO_SRGBL, LMS3_TO_OKLAB, OKLAB_TO_LMS3, XYZD50_TO_LMS, LMS_TO_XYZD50, XYZD50_TO_LMS
|
|
98
|
-
np.set_printoptions(precision=8)
|
|
99
|
-
|
|
100
|
-
var_name = 'linear_sRGB'
|
|
101
|
-
if GAMUT == 'display-p3':
|
|
102
|
-
var_name = 'linear_DisplayP3'
|
|
103
|
-
elif GAMUT == 'rec2020':
|
|
104
|
-
var_name = 'linear_Rec2020'
|
|
105
|
-
elif GAMUT == 'a98-rgb':
|
|
106
|
-
var_name = 'linear_A98RGB'
|
|
107
|
-
elif GAMUT == 'prophoto-rgb':
|
|
108
|
-
var_name = 'linear_ProPhotoRGB'
|
|
109
|
-
|
|
110
|
-
white = xyzt_white_d50 if GAMUT == 'prophoto-rgb' else xyzt_white_d65
|
|
111
|
-
whitepoint = 'D50' if GAMUT == 'prophoto-rgb' else 'D65'
|
|
112
|
-
RGBL_TO_XYZ, XYZ_TO_RGBL = xyzt_get_matrix(white, GAMUT)
|
|
113
|
-
|
|
114
|
-
"""
|
|
115
|
-
Hard coding the matrices using rational numbers to match CSS working draft spec.
|
|
116
|
-
https://github.com/w3c/csswg-drafts/pull/7320
|
|
117
|
-
https://drafts.csswg.org/css-color-4/#color-conversion-code
|
|
118
|
-
"""
|
|
119
|
-
RGBL_TO_XYZ_RATIONAL = ""
|
|
120
|
-
XYZ_TO_RGBL_RATIONAL = ""
|
|
121
|
-
|
|
122
|
-
if GAMUT == 'srgb':
|
|
123
|
-
RGBL_TO_XYZ_RATIONAL = """[
|
|
124
|
-
[ 506752 / 1228815, 87881 / 245763, 12673 / 70218 ],
|
|
125
|
-
[ 87098 / 409605, 175762 / 245763, 12673 / 175545 ],
|
|
126
|
-
[ 7918 / 409605, 87881 / 737289, 1001167 / 1053270 ],
|
|
127
|
-
]"""
|
|
128
|
-
XYZ_TO_RGBL_RATIONAL = """[
|
|
129
|
-
[ 12831 / 3959, -329 / 214, -1974 / 3959 ],
|
|
130
|
-
[ -851781 / 878810, 1648619 / 878810, 36519 / 878810 ],
|
|
131
|
-
[ 705 / 12673, -2585 / 12673, 705 / 667 ],
|
|
132
|
-
]"""
|
|
133
|
-
elif GAMUT == 'rec2020':
|
|
134
|
-
RGBL_TO_XYZ_RATIONAL = """[
|
|
135
|
-
[ 63426534 / 99577255, 20160776 / 139408157, 47086771 / 278816314 ],
|
|
136
|
-
[ 26158966 / 99577255, 472592308 / 697040785, 8267143 / 139408157 ],
|
|
137
|
-
[ 0 / 1, 19567812 / 697040785, 295819943 / 278816314 ],
|
|
138
|
-
]"""
|
|
139
|
-
XYZ_TO_RGBL_RATIONAL = """[
|
|
140
|
-
[ 30757411 / 17917100, -6372589 / 17917100, -4539589 / 17917100 ],
|
|
141
|
-
[ -19765991 / 29648200, 47925759 / 29648200, 467509 / 29648200 ],
|
|
142
|
-
[ 792561 / 44930125, -1921689 / 44930125, 42328811 / 44930125 ],
|
|
143
|
-
]"""
|
|
144
|
-
elif GAMUT == 'a98-rgb':
|
|
145
|
-
# convert an array of linear-light a98-rgb values to CIE XYZ
|
|
146
|
-
# http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
|
147
|
-
# has greater numerical precision than section 4.3.5.3 of
|
|
148
|
-
# https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
|
|
149
|
-
# but the values below were calculated from first principles
|
|
150
|
-
# from the chromaticity coordinates of R G B W
|
|
151
|
-
RGBL_TO_XYZ_RATIONAL = """[
|
|
152
|
-
[ 573536 / 994567, 263643 / 1420810, 187206 / 994567 ],
|
|
153
|
-
[ 591459 / 1989134, 6239551 / 9945670, 374412 / 4972835 ],
|
|
154
|
-
[ 53769 / 1989134, 351524 / 4972835, 4929758 / 4972835 ],
|
|
155
|
-
]"""
|
|
156
|
-
XYZ_TO_RGBL_RATIONAL = """[
|
|
157
|
-
[ 1829569 / 896150, -506331 / 896150, -308931 / 896150 ],
|
|
158
|
-
[ -851781 / 878810, 1648619 / 878810, 36519 / 878810 ],
|
|
159
|
-
[ 16779 / 1248040, -147721 / 1248040, 1266979 / 1248040 ],
|
|
160
|
-
]"""
|
|
161
|
-
elif GAMUT == 'display-p3':
|
|
162
|
-
# TODO: Evaluate whether this is really superior to what coloraide suggests
|
|
163
|
-
# Right now it is needed to ensure accuracy with Colorjs.io
|
|
164
|
-
# However, it is not clear how they have computed the results
|
|
165
|
-
RGBL_TO_XYZ_RATIONAL = """[
|
|
166
|
-
[ 608311 / 1250200, 189793 / 714400, 198249 / 1000160 ],
|
|
167
|
-
[ 35783 / 156275, 247089 / 357200, 198249 / 2500400 ],
|
|
168
|
-
[ 0 / 1, 32229 / 714400, 5220557 / 5000800 ],
|
|
169
|
-
]"""
|
|
170
|
-
XYZ_TO_RGBL_RATIONAL = """[
|
|
171
|
-
[ 446124 / 178915, -333277 / 357830, -72051 / 178915 ],
|
|
172
|
-
[ -14852 / 17905, 63121 / 35810, 423 / 17905 ],
|
|
173
|
-
[ 11844 / 330415, -50337 / 660830, 316169 / 330415 ],
|
|
174
|
-
]"""
|
|
175
|
-
elif GAMUT == 'prophoto-rgb':
|
|
176
|
-
# override from https://github.com/w3c/csswg-drafts/issues/7675
|
|
177
|
-
# rational form exceeds JavaScript precision:
|
|
178
|
-
# https://github.com/w3c/csswg-drafts/pull/7320
|
|
179
|
-
RGBL_TO_XYZ = [
|
|
180
|
-
[ 0.79776664490064230, 0.13518129740053308, 0.03134773412839220 ],
|
|
181
|
-
[ 0.28807482881940130, 0.71183523424187300, 0.00008993693872564 ],
|
|
182
|
-
[ 0.00000000000000000, 0.00000000000000000, 0.82510460251046020 ]
|
|
183
|
-
]
|
|
184
|
-
XYZ_TO_RGBL = [
|
|
185
|
-
[ 1.34578688164715830, -0.25557208737979464, -0.05110186497554526 ],
|
|
186
|
-
[ -0.54463070512490190, 1.50824774284514680, 0.02052744743642139 ],
|
|
187
|
-
[ 0.00000000000000000, 0.00000000000000000, 1.21196754563894520 ]
|
|
188
|
-
]
|
|
189
|
-
|
|
190
|
-
if len(XYZ_TO_RGBL_RATIONAL) > 0:
|
|
191
|
-
XYZ_TO_RGBL = eval(XYZ_TO_RGBL_RATIONAL)
|
|
192
|
-
if len(RGBL_TO_XYZ_RATIONAL) > 0:
|
|
193
|
-
RGBL_TO_XYZ = eval(RGBL_TO_XYZ_RATIONAL)
|
|
194
|
-
|
|
195
|
-
# Calculate the gamut <-> LMS matrices to adjust the working gamut
|
|
196
|
-
if GAMUT == 'srgb':
|
|
197
|
-
RGBL_TO_LMS = SRGBL_TO_LMS
|
|
198
|
-
LMS_TO_RGBL = LMS_TO_SRGBL
|
|
199
|
-
elif GAMUT == 'prophoto-rgb':
|
|
200
|
-
# Note: this is not currently used in the final results as ProPhoto gamut is not yet supported
|
|
201
|
-
RGBL_TO_LMS = alg.matmul(XYZD50_TO_LMS, RGBL_TO_XYZ)
|
|
202
|
-
LMS_TO_RGBL = alg.inv(RGBL_TO_LMS)
|
|
203
|
-
else:
|
|
204
|
-
RGBL_TO_LMS = alg.matmul(XYZ_TO_LMS, RGBL_TO_XYZ)
|
|
205
|
-
LMS_TO_RGBL = alg.inv(RGBL_TO_LMS)
|
|
206
|
-
|
|
207
|
-
def printarray (label, arr):
|
|
208
|
-
print(label, '[ ' + ', '.join([str(n) for n in arr]) + ' ]')
|
|
209
|
-
|
|
210
|
-
# print('RGBL_TO_LMS',RGBL_TO_LMS)
|
|
211
|
-
# print('LMS_TO_RGBL', LMS_TO_RGBL)
|
|
212
|
-
|
|
213
|
-
RGBL_TO_LMS = np.asfarray(RGBL_TO_LMS)
|
|
214
|
-
LMS_TO_RGBL = np.asfarray(LMS_TO_RGBL)
|
|
215
|
-
LMS3_TO_OKLAB = np.asfarray(LMS3_TO_OKLAB)
|
|
216
|
-
OKLAB_TO_LMS3 = np.asfarray(OKLAB_TO_LMS3)
|
|
217
|
-
|
|
218
|
-
def linear_srgb_to_oklab(c):
|
|
219
|
-
l = RGBL_TO_LMS[0][0] * c[0, ...] + RGBL_TO_LMS[0][1] * c[1, ...] + RGBL_TO_LMS[0][2] * c[2, ...]
|
|
220
|
-
m = RGBL_TO_LMS[1][0] * c[0, ...] + RGBL_TO_LMS[1][1] * c[1, ...] + RGBL_TO_LMS[1][2] * c[2, ...]
|
|
221
|
-
s = RGBL_TO_LMS[2][0] * c[0, ...] + RGBL_TO_LMS[2][1] * c[1, ...] + RGBL_TO_LMS[2][2] * c[2, ...]
|
|
222
|
-
|
|
223
|
-
l_ = np.cbrt(l)
|
|
224
|
-
m_ = np.cbrt(m)
|
|
225
|
-
s_ = np.cbrt(s)
|
|
226
|
-
|
|
227
|
-
return np.array([
|
|
228
|
-
LMS3_TO_OKLAB[0][0] * l_ + LMS3_TO_OKLAB[0][1] * m_ + LMS3_TO_OKLAB[0][2] * s_,
|
|
229
|
-
LMS3_TO_OKLAB[1][0] * l_ + LMS3_TO_OKLAB[1][1] * m_ + LMS3_TO_OKLAB[1][2] * s_,
|
|
230
|
-
LMS3_TO_OKLAB[2][0] * l_ + LMS3_TO_OKLAB[2][1] * m_ + LMS3_TO_OKLAB[2][2] * s_,
|
|
231
|
-
])
|
|
232
|
-
|
|
233
|
-
# define functions for R, G and B as functions of S, h (with L = 1 and S = C/L)
|
|
234
|
-
|
|
235
|
-
def to_lms(S, h):
|
|
236
|
-
a = S * np.cos(h)
|
|
237
|
-
b = S * np.sin(h)
|
|
238
|
-
|
|
239
|
-
l_ = OKLAB_TO_LMS3[0][0] + OKLAB_TO_LMS3[0][1] * a + OKLAB_TO_LMS3[0][2] * b
|
|
240
|
-
m_ = OKLAB_TO_LMS3[1][0] + OKLAB_TO_LMS3[1][1] * a + OKLAB_TO_LMS3[1][2] * b
|
|
241
|
-
s_ = OKLAB_TO_LMS3[2][0] + OKLAB_TO_LMS3[2][1] * a + OKLAB_TO_LMS3[2][2] * b
|
|
242
|
-
|
|
243
|
-
l = l_ * l_ * l_
|
|
244
|
-
m = m_ * m_ * m_
|
|
245
|
-
s = s_ * s_ * s_
|
|
246
|
-
|
|
247
|
-
return (l, m, s)
|
|
248
|
-
|
|
249
|
-
def to_lms_dS(S, h):
|
|
250
|
-
a = S * np.cos(h)
|
|
251
|
-
b = S * np.sin(h)
|
|
252
|
-
|
|
253
|
-
l_ = OKLAB_TO_LMS3[0][0] + OKLAB_TO_LMS3[0][1] * a + OKLAB_TO_LMS3[0][2] * b
|
|
254
|
-
m_ = OKLAB_TO_LMS3[1][0] + OKLAB_TO_LMS3[1][1] * a + OKLAB_TO_LMS3[1][2] * b
|
|
255
|
-
s_ = OKLAB_TO_LMS3[2][0] + OKLAB_TO_LMS3[2][1] * a + OKLAB_TO_LMS3[2][2] * b
|
|
256
|
-
|
|
257
|
-
l = (LMS3_TO_OKLAB[0][1] * np.cos(h) + LMS3_TO_OKLAB[0][1] * np.sin(h)) * 3 * l_* l_
|
|
258
|
-
m = (LMS3_TO_OKLAB[1][1] * np.cos(h) + LMS3_TO_OKLAB[1][1] * np.sin(h)) * 3 * m_* m_
|
|
259
|
-
s = (LMS3_TO_OKLAB[2][1] * np.cos(h) + LMS3_TO_OKLAB[2][1] * np.sin(h)) * 3 * s_* s_
|
|
260
|
-
|
|
261
|
-
return (l, m, s)
|
|
262
|
-
|
|
263
|
-
def to_lms_dS2(S, h):
|
|
264
|
-
a = S * np.cos(h)
|
|
265
|
-
b = S * np.sin(h)
|
|
266
|
-
|
|
267
|
-
l_ = OKLAB_TO_LMS3[0][0] + OKLAB_TO_LMS3[0][1] * a + OKLAB_TO_LMS3[0][2] * b
|
|
268
|
-
m_ = OKLAB_TO_LMS3[1][0] + OKLAB_TO_LMS3[1][1] * a + OKLAB_TO_LMS3[1][2] * b
|
|
269
|
-
s_ = OKLAB_TO_LMS3[2][0] + OKLAB_TO_LMS3[2][1] * a + OKLAB_TO_LMS3[2][2] * b
|
|
270
|
-
|
|
271
|
-
l = (LMS3_TO_OKLAB[0][1] * np.cos(h) + LMS3_TO_OKLAB[0][2] * np.sin(h)) ** 2 * 6 * l_
|
|
272
|
-
m = (LMS3_TO_OKLAB[1][1] * np.cos(h) + LMS3_TO_OKLAB[0][2] * np.sin(h)) ** 2 * 6 * m_
|
|
273
|
-
s = (LMS3_TO_OKLAB[2][1] * np.cos(h) + LMS3_TO_OKLAB[0][2] * np.sin(h)) ** 2 * 6 * s_
|
|
274
|
-
|
|
275
|
-
return (l, m, s)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def to_R(S, h):
|
|
279
|
-
(l, m, s) = to_lms(S, h)
|
|
280
|
-
return LMS_TO_RGBL[0][0] * l + LMS_TO_RGBL[0][1] * m + LMS_TO_RGBL[0][2] * s
|
|
281
|
-
|
|
282
|
-
def to_R_dS(S, h):
|
|
283
|
-
(l, m, s) = to_lms_dS(S, h)
|
|
284
|
-
return LMS_TO_RGBL[0][0] * l + LMS_TO_RGBL[0][1] * m + LMS_TO_RGBL[0][2] * s
|
|
285
|
-
|
|
286
|
-
def to_R_dS2(S, h):
|
|
287
|
-
(l, m, s) = to_lms_dS2(S, h)
|
|
288
|
-
return LMS_TO_RGBL[0][0] * l + LMS_TO_RGBL[0][1] * m + LMS_TO_RGBL[0][2] * s
|
|
289
|
-
|
|
290
|
-
def to_G(S, h):
|
|
291
|
-
(l, m, s) = to_lms(S, h)
|
|
292
|
-
return LMS_TO_RGBL[1][0] * l + LMS_TO_RGBL[1][1] * m + LMS_TO_RGBL[1][2] * s
|
|
293
|
-
|
|
294
|
-
def to_G_dS(S, h):
|
|
295
|
-
(l, m, s) = to_lms_dS(S, h)
|
|
296
|
-
return LMS_TO_RGBL[1][0] * l + LMS_TO_RGBL[1][1] * m + LMS_TO_RGBL[1][2] * s
|
|
297
|
-
|
|
298
|
-
def to_G_dS2(S, h):
|
|
299
|
-
(l, m, s) = to_lms_dS2(S, h)
|
|
300
|
-
return LMS_TO_RGBL[1][0] * l + LMS_TO_RGBL[1][1] * m + LMS_TO_RGBL[1][2] * s
|
|
301
|
-
|
|
302
|
-
def to_B(S, h):
|
|
303
|
-
(l, m, s) = to_lms(S, h)
|
|
304
|
-
return LMS_TO_RGBL[2][0] * l + LMS_TO_RGBL[2][1] * m + LMS_TO_RGBL[2][2] * s
|
|
305
|
-
|
|
306
|
-
def to_B_dS(S, h):
|
|
307
|
-
(l, m, s) = to_lms_dS(S, h)
|
|
308
|
-
return LMS_TO_RGBL[2][0] * l + LMS_TO_RGBL[2][1] * m + LMS_TO_RGBL[2][2] * s
|
|
309
|
-
|
|
310
|
-
def to_B_dS2(S, h):
|
|
311
|
-
(l, m, s) = to_lms_dS2(S, h)
|
|
312
|
-
return LMS_TO_RGBL[2][0] * l + LMS_TO_RGBL[2][1] * m + LMS_TO_RGBL[2][2] * s
|
|
313
|
-
|
|
314
|
-
if GAMUT != 'prophoto-rgb':
|
|
315
|
-
hs, Ss = np.meshgrid(np.linspace(-np.pi, np.pi, 720), np.linspace(0, 1, 200))
|
|
316
|
-
|
|
317
|
-
Rs = to_R(Ss, hs)
|
|
318
|
-
Gs = to_G(Ss, hs)
|
|
319
|
-
Bs = to_B(Ss, hs)
|
|
320
|
-
|
|
321
|
-
gamut = np.minimum(Rs, np.minimum(Gs, Bs))
|
|
322
|
-
|
|
323
|
-
r_lab = linear_srgb_to_oklab(np.array([1, 0, 0]))
|
|
324
|
-
g_lab = linear_srgb_to_oklab(np.array([0, 1, 0]))
|
|
325
|
-
b_lab = linear_srgb_to_oklab(np.array([0, 0, 1]))
|
|
326
|
-
|
|
327
|
-
r_h = np.arctan2(r_lab[2], r_lab[1])
|
|
328
|
-
g_h = np.arctan2(g_lab[2], g_lab[1])
|
|
329
|
-
b_h = np.arctan2(b_lab[2], b_lab[1])
|
|
330
|
-
|
|
331
|
-
r_dir = 0.5 * np.array([np.cos(b_h) + np.cos(g_h), np.sin(b_h) + np.sin(g_h)])
|
|
332
|
-
g_dir = 0.5 * np.array([np.cos(b_h) + np.cos(r_h), np.sin(b_h) + np.sin(r_h)])
|
|
333
|
-
b_dir = 0.5 * np.array([np.cos(r_h) + np.cos(g_h), np.sin(r_h) + np.sin(g_h)])
|
|
334
|
-
|
|
335
|
-
r_dir /= r_dir[0]** 2 + r_dir[1]** 2
|
|
336
|
-
g_dir /= g_dir[0]** 2 + g_dir[1]** 2
|
|
337
|
-
b_dir /= b_dir[0]** 2 + b_dir[1]** 2
|
|
338
|
-
|
|
339
|
-
# These are coefficients to quickly test which component goes below zero first.
|
|
340
|
-
# Used like this in compute_max_saturation:
|
|
341
|
-
# if (-1.88170328f * a - 0.80936493f * b > 1) // Red component goes below zero first
|
|
342
|
-
|
|
343
|
-
r_hs, r_Ss = np.meshgrid(np.linspace(g_h, 2 * np.pi + b_h, 200), np.linspace(0, 1, 200))
|
|
344
|
-
|
|
345
|
-
r_Rs = to_R(r_Ss, r_hs)
|
|
346
|
-
|
|
347
|
-
g_hs, g_Ss = np.meshgrid(np.linspace(b_h, r_h, 200), np.linspace(0, 1, 200))
|
|
348
|
-
|
|
349
|
-
g_Gs = to_G(g_Ss, g_hs)
|
|
350
|
-
|
|
351
|
-
b_hs, b_Ss = np.meshgrid(np.linspace(r_h, g_h, 200), np.linspace(0, 1, 200))
|
|
352
|
-
|
|
353
|
-
b_Bs = to_B(b_Ss, b_hs)
|
|
354
|
-
|
|
355
|
-
# These are numerical fits to the edge of the chroma
|
|
356
|
-
# The resulting coefficient, x_R, x_G and x_B are used in compute_max_saturation, as values for k0
|
|
357
|
-
its = 1
|
|
358
|
-
|
|
359
|
-
resolution = 100000
|
|
360
|
-
|
|
361
|
-
h = np.linspace(g_h, 2 * np.pi + b_h, resolution)
|
|
362
|
-
a = np.cos(h)
|
|
363
|
-
b = np.sin(h)
|
|
364
|
-
|
|
365
|
-
def e_R(x):
|
|
366
|
-
S = x[0] + x[1] * a + x[2] * b + x[3] * a ** 2 + x[4] * a * b
|
|
367
|
-
S = np.maximum(0, S)
|
|
368
|
-
|
|
369
|
-
# optimize for solution that is easiest to solve with one step Haley's method
|
|
370
|
-
f = to_R(S, h)
|
|
371
|
-
f1 = to_R_dS(S, h)
|
|
372
|
-
f2 = to_R_dS2(S, h)
|
|
373
|
-
S_1 = S - f * f1 / (f1 ** 2 - f * f2 / 2)
|
|
374
|
-
|
|
375
|
-
f_ = to_R(S_1, h)
|
|
376
|
-
return np.average(f_ ** 10) # + f_[0] ** 2 + f_[-1] ** 2
|
|
377
|
-
|
|
378
|
-
x_R = scipy.optimize.minimize(e_R, np.array([1.19086277, 1.76576728, 0.59662641, 0.75515197, 0.56771245])).x
|
|
379
|
-
|
|
380
|
-
# printarray('R COEFF', x_R)
|
|
381
|
-
|
|
382
|
-
S_R = x_R[0] + x_R[1] * a + x_R[2] * b + x_R[3] * a ** 2 + x_R[4] * a * b
|
|
383
|
-
|
|
384
|
-
S_R1 = S_R
|
|
385
|
-
for i in range(0, its):
|
|
386
|
-
f = to_R(S_R1, h)
|
|
387
|
-
f1 = to_R_dS(S_R1, h)
|
|
388
|
-
f2 = to_R_dS2(S_R1, h)
|
|
389
|
-
S_R1 = S_R1 - f * f1 / (f1 ** 2 - f * f2 / (2))
|
|
390
|
-
|
|
391
|
-
plt.plot(S_R1, 'r')
|
|
392
|
-
|
|
393
|
-
#####
|
|
394
|
-
|
|
395
|
-
h = np.linspace(b_h, r_h, resolution)
|
|
396
|
-
a = np.cos(h)
|
|
397
|
-
b = np.sin(h)
|
|
398
|
-
|
|
399
|
-
def e_G(x):
|
|
400
|
-
S = x[0] + x[1] * a + x[2] * b + x[3] * a ** 2 + x[4] * a * b
|
|
401
|
-
S = np.maximum(0, S)
|
|
402
|
-
|
|
403
|
-
# optimize for solution that is easiest to solve with one step Haley's method
|
|
404
|
-
f = to_G(S, h)
|
|
405
|
-
f1 = to_G_dS(S, h)
|
|
406
|
-
f2 = to_G_dS2(S, h)
|
|
407
|
-
S_1 = S - f * f1 / (f1 ** 2 - f * f2 / 2)
|
|
408
|
-
|
|
409
|
-
f_ = to_G(S_1, h)
|
|
410
|
-
return np.average(f_ ** 10) # + f_[0] ** 2 + f_[-1] ** 2
|
|
411
|
-
|
|
412
|
-
x_G = scipy.optimize.minimize(e_G, np.array([0.73956515, -0.45954404, 0.08285427, 0.12541073, -0.14503204])).x
|
|
413
|
-
|
|
414
|
-
# printarray('G COEFF', x_G)
|
|
415
|
-
|
|
416
|
-
S_G = x_G[0] + x_G[1] * a + x_G[2] * b + x_G[3] * a ** 2 + x_G[4] * a * b
|
|
417
|
-
|
|
418
|
-
S_G1 = S_G
|
|
419
|
-
for i in range(0, its):
|
|
420
|
-
f = to_G(S_G1, h)
|
|
421
|
-
f1 = to_G_dS(S_G1, h)
|
|
422
|
-
f2 = to_G_dS2(S_G1, h)
|
|
423
|
-
S_G1 = S_G1 - f * f1 / (f1 ** 2 - f * f2 / (2))
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
#####
|
|
427
|
-
|
|
428
|
-
h = np.linspace(r_h, g_h, resolution)
|
|
429
|
-
a = np.cos(h)
|
|
430
|
-
b = np.sin(h)
|
|
431
|
-
|
|
432
|
-
def e_B(x):
|
|
433
|
-
S = x[0] + x[1] * a + x[2] * b + x[3] * a ** 2 + x[4] * a * b
|
|
434
|
-
S = np.maximum(0, S)
|
|
435
|
-
|
|
436
|
-
# optimize for solution that is easiest to solve with one step Haley's method
|
|
437
|
-
f = to_B(S, h)
|
|
438
|
-
f1 = to_B_dS(S, h)
|
|
439
|
-
f2 = to_B_dS2(S, h)
|
|
440
|
-
S_1 = S - f * f1 / (f1 ** 2 - f * f2 / 2)
|
|
441
|
-
|
|
442
|
-
f_ = to_B(S_1, h)
|
|
443
|
-
return np.average(f_ ** 10) # + f_[0] ** 2 + f_[-1] ** 2
|
|
444
|
-
|
|
445
|
-
x_B = scipy.optimize.minimize(e_B, np.array([1.35733652, -0.00915799, -1.1513021, -0.50559606, 0.00692167])).x
|
|
446
|
-
|
|
447
|
-
# printarray('B COEFF', x_B)
|
|
448
|
-
|
|
449
|
-
S_B = x_B[0] + x_B[1] * a + x_B[2] * b + x_B[3] * a ** 2 + x_B[4] * a * b
|
|
450
|
-
|
|
451
|
-
S_B1 = S_B
|
|
452
|
-
for i in range(0, its):
|
|
453
|
-
f = to_B(S_B1, h)
|
|
454
|
-
f1 = to_B_dS(S_B1, h)
|
|
455
|
-
f2 = to_B_dS2(S_B1, h)
|
|
456
|
-
S_B1 = S_B1 - f * f1 / (f1 ** 2 - f * f2 / (2))
|
|
457
|
-
|
|
458
|
-
print(f'// {var_name} space\n')
|
|
459
|
-
|
|
460
|
-
print(f'// {var_name} to XYZ ({whitepoint}) matrices\n')
|
|
461
|
-
if len(RGBL_TO_XYZ_RATIONAL) > 0:
|
|
462
|
-
print_rational(var_name, 'XYZ', RGBL_TO_XYZ_RATIONAL)
|
|
463
|
-
else:
|
|
464
|
-
print_matrix(var_name, 'XYZ', np.asfarray(RGBL_TO_XYZ))
|
|
465
|
-
|
|
466
|
-
if len(XYZ_TO_RGBL_RATIONAL) > 0:
|
|
467
|
-
print_rational('XYZ', var_name, XYZ_TO_RGBL_RATIONAL)
|
|
468
|
-
else:
|
|
469
|
-
print_matrix('XYZ', var_name, np.asfarray(XYZ_TO_RGBL))
|
|
470
|
-
|
|
471
|
-
print(f'// {var_name} to LMS matrices\n')
|
|
472
|
-
print_matrix(var_name, 'LMS', RGBL_TO_LMS)
|
|
473
|
-
print_matrix('LMS', var_name, LMS_TO_RGBL)
|
|
474
|
-
|
|
475
|
-
if GAMUT != 'prophoto-rgb':
|
|
476
|
-
coeff = [
|
|
477
|
-
[
|
|
478
|
-
r_dir.tolist(),
|
|
479
|
-
x_R.tolist(),
|
|
480
|
-
],
|
|
481
|
-
[
|
|
482
|
-
g_dir.tolist(),
|
|
483
|
-
x_G.tolist()
|
|
484
|
-
],
|
|
485
|
-
[
|
|
486
|
-
b_dir.tolist(),
|
|
487
|
-
x_B.tolist()
|
|
488
|
-
]
|
|
489
|
-
]
|
|
490
|
-
print(f'// {var_name} coefficients for OKLab gamut approximation\n')
|
|
491
|
-
print_json(f'OKLab_to_{var_name}_coefficients', coeff)
|
|
492
|
-
else:
|
|
493
|
-
print(f'// {var_name} does not yet support OKLab gamut approximation\n')
|
|
494
|
-
|
|
495
|
-
# print things...
|
|
496
|
-
|
|
497
|
-
print(f'/** This file is auto-generated by tools/print_matrices.py */\n')
|
|
498
|
-
print(f'// OKLab to LMS matrices\n')
|
|
499
|
-
print_matrix('OKLab', 'LMS', np.asfarray(OKLAB_TO_LMS3))
|
|
500
|
-
print_matrix('LMS', 'OKLab', np.asfarray(LMS3_TO_OKLAB))
|
|
501
|
-
print_matrix('XYZ', 'LMS', np.asfarray(XYZ_TO_LMS))
|
|
502
|
-
print_matrix('LMS', 'XYZ', np.asfarray(LMS_TO_XYZ))
|
|
503
|
-
|
|
504
|
-
# don't need these...
|
|
505
|
-
# print_matrix('XYZD50', 'LMS', np.asfarray(XYZD50_TO_LMS))
|
|
506
|
-
# print_matrix('LMS', 'XYZD50', np.asfarray(LMS_TO_XYZD50))
|
|
507
|
-
|
|
508
|
-
for gamut in ['srgb', 'display-p3', 'rec2020', 'a98-rgb', 'prophoto-rgb']:
|
|
509
|
-
d = do_calc(gamut)
|