Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSS Linear mode #367

Merged
merged 5 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion coloraide/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(2, 10, 0, "final")
__version_info__ = Version(2, 11, 0, "final")
__version__ = __version_info__._get_canonical()
7 changes: 5 additions & 2 deletions coloraide/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from .filters.cvd import Protan, Deutan, Tritan
from .interpolate import Interpolator, Interpolate
from .interpolate.linear import Linear
from .interpolate.css_linear import CSSLinear
from .interpolate.continuous import Continuous
from .interpolate.bspline import BSpline
from .interpolate.bspline_natural import NaturalBSpline
Expand Down Expand Up @@ -140,6 +141,7 @@ class Color(metaclass=ColorMeta):
PRECISION = util.DEF_PREC
FIT = util.DEF_FIT
INTERPOLATE = util.DEF_INTERPOLATE
INTERPOLATOR = util.DEF_INTERPOLATOR
DELTA_E = util.DEF_DELTA_E
HARMONY = util.DEF_HARMONY
AVERAGE = util.DEF_AVERAGE
Expand Down Expand Up @@ -1030,7 +1032,7 @@ def interpolate(
premultiplied: bool = True,
extrapolate: bool = False,
domain: list[float] | None = None,
method: str = "linear",
method: str | None = None,
padding: float | tuple[float, float] | None = None,
carryforward: bool | None = None,
powerless: bool | None = None,
Expand All @@ -1049,7 +1051,7 @@ def interpolate(
"""

return interpolate.interpolator(
method,
method if method is not None else cls.INTERPOLATOR,
cls,
colors=colors,
space=space,
Expand Down Expand Up @@ -1382,6 +1384,7 @@ def alpha(self, *, nans: bool = True) -> float:

# Interpolation
Linear(),
CSSLinear(),
Continuous(),
BSpline(),
NaturalBSpline(),
Expand Down
114 changes: 6 additions & 108 deletions coloraide/interpolate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import math
import functools
from abc import ABCMeta, abstractmethod
from .. import util
from .. import algebra as alg
from .. spaces import HSVish, HSLish, Cylindrical, RGBish, LChish, Labish
from ..types import Vector, ColorInput, Plugin
Expand Down Expand Up @@ -83,6 +82,7 @@ def __init__(
extrapolate: bool = False,
domain: Sequence[float] | None = None,
padding: float | tuple[float, float] | None = None,
hue: str = 'shorter',
**kwargs: Any
):
"""Initialize."""
Expand All @@ -100,6 +100,7 @@ def __init__(
self._out_space = out_space
self.extrapolate = extrapolate
self.current_easing = None # type: Mapping[str, Callable[..., float]] | Callable[..., float] | None
self.hue = hue
cs = self.create.CS_MAP[space]
if isinstance(cs, Cylindrical):
self.hue_index = cs.hue_index()
Expand Down Expand Up @@ -469,6 +470,7 @@ def interpolator(
extrapolate: bool = False,
domain: list[float] | None = None,
padding: float | tuple[float, float] | None = None,
hue: str = 'shorter',
**kwargs: Any
) -> Interpolator:
"""Get the interpolator object."""
Expand Down Expand Up @@ -550,93 +552,6 @@ def process_mapping(
return {aliases.get(k, k): v for k, v in progress.items()}


def adjust_shorter(h1: float, h2: float, offset: float) -> tuple[float, float]:
"""Adjust the given hues."""

d = h2 - h1
if d > 180:
h2 -= 360.0
offset -= 360.0
elif d < -180:
h2 += 360
offset += 360.0
return h2, offset


def adjust_longer(h1: float, h2: float, offset: float) -> tuple[float, float]:
"""Adjust the given hues."""

d = h2 - h1
if 0 < d < 180:
h2 -= 360.0
offset -= 360.0
elif -180 < d <= 0:
h2 += 360
offset += 360.0
return h2, offset


def adjust_increase(h1: float, h2: float, offset: float) -> tuple[float, float]:
"""Adjust the given hues."""

if h2 < h1:
h2 += 360.0
offset += 360.0
return h2, offset


def adjust_decrease(h1: float, h2: float, offset: float) -> tuple[float, float]:
"""Adjust the given hues."""

if h2 > h1:
h2 -= 360.0
offset -= 360.0
return h2, offset


def normalize_hue(
color1: Vector,
color2: Vector | None,
index: int,
offset: float,
hue: str,
fallback: float | None
) -> tuple[Vector, float]:
"""Normalize hues according the hue specifier."""

if hue == 'specified':
return (color2 or color1), offset

# Probably the first hue
if color2 is None:
color1[index] = util.constrain_hue(color1[index])
return color1, offset

if hue == 'shorter':
adjuster = adjust_shorter
elif hue == 'longer':
adjuster = adjust_longer
elif hue == 'increasing':
adjuster = adjust_increase
elif hue == 'decreasing':
adjuster = adjust_decrease
else:
raise ValueError("Unknown hue adjuster '{}'".format(hue))

c1 = color1[index] + offset
c2 = util.constrain_hue(color2[index]) + offset

# Adjust hue, handle gaps across `NaN`s
if not math.isnan(c2):
if not math.isnan(c1):
c2, offset = adjuster(c1, c2, offset)
elif fallback is not None:
c2, offset = adjuster(fallback, c2, offset)

color2[index] = c2
return color2, offset


def carryforward_convert(color: Color, space: str, hue_index: int, powerless: bool) -> None: # pragma: no cover
"""Carry forward undefined values during conversion."""

Expand Down Expand Up @@ -776,19 +691,9 @@ def interpolator(
elif powerless and is_cyl and current.is_achromatic():
current[hue_index] = math.nan

# Normalize hue
offset = 0.0
norm_coords = current[:]
fallback = None
if hue_index >= 0:
h = norm_coords[hue_index]
norm_coords, offset = normalize_hue(norm_coords, None, hue_index, offset, hue, fallback)
if not math.isnan(h):
fallback = h

easing = None # type: Any
easings = [] # type: Any
coords = [norm_coords]
coords = [current[:]]

i = 0
for x in colors[1:]:
Expand All @@ -814,16 +719,8 @@ def interpolator(
elif powerless and is_cyl and color.is_achromatic():
color[hue_index] = math.nan

# Normalize the hue
norm_coords = color[:]
if hue_index >= 0:
h = norm_coords[hue_index]
norm_coords, offset = normalize_hue(current[:], norm_coords, hue_index, offset, hue, fallback)
if not math.isnan(h):
fallback = h

# Create an entry interpolating the current color and the next color
coords.append(norm_coords)
coords.append(color[:])
easings.append(easing if easing is not None else progress)

# The "next" color is now the "current" color
Expand All @@ -836,6 +733,7 @@ def interpolator(

# Calculate stops
stops = calc_stops(stops, i)
kwargs['hue'] = hue

# Send the interpolation list along with the stop map to the Piecewise interpolator
return plugin.interpolator(
Expand Down
38 changes: 3 additions & 35 deletions coloraide/interpolate/bspline.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
from .continuous import InterpolatorContinuous
from ..interpolate import Interpolator, Interpolate
from ..types import Vector
from typing import Callable, Mapping, Sequence, Any, TYPE_CHECKING

if TYPE_CHECKING: # pragma: no cover
from ..color import Color
from typing import Any


class InterpolatorBSpline(InterpolatorContinuous):
Expand Down Expand Up @@ -80,36 +77,7 @@ class BSpline(Interpolate):

NAME = "bspline"

def interpolator(
self,
coordinates: list[Vector],
channel_names: Sequence[str],
create: type[Color],
easings: list[Callable[..., float] | None],
stops: dict[int, float],
space: str,
out_space: str,
progress: Mapping[str, Callable[..., float]] | Callable[..., float] | None,
premultiplied: bool,
extrapolate: bool = False,
domain: list[float] | None = None,
padding: float | tuple[float, float] | None = None,
**kwargs: Any
) -> Interpolator:
def interpolator(self, *args: Any, **kwargs: Any) -> Interpolator:
"""Return the B-spline interpolator."""

return InterpolatorBSpline(
coordinates,
channel_names,
create,
easings,
stops,
space,
out_space,
progress,
premultiplied,
extrapolate,
domain,
padding,
**kwargs
)
return InterpolatorBSpline(*args, **kwargs)
39 changes: 3 additions & 36 deletions coloraide/interpolate/bspline_natural.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
from .. interpolate import Interpolate, Interpolator
from .bspline import InterpolatorBSpline
from .. import algebra as alg
from ..types import Vector
from typing import List, Sequence, Any, Mapping, Callable, Dict, Type, TYPE_CHECKING

if TYPE_CHECKING: # pragma: no cover
from ..color import Color
from typing import Any


class InterpolatorNaturalBSpline(InterpolatorBSpline):
Expand Down Expand Up @@ -39,36 +35,7 @@ class NaturalBSpline(Interpolate):

NAME = "natural"

def interpolator(
self,
coordinates: List[Vector],
channel_names: Sequence[str],
create: Type[Color],
easings: List[Callable[..., float] | None],
stops: Dict[int, float],
space: str,
out_space: str,
progress: Mapping[str, Callable[..., float]] | Callable[..., float] | None,
premultiplied: bool,
extrapolate: bool = False,
domain: list[float] | None = None,
padding: float | tuple[float, float] | None = None,
**kwargs: Any
) -> Interpolator:
def interpolator(self, *args: Any, **kwargs: Any) -> Interpolator:
"""Return the natural B-spline interpolator."""

return InterpolatorNaturalBSpline(
coordinates,
channel_names,
create,
easings,
stops,
space,
out_space,
progress,
premultiplied,
extrapolate,
domain,
padding,
**kwargs
)
return InterpolatorNaturalBSpline(*args, **kwargs)
39 changes: 3 additions & 36 deletions coloraide/interpolate/catmull_rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
from .bspline import InterpolatorBSpline
from ..interpolate import Interpolator, Interpolate
from .. import algebra as alg
from ..types import Vector
from typing import Callable, Mapping, Sequence, Any, TYPE_CHECKING

if TYPE_CHECKING: # pragma: no cover
from ..color import Color
from typing import Any


class InterpolatorCatmullRom(InterpolatorBSpline):
Expand All @@ -29,36 +25,7 @@ class CatmullRom(Interpolate):

NAME = "catrom"

def interpolator(
self,
coordinates: list[Vector],
channel_names: Sequence[str],
create: type[Color],
easings: list[Callable[..., float] | None],
stops: dict[int, float],
space: str,
out_space: str,
progress: Mapping[str, Callable[..., float]] | Callable[..., float] | None,
premultiplied: bool,
extrapolate: bool = False,
domain: list[float] | None = None,
padding: float | tuple[float, float] | None = None,
**kwargs: Any
) -> Interpolator:
def interpolator(self, *args: Any, **kwargs: Any) -> Interpolator:
"""Return the Catmull-Rom interpolator."""

return InterpolatorCatmullRom(
coordinates,
channel_names,
create,
easings,
stops,
space,
out_space,
progress,
premultiplied,
extrapolate,
domain,
padding,
**kwargs
)
return InterpolatorCatmullRom(*args, **kwargs)
Loading