Skip to content

Commit 62d7ecf

Browse files
committed
Update helmgen
1 parent 4942ef2 commit 62d7ecf

6 files changed

Lines changed: 87 additions & 134 deletions

File tree

coloraide/spaces/helmgen.py

Lines changed: 24 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -15,138 +15,62 @@
1515
- https://github.com/Grkmyldz148/helmlab
1616
"""
1717
from __future__ import annotations
18-
from functools import lru_cache
1918
from .lab import Lab
2019
from ..cat import WHITES
2120
from ..channels import Channel, FLG_MIRROR_PERCENT
22-
from .. import util
2321
from .. import algebra as alg
24-
from ..types import Vector, Matrix
22+
from ..types import Vector
23+
2524

2625
M1 = [
27-
[ 0.4407412072890238, 0.40911369156796634, 0.18687249931895067],
28-
[ 0.12308224353121994, 0.557136239636739, 0.19274910862205916],
29-
[-0.23021079382916068, 0.9278243045135821, 0.4854100909928004]
26+
[0.758376129483666, 0.38380162590825073, -0.09608055040602373],
27+
[0.1267139363153284, 0.8421628149123207, 0.03434823621506485],
28+
[0.07639223722200054, 0.25894352627545103, 0.6139139663787314]
3029
]
30+
3131
M1_INV = [
32-
[2.126067208590642, -0.584957446988563, -0.5862125072812663],
33-
[-2.416561715802903, 5.96398983868483, -1.4378868725604865],
34-
[5.627382627163576, -11.677133103760319, 4.530507259029062]
32+
[1.4133073795748359, -0.7245661027731641, 0.2617287231983285],
33+
[-0.20907372745004316, 1.3153903462455017, -0.10631661879545858],
34+
[-0.08767910052303855, -0.46465890124976855, 1.641168001772807]
3535
]
36+
3637
M2 = [
37-
[ 0.2778609560084774, 0.21180362605092856, 0.6372017137356791],
38-
[ 1.7548720474157444, -0.9793270531556616, -0.7760752041286899],
39-
[-2.418690735750103, 3.982044105359993, -1.2833774660668076]
38+
[0.10058070589596234, 1.0155897099394149, -0.1161704158353769],
39+
[2.361576469961645, -2.4409973750629357, 0.07942090510129071],
40+
[0.04565327074453785, 0.818754884454245, -0.8644081551987829]
4041
]
42+
4143
M2_INV = [
42-
[0.8649568923272442, 0.5589393137919957, 0.09145639155676465],
43-
[0.8215892255459026, 0.2356964021282657, 0.2653934155119385],
44-
[0.9190914921797732, -0.3220781744225231, -0.1280967178320807]
44+
[0.9999999999999997, 0.3827736318539182, -0.09922417671418936],
45+
[0.9999999999999996, -0.039921540824987126, -0.13806096115936264],
46+
[0.9999999999999997, -0.017597113360184317, -1.292870720601441]
4547
]
4648

4749

48-
def xyz_d65_to_helmlab(xyz: Vector) -> Vector:
49-
"""Convert XYZ to Helmlab."""
50+
def xyz_d65_to_helmgen(xyz: Vector) -> Vector:
51+
"""Convert XYZ to Helmgen."""
5052

5153
lms = alg.matmul(M1, xyz, dims=alg.D2_D1)
5254
c = [alg.nth_root(v, 3) for v in lms]
5355
return alg.matmul(M2, c, dims=alg.D2_D1)
5456

5557

56-
def helmlab_to_xyz(lab: Vector) -> Vector:
57-
"""Convert Helmlab to XYZ."""
58+
def helmgen_to_xyz(lab: Vector) -> Vector:
59+
"""Convert Helmgen to XYZ."""
5860

5961
c = alg.matmul(M2_INV, lab, dims=alg.D2_D1)
6062
lms = [alg.spow(v, 3) for v in c]
6163
return alg.matmul(M1_INV, lms, dims=alg.D2_D1)
6264

6365

64-
@lru_cache(maxsize=1)
65-
def get_nc_lut() -> Matrix:
66-
"""
67-
Run pipeline without NC on D65 neutrals to measure achromatic error.
68-
69-
```
70-
nc = [[ncl, nca, ncb], ...]
71-
```
72-
"""
73-
74-
n = 256
75-
nc = alg.zeros((n, 3))
76-
x, y, z = util.xy_to_xyz(WHITES['2deg']['ASTM-E308-D65'])
77-
for i in range(n):
78-
y = i / (n - 1)
79-
xyz = [y * x, y, y * z]
80-
81-
# Pipeline without NC
82-
nc[i][:] = xyz_d65_to_helmlab(xyz)
83-
return nc
84-
85-
86-
def neutral_error(l: float, nc: Matrix | None = None) -> Vector:
87-
"""Neutral correction error."""
88-
89-
if nc is None:
90-
nc = get_nc_lut()
91-
92-
n = len(nc)
93-
94-
if l <= 0:
95-
return [0.0, 0.0]
96-
97-
if l < nc[0][0]:
98-
t = l / nc[0][0]
99-
return [nc[0][1] * t, nc[0][2] * t]
100-
101-
if l >= nc[n - 1][0]:
102-
return nc[n - 1][1:]
103-
104-
lo, hi = 0, n - 1
105-
while (hi - lo) > 1:
106-
mid = (lo + hi) >> 1
107-
if nc[mid][0] <= l:
108-
lo = mid
109-
else:
110-
hi = mid
111-
112-
t = (l - nc[lo][0]) / (nc[lo + 1][0] - nc[lo][0])
113-
return [
114-
nc[lo][1] + t * (nc[lo + 1][1] - nc[lo][1]),
115-
nc[lo][2] + t * (nc[lo + 1][2] - nc[lo][2])
116-
]
117-
118-
119-
def xyz_d65_to_helmlab_corrected(xyz: Vector) -> Vector:
120-
"""Convert XYZ to Helmlab."""
121-
122-
lab = xyz_d65_to_helmlab(xyz)
123-
124-
# Stage 10: Neutral correction (LUT)
125-
a_err, b_err = neutral_error(lab[0])
126-
lab[1] -= a_err
127-
lab[2] -= b_err
128-
return lab
129-
130-
131-
def helmlab_corrected_to_xyz(lab: Vector) -> Vector:
132-
"""Convert XYZ to Helmlab."""
133-
134-
# Stage 10: Neutral correction (LUT)
135-
a_err, b_err = neutral_error(lab[0])
136-
lab[1] += a_err
137-
lab[2] += b_err
138-
139-
return helmlab_to_xyz(lab)
140-
141-
14266
class Helmgen(Lab):
14367
"""Helmgen class."""
14468

14569
BASE = "xyz-d65"
14670
NAME = "helmgen"
14771
SERIALIZE = ("--helmgen",)
14872
CHANNELS = (
149-
Channel("l", 0.0, 1.168140042703694),
73+
Channel("l", 0.0, 1.0),
15074
Channel("a", -0.4, 0.4, flags=FLG_MIRROR_PERCENT),
15175
Channel("b", -0.4, 0.4, flags=FLG_MIRROR_PERCENT)
15276
)
@@ -155,9 +79,9 @@ class Helmgen(Lab):
15579
def to_base(self, coords: Vector) -> Vector:
15680
"""To XYZ."""
15781

158-
return helmlab_corrected_to_xyz(coords)
82+
return helmgen_to_xyz(coords)
15983

16084
def from_base(self, coords: Vector) -> Vector:
16185
"""From XYZ."""
16286

163-
return xyz_d65_to_helmlab_corrected(coords)
87+
return xyz_d65_to_helmgen(coords)

coloraide/spaces/helmgenlch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class Helmgenlch(LCh):
2929
NAME = "helmgenlch"
3030
SERIALIZE = ("--helmgenlch",)
3131
CHANNELS = (
32-
Channel("l", 0.0, 1.168140042703694),
33-
Channel("c", 0.0, 1.0),
32+
Channel("l", 0.0, 1.0),
33+
Channel("c", 0.0, 0.4),
3434
Channel("h", flags=FLG_ANGLE)
3535
)
3636
CHANNEL_ALIASES = {

coloraide/spaces/helmlab.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
from ..cat import WHITES
1818
from ..channels import Channel, FLG_MIRROR_PERCENT
1919
from .. import algebra as alg
20-
from ..types import Vector
20+
from ..types import Vector, Matrix
2121
import math
22-
from .helmgen import neutral_error
2322

2423
M1 = [
2524
[ 0.7342943222452644, 0.24049249952372878, -0.15763751765949208],
@@ -362,6 +361,36 @@
362361
]
363362

364363

364+
def neutral_error(l: float, nc: Matrix) -> Vector:
365+
"""Neutral correction error."""
366+
367+
n = len(nc)
368+
369+
if l <= 0:
370+
return [0.0, 0.0]
371+
372+
if l < nc[0][0]:
373+
t = l / nc[0][0]
374+
return [nc[0][1] * t, nc[0][2] * t]
375+
376+
if l >= nc[n - 1][0]:
377+
return nc[n - 1][1:]
378+
379+
lo, hi = 0, n - 1
380+
while (hi - lo) > 1:
381+
mid = (lo + hi) >> 1
382+
if nc[mid][0] <= l:
383+
lo = mid
384+
else:
385+
hi = mid
386+
387+
t = (l - nc[lo][0]) / (nc[lo + 1][0] - nc[lo][0])
388+
return [
389+
nc[lo][1] + t * (nc[lo + 1][1] - nc[lo][1]),
390+
nc[lo][2] + t * (nc[lo + 1][2] - nc[lo][2])
391+
]
392+
393+
365394
def xyz_d65_to_helmlab(xyz: Vector) -> Vector:
366395
"""Convert XYZ to Helmlab."""
367396

docs/src/markdown/colors/helmgen.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
>
1313
> Name | Range^\*^
1414
> ---- | -----
15-
> `l` | [0, ~1.168]
15+
> `l` | [0, ~0.9287]
1616
> `a` | [-0.4, 0.4]
1717
> `b` | [-0.4, 0.4]
1818
>
@@ -55,8 +55,8 @@ The string representation of the color object and the default string output use
5555
`#!css-color color(--helmgen l a b / a)` form.
5656

5757
```py play
58-
Color("helmgen", [0.60503, 0.2181, 0.03265])
59-
Color("helmgen", [0.81194, 0.10796, 0.04114]).to_string()
58+
Color("helmgen", [0.64405, 0.26587, 0.13741])
59+
Color("helmgen", [0.80957, 0.12402, 0.16774]).to_string()
6060
```
6161

6262
## Registering

tests/test_helmgen.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,27 @@ class TestHelmgen(util.ColorAssertsPyTest):
99
"""Test Helmgen."""
1010

1111
COLORS = [
12-
('red', 'color(--helmgen 0.60503 0.2181 0.03265)'),
13-
('orange', 'color(--helmgen 0.81194 0.10796 0.04114)'),
14-
('yellow', 'color(--helmgen 1.0134 0.04213 0.06982)'),
15-
('green', 'color(--helmgen 0.55753 -0.03637 0.05801)'),
16-
('blue', 'color(--helmgen 0.81708 -0.06669 -0.11057)'),
17-
('indigo', 'color(--helmgen 0.51852 -0.01084 -0.06524)'),
18-
('violet', 'color(--helmgen 0.94909 0.03565 -0.0574)'),
19-
('white', 'color(--helmgen 1.1681 0 0)'),
20-
('gray', 'color(--helmgen 0.70073 0 0)'),
12+
('red', 'color(--helmgen 0.64405 0.26587 0.13741)'),
13+
('orange', 'color(--helmgen 0.80957 0.12402 0.16774)'),
14+
('yellow', 'color(--helmgen 0.9882 0.01774 0.20186)'),
15+
('green', 'color(--helmgen 0.53125 -0.08866 0.10654)'),
16+
('blue', 'color(--helmgen 0.43897 -0.13584 -0.3167)'),
17+
('indigo', 'color(--helmgen 0.32837 0.02477 -0.15215)'),
18+
('violet', 'color(--helmgen 0.75323 0.10444 -0.09715)'),
19+
('white', 'color(--helmgen 1 0 0)'),
20+
('gray', 'color(--helmgen 0.59987 0 0)'),
2121
('black', 'color(--helmgen 0 0 0)'),
22-
('color(srgb 1.01 1.01 1.01)', 'color(--helmgen 1.177 0 0)'),
23-
('color(srgb 1e-3 1e-3 1e-3)', 'color(--helmgen 0.04978 0 0)'),
22+
('color(srgb 1.01 1.01 1.01)', 'color(--helmgen 1.0076 0 0)'),
23+
('color(srgb 1e-3 1e-3 1e-3)', 'color(--helmgen 0.04262 0 0)'),
2424
# Test color
2525
('color(--helmgen 0.5 0.1 -0.1)', 'color(--helmgen 0.5 0.1 -0.1)'),
2626
('color(--helmgen 0.5 0.1 -0.1 / 0.5)', 'color(--helmgen 0.5 0.1 -0.1 / 0.5)'),
27-
('color(--helmgen 50% 50% -50% / 50%)', 'color(--helmgen 0.58407 0.2 -0.2 / 0.5)'),
27+
('color(--helmgen 50% 50% -50% / 50%)', 'color(--helmgen 0.5 0.2 -0.2 / 0.5)'),
2828
('color(--helmgen none none none / none)', 'color(--helmgen none none none / none)'),
2929
# Test percent ranges
3030
('color(--helmgen 0% 0% 0%)', 'color(--helmgen 0 0 0)'),
31-
('color(--helmgen 100% 100% 100%)', 'color(--helmgen 1.1681 0.4 0.4)'),
32-
('color(--helmgen -100% -100% -100%)', 'color(--helmgen -1.1681 -0.4 -0.4)')
31+
('color(--helmgen 100% 100% 100%)', 'color(--helmgen 1 0.4 0.4)'),
32+
('color(--helmgen -100% -100% -100%)', 'color(--helmgen -1 -0.4 -0.4)')
3333
]
3434

3535
@pytest.mark.parametrize('color1,color2', COLORS)

tests/test_helmgenlch.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ class TestHelmgenlch(util.ColorAssertsPyTest):
1010
"""Test Helmgenlch."""
1111

1212
COLORS = [
13-
('red', 'color(--helmgenlch 0.60503 0.22053 8.5153)'),
14-
('orange', 'color(--helmgenlch 0.81194 0.11553 20.862)'),
15-
('yellow', 'color(--helmgenlch 1.0134 0.08154 58.894)'),
16-
('green', 'color(--helmgenlch 0.55753 0.06847 122.09)'),
17-
('blue', 'color(--helmgenlch 0.81708 0.12912 238.9)'),
18-
('indigo', 'color(--helmgenlch 0.51852 0.06614 260.57)'),
19-
('violet', 'color(--helmgenlch 0.94909 0.06757 301.85)'),
20-
('white', 'color(--helmgenlch 1.1681 0 none)'),
21-
('gray', 'color(--helmgenlch 0.70073 0 none)'),
13+
('red', 'color(--helmgenlch 0.64405 0.29928 27.332)'),
14+
('orange', 'color(--helmgenlch 0.80957 0.20861 53.523)'),
15+
('yellow', 'color(--helmgenlch 0.9882 0.20264 84.978)'),
16+
('green', 'color(--helmgenlch 0.53125 0.1386 129.77)'),
17+
('blue', 'color(--helmgenlch 0.43897 0.34461 246.79)'),
18+
('indigo', 'color(--helmgenlch 0.32837 0.15416 279.25)'),
19+
('violet', 'color(--helmgenlch 0.75323 0.14264 317.07)'),
20+
('white', 'color(--helmgenlch 1 0 0)'),
21+
('gray', 'color(--helmgenlch 0.59987 0 0)'),
2222
('black', 'color(--helmgenlch 0 0 none)'),
2323
# Test color
2424
('color(--helmgenlch 1.0 0.5 270)', 'color(--helmgenlch 1 0.5 270)'),
2525
('color(--helmgenlch 1.0 0.5 270 / 0.5)', 'color(--helmgenlch 1 0.5 270 / 0.5)'),
26-
('color(--helmgenlch 50% 50% 180 / 50%)', 'color(--helmgenlch 0.58407 0.5 180 / 0.5)'),
26+
('color(--helmgenlch 50% 50% 180 / 50%)', 'color(--helmgenlch 0.5 0.2 180 / 0.5)'),
2727
('color(--helmgenlch none none none / none)', 'color(--helmgenlch none none none / none)'),
2828
# Test percent ranges
2929
('color(--helmgenlch 0% 0% 0)', 'color(--helmgenlch 0 0 none)'),
30-
('color(--helmgenlch 100% 100% 360)', 'color(--helmgenlch 1.1681 1 360)'),
31-
('color(--helmgenlch -100% -100% -360)', 'color(--helmgenlch -1.1681 -1 -360)')
30+
('color(--helmgenlch 100% 100% 360)', 'color(--helmgenlch 1 0.4 360)'),
31+
('color(--helmgenlch -100% -100% -360)', 'color(--helmgenlch -1 -0.4 -360)')
3232
]
3333

3434
@pytest.mark.parametrize('color1,color2', COLORS)
@@ -69,7 +69,7 @@ def test_lightness(self):
6969
"""Test `lightness`."""
7070

7171
c = Color('color(--helmgenlch 90% 0.5 120 / 1)')
72-
self.assertEqual(c['lightness'], 1.0513260384333247)
72+
self.assertEqual(c['lightness'], 0.9)
7373
c['lightness'] = 0.3
7474
self.assertEqual(c['lightness'], 0.3)
7575

0 commit comments

Comments
 (0)