Skip to content

Commit ff5851a

Browse files
authored
Handle achromatic threshold better (#462)
Spaces that are scaled on an order of magnitude of 1 should not have the same threshold of a space scaled at an order of magnitude of 100. If they are, this can cause colors to be considered achromatic much earlier if they are scaled small.
1 parent 1621167 commit ff5851a

37 files changed

+118
-81
lines changed

coloraide/average.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Average colors together."""
22
from __future__ import annotations
33
import math
4+
from . import util
45
from .spaces import HWBish
56
from .types import ColorInput
67
from typing import Iterable, TYPE_CHECKING
@@ -9,9 +10,6 @@
910
from .color import Color
1011

1112

12-
ACHROMATIC_THRESHOLD = 1e-4
13-
14-
1513
def average(
1614
color_cls: type[Color],
1715
colors: Iterable[ColorInput],
@@ -88,7 +86,7 @@ def average(
8886
else:
8987
sin /= total
9088
cos /= total
91-
if abs(sin) < ACHROMATIC_THRESHOLD and abs(cos) < ACHROMATIC_THRESHOLD:
89+
if abs(sin) < util.ACHROMATIC_THRESHOLD_SM and abs(cos) < util.ACHROMATIC_THRESHOLD_SM:
9290
sums[i] = math.nan
9391
else:
9492
avg_theta = math.degrees(math.atan2(sin, cos))

coloraide/spaces/cam16_jmh.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
import bisect
1313
from .. import util
1414
from .. import algebra as alg
15-
from ..spaces import Space, LChish
15+
from .lch import LCh
1616
from ..cat import WHITES, CAT16
1717
from ..channels import Channel, FLG_ANGLE
18-
from .lch import ACHROMATIC_THRESHOLD
1918
from ..types import Vector, VectorLike
2019

2120
# CAT16
@@ -352,7 +351,7 @@ def cam_jmh_to_xyz(jmh: Vector, env: Environment) -> Vector:
352351
return cam_to_xyz(J=J, M=M, h=h, env=env)
353352

354353

355-
class CAM16JMh(LChish, Space):
354+
class CAM16JMh(LCh):
356355
"""CAM16 class (JMh)."""
357356

358357
BASE = "xyz-d65"
@@ -396,7 +395,7 @@ def is_achromatic(self, coords: Vector) -> bool | None:
396395
"""Check if color is achromatic."""
397396

398397
# Account for both positive and negative chroma
399-
return coords[0] == 0 or abs(coords[1]) < ACHROMATIC_THRESHOLD
398+
return coords[0] == 0 or abs(coords[1]) < self.achromatic_threshold
400399

401400
def to_base(self, coords: Vector) -> Vector:
402401
"""From CAM16 JMh to XYZ."""

coloraide/spaces/cam16_ucs.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from __future__ import annotations
99
import math
1010
from .cam16_jmh import CAM16JMh, xyz_to_cam, cam_to_xyz, Environment
11-
from ..spaces import Space, Labish
12-
from .lch import ACHROMATIC_THRESHOLD
11+
from .lab import Lab
1312
from ..cat import WHITES
1413
from .. import util
1514
from ..channels import Channel, FLG_MIRROR_PERCENT
@@ -91,7 +90,7 @@ def cam_ucs_to_cam_jmh(ucs: Vector, model: str) -> Vector:
9190
]
9291

9392

94-
class CAM16UCS(Labish, Space):
93+
class CAM16UCS(Lab):
9594
"""CAM16 UCS (Jab) class."""
9695

9796
BASE = "cam16-jmh"
@@ -114,7 +113,7 @@ def is_achromatic(self, coords: Vector) -> bool:
114113
"""Check if color is achromatic."""
115114

116115
j, m = cam_ucs_to_cam_jmh(coords, self.MODEL)[:-1]
117-
return j == 0 or abs(m) < ACHROMATIC_THRESHOLD
116+
return j == 0 or abs(m) < self.achromatic_threshold
118117

119118
def to_base(self, coords: Vector) -> Vector:
120119
"""To CAM16 JMh from CAM16."""

coloraide/spaces/cmy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Uncalibrated, naive CMY color space."""
22
from __future__ import annotations
3+
from .. import util
34
from ..spaces import Regular, Space
45
from ..channels import Channel
56
from ..cat import WHITES
@@ -43,7 +44,7 @@ def is_achromatic(self, coords: Vector) -> bool:
4344

4445
black = [1, 1, 1]
4546
for x in alg.vcross(coords, black):
46-
if not math.isclose(0.0, x, abs_tol=1e-4):
47+
if not math.isclose(0.0, x, abs_tol=util.ACHROMATIC_THRESHOLD_SM):
4748
return False
4849
return True
4950

coloraide/spaces/cmyk.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
https://www.w3.org/TR/css-color-5/#cmyk-rgb
55
"""
66
from __future__ import annotations
7+
from .. import util
78
from ..spaces import Space
89
from ..channels import Channel
910
from ..cat import WHITES
@@ -60,12 +61,12 @@ class CMYK(Space):
6061
def is_achromatic(self, coords: Vector) -> bool:
6162
"""Test if color is achromatic."""
6263

63-
if math.isclose(1.0, coords[-1], abs_tol=1e-4):
64+
if math.isclose(1.0, coords[-1], abs_tol=util.ACHROMATIC_THRESHOLD_SM):
6465
return True
6566

6667
black = [1, 1, 1]
6768
for x in alg.vcross(coords[:-1], black):
68-
if not math.isclose(0.0, x, abs_tol=1e-5):
69+
if not math.isclose(0.0, x, abs_tol=util.ACHROMATIC_THRESHOLD_SM):
6970
return False
7071
return True
7172

coloraide/spaces/cubehelix.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
THIS SOFTWARE.
2525
"""
2626
from __future__ import annotations
27-
from ..spaces import Space, HSLish
27+
from . hsl import HSL
2828
from ..cat import WHITES
2929
from ..channels import Channel, FLG_ANGLE
3030
import math
@@ -72,7 +72,7 @@ def cubehelix_to_srgb(coords: Vector) -> Vector:
7272
]
7373

7474

75-
class Cubehelix(HSLish, Space):
75+
class Cubehelix(HSL):
7676
"""Cubehelix class."""
7777

7878
BASE = 'srgb'
@@ -103,7 +103,7 @@ def normalize(self, coords: Vector) -> Vector:
103103
def is_achromatic(self, coords: Vector) -> bool:
104104
"""Check if color is achromatic."""
105105

106-
return abs(coords[1]) < 1e-4 or coords[2] > (1 - 1e-7) or coords[2] < 1e-08
106+
return abs(coords[1]) < self.achromatic_threshold or coords[2] > (1 - 1e-7) or coords[2] < 1e-08
107107

108108
def to_base(self, coords: Vector) -> Vector:
109109
"""To LChuv from HSLuv."""

coloraide/spaces/hct.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,11 @@
3838
"""
3939
from __future__ import annotations
4040
from .. import algebra as alg
41-
from ..spaces import Space, LChish
41+
from .lch import LCh
4242
from ..cat import WHITES
4343
from ..channels import Channel, FLG_ANGLE
4444
from .cam16_jmh import Environment, cam_to_xyz, xyz_to_cam
4545
from .lab import EPSILON, KAPPA, KE
46-
from .lch import ACHROMATIC_THRESHOLD
4746
from ..types import Vector
4847
import math
4948

@@ -156,7 +155,7 @@ def xyz_to_hct(coords: Vector, env: Environment) -> Vector:
156155
return [h, c, t]
157156

158157

159-
class HCT(LChish, Space):
158+
class HCT(LCh):
160159
"""HCT class."""
161160

162161
BASE = "xyz-d65"
@@ -200,7 +199,7 @@ def is_achromatic(self, coords: Vector) -> bool | None:
200199
"""Check if color is achromatic."""
201200

202201
# Account for both positive and negative chroma
203-
return coords[2] == 0 or abs(coords[1]) < ACHROMATIC_THRESHOLD
202+
return coords[2] == 0 or abs(coords[1]) < self.achromatic_threshold
204203

205204
def names(self) -> tuple[Channel, ...]:
206205
"""Return LCh-ish names in the order L C h."""

coloraide/spaces/hpluv.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
SOFTWARE.
2626
"""
2727
from __future__ import annotations
28-
from ..spaces import Space, HSLish
28+
from .hsl import HSL
2929
from ..cat import WHITES
3030
from ..channels import Channel, FLG_ANGLE
3131
from .lab import EPSILON, KAPPA
@@ -103,7 +103,7 @@ def luv_to_hpluv(luv: Vector) -> Vector:
103103
return [util.constrain_hue(h), s, l]
104104

105105

106-
class HPLuv(HSLish, Space):
106+
class HPLuv(HSL):
107107
"""HPLuv class."""
108108

109109
BASE = 'luv'
@@ -120,6 +120,7 @@ class HPLuv(HSLish, Space):
120120
"lightness": "l"
121121
}
122122
WHITE = WHITES['2deg']['D65']
123+
GAMUT_CHECK = None
123124

124125
def normalize(self, coords: Vector) -> Vector:
125126
"""Normalize coordinates."""
@@ -132,7 +133,7 @@ def normalize(self, coords: Vector) -> Vector:
132133
def is_achromatic(self, coords: Vector) -> bool:
133134
"""Check if color is achromatic."""
134135

135-
return abs(coords[1]) < 1e-4 or coords[2] > (100 - 1e-7) or coords[2] < 1e-08
136+
return abs(coords[1]) < self.achromatic_threshold or coords[2] > (100 - 1e-7) or coords[2] < 1e-08
136137

137138
def to_base(self, coords: Vector) -> Vector:
138139
"""To LChuv from HPLuv."""

coloraide/spaces/hsl/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""HSL class."""
22
from __future__ import annotations
3+
from ... import algebra as alg
34
from ...spaces import HSLish, Space
45
from ...cat import WHITES
56
from ...channels import Channel, FLG_ANGLE
67
from ... import util
78
from ...types import Vector
9+
from typing import Any
810

911

1012
def srgb_to_hsl(rgb: Vector) -> Vector:
@@ -82,6 +84,13 @@ class HSL(HSLish, Space):
8284
GAMUT_CHECK = "srgb" # type: str | None
8385
CLIP_SPACE = "hsl" # type: str | None
8486

87+
def __init__(self, **kwargs: Any):
88+
"""Initialize."""
89+
90+
super().__init__(**kwargs)
91+
order = alg.order(self.channels[self.indexes()[2]].high)
92+
self.achromatic_threshold = util.ACHROMATIC_THRESHOLD_SM if order == 0 else util.ACHROMATIC_THRESHOLD
93+
8594
def normalize(self, coords: Vector) -> Vector:
8695
"""Normalize coordinates."""
8796

@@ -94,7 +103,11 @@ def normalize(self, coords: Vector) -> Vector:
94103
def is_achromatic(self, coords: Vector) -> bool | None:
95104
"""Check if color is achromatic."""
96105

97-
return abs(coords[1]) < 1e-4 or coords[2] == 0.0 or abs(1 - coords[2]) < 1e-7
106+
return (
107+
abs(coords[1]) < self.achromatic_threshold or
108+
coords[2] == 0.0 or
109+
abs(1 - coords[2]) < self.achromatic_threshold
110+
)
98111

99112
def to_base(self, coords: Vector) -> Vector:
100113
"""To sRGB from HSL."""

coloraide/spaces/hsluv.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
SOFTWARE.
2626
"""
2727
from __future__ import annotations
28-
from ..spaces import Space, HSLish
2928
from ..cat import WHITES
3029
from ..channels import Channel, FLG_ANGLE
30+
from .hsl import HSL
3131
from .lab import EPSILON, KAPPA
3232
from .srgb_linear import XYZ_TO_RGB
3333
import math
@@ -106,7 +106,7 @@ def luv_to_hsluv(luv: Vector) -> Vector:
106106
return [util.constrain_hue(h), s, l]
107107

108108

109-
class HSLuv(HSLish, Space):
109+
class HSLuv(HSL):
110110
"""HSLuv class."""
111111

112112
BASE = 'luv'
@@ -137,7 +137,7 @@ def normalize(self, coords: Vector) -> Vector:
137137
def is_achromatic(self, coords: Vector) -> bool:
138138
"""Check if color is achromatic."""
139139

140-
return abs(coords[1]) < 1e-4 or coords[2] > (100 - 1e-7) or coords[2] < 1e-08
140+
return abs(coords[1]) < self.achromatic_threshold or coords[2] > (100 - 1e-7) or coords[2] < 1e-08
141141

142142
def to_base(self, coords: Vector) -> Vector:
143143
"""To LChuv from HSLuv."""

0 commit comments

Comments
 (0)