Skip to content

Commit

Permalink
Merge pull request #171 from facelessuser/bugfix/steps-optimize
Browse files Browse the repository at this point in the history
Steps was reducing ∆E too much with max_delta_e
  • Loading branch information
facelessuser authored May 14, 2022
2 parents 69ca704 + f47d910 commit 2fecf92
Show file tree
Hide file tree
Showing 11 changed files with 39 additions and 49 deletions.
2 changes: 1 addition & 1 deletion coloraide/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,5 @@ def parse_version(ver: str) -> Version:
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(0, 18, 0, "final")
__version_info__ = Version(0, 18, 1, "final")
__version__ = __version_info__._get_canonical()
57 changes: 18 additions & 39 deletions coloraide/interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
Original Authors: Lea Verou, Chris Lilley
License: MIT (As noted in https://github.com/LeaVerou/color.js/blob/master/package.json)
"""
import math
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from . import algebra as alg
from .types import Vector
from .spaces import Cylindrical
from .gamut.bounds import FLG_ANGLE
from typing import Optional, Callable, Sequence, Mapping, Type, Dict, List, Any, Union, cast, TYPE_CHECKING
from typing import Optional, Callable, Sequence, Mapping, Type, Dict, List, Union, cast, TYPE_CHECKING
from .types import ColorInput

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -66,10 +65,6 @@ class Interpolator(metaclass=ABCMeta):
def __init__(self) -> None:
"""Initialize."""

@abstractmethod
def get_delta(self, method: Optional[str]) -> Any:
"""Get the delta."""

@abstractmethod
def __call__(self, p: float) -> 'Color':
"""Call the interpolator."""
Expand Down Expand Up @@ -111,14 +106,6 @@ def __init__(
self.outspace = outspace
self.premultiplied = premultiplied

def get_delta(self, method: Optional[str]) -> float:
"""Get the delta."""

return self.create(self.space, self.channels1[:-1]).delta_e(
self.create(self.space, self.channels2[:-1]),
method=method
)

def __call__(self, p: float) -> 'Color':
"""Run through the coordinates and run the interpolation on them."""

Expand Down Expand Up @@ -160,11 +147,6 @@ def __init__(self, stops: Dict[int, float], interpolators: List[InterpolateSingl
self.stops = stops
self.interpolators = interpolators

def get_delta(self, method: Optional[str]) -> Vector:
"""Get the delta total."""

return [i.get_delta(method) for i in self.interpolators]

def __call__(self, p: float) -> 'Color':
"""Interpolate."""

Expand Down Expand Up @@ -354,23 +336,20 @@ def color_steps(
) -> List['Color']:
"""Color steps."""

if max_delta_e <= 0:
actual_steps = steps
else:
actual_steps = 0
deltas = interpolator.get_delta(delta_e)
if not isinstance(deltas, Sequence):
deltas = [deltas]
# Make a very rough guess of required steps.
actual_steps = max(steps, sum([math.ceil(d / max_delta_e) + 1 for d in deltas]))
actual_steps = steps

# Allocate at least two steps if we are doing a maximum delta E,
if max_delta_e != 0 and actual_steps < 2:
actual_steps = 2

# Make sure we don't start out allocating too many colors
if max_steps is not None:
actual_steps = min(actual_steps, max_steps)

ret = []
if actual_steps == 1:
ret = [{"p": 0.5, "color": interpolator(0.5)}]
else:
elif actual_steps > 1:
step = 1 / (actual_steps - 1)
for i in range(actual_steps):
p = i * step
Expand All @@ -382,13 +361,11 @@ def color_steps(
if max_delta_e > 0:
# Initial check to see if we need to insert more stops
m_delta = 0.0
for i, entry in enumerate(ret):
if i == 0:
continue
for i in range(1, len(ret)):
m_delta = max(
m_delta,
cast('Color', entry['color']).delta_e(
cast('Color', ret[i - 1]['color']),
cast('Color', ret[i - 1]['color']).delta_e(
cast('Color', ret[i]['color']),
method=delta_e
)
)
Expand All @@ -399,18 +376,20 @@ def color_steps(
# Inject stops while measuring again to see if it was sufficient
m_delta = 0.0
i = 1
while i < len(ret):
prev = ret[i - 1]
cur = ret[i]
offset = 0
for i in range(1, len(ret)):
index = i + offset
prev = ret[index - 1]
cur = ret[index]
p = (cast(float, cur['p']) + cast(float, prev['p'])) / 2
color = interpolator(p)
m_delta = max(
m_delta,
color.delta_e(cast('Color', prev['color']), method=delta_e),
color.delta_e(cast('Color', cur['color']), method=delta_e)
)
ret.insert(i, {'p': p, 'color': color})
i += 2
ret.insert(index, {'p': p, 'color': color})
offset += 1

return [cast('Color', i['color']) for i in ret]

Expand Down
5 changes: 5 additions & 0 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.18.1

- **FIX**: Fix issue where when generating steps with a `max_delta_e`, the ∆E was reduced too much causing additional,
unnecessary steps along with longer processing time.

## 0.18.0

- **NEW**: Allow dictionary input to use aliases in the dictionary.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ markdown_extensions:
extra_css:
- assets/coloraide-extras/extra.css
extra_javascript:
- playground-config-18a6b090.js
- playground-config-5174321b.js
- https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js
- assets/coloraide-extras/extra-notebook.js

Expand Down
2 changes: 1 addition & 1 deletion docs/src/py/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def get_colors(result):
elif isinstance(result, Color):
colors.append(ColorTuple(result.to_string(fit=False), result.clone()))
elif isinstance(result, Interpolator):
colors = HtmlGradient(result.steps(steps=5, max_delta_e=3))
colors = HtmlGradient(result.steps(steps=5, max_delta_e=2.3))
elif isinstance(result, str):
try:
colors.append(ColorTuple(result, Color2(result)))
Expand Down
2 changes: 2 additions & 0 deletions docs/theme/assets/coloraide-extras/extra-notebook-07d42fe2.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions docs/theme/assets/coloraide-extras/extra-notebook-fe68fb4e.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var color_notebook = {
"playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-0.18.0-py3-none-any.whl'],
"notebook_wheels": ['Markdown-3.3.4-py3-none-any.whl', 'pymdown_extensions-9.4-py3-none-any.whl', 'Pygments-2.12.0-py3-none-any.whl', 'coloraide-0.18.0-py3-none-any.whl'],
"playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-0.18.1-py3-none-any.whl'],
"notebook_wheels": ['Markdown-3.3.4-py3-none-any.whl', 'pymdown_extensions-9.4-py3-none-any.whl', 'Pygments-2.12.0-py3-none-any.whl', 'coloraide-0.18.1-py3-none-any.whl'],
"default_playground": "import coloraide\ncoloraide.__version__\nColor('red')"
}
4 changes: 2 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ markdown_extensions:
extra_css:
- assets/coloraide-extras/extra-753860a807.css
extra_javascript:
- playground-config-18a6b090.js
- playground-config-5174321b.js
- https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js
- assets/coloraide-extras/extra-notebook-fe68fb4e.js
- assets/coloraide-extras/extra-notebook-07d42fe2.js

extra:
social:
Expand Down
6 changes: 6 additions & 0 deletions tests/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,12 @@ def test_steps_max_delta_e(self):
continue
self.assertTrue(color.delta_e(colors[index - 1]) <= 3)

def test_max_delta_min_step_less_than_two(self):
"""Test that when a minimum step less than 2 is given that `max_delta_e` won't break."""

colors = Color('lightblue').steps('blue', space="srgb", steps=1, max_delta_e=10)
self.assertTrue(len(colors) > 2)

def test_steps_max_delta_e_steps(self):
"""Test steps with a max delta e."""

Expand Down

0 comments on commit 2fecf92

Please sign in to comment.