Skip to content

Commit

Permalink
Merge pull request #24 from facelessuser/chore/refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
facelessuser authored Mar 7, 2021
2 parents 00a39d2 + 845e831 commit 35ffbfe
Show file tree
Hide file tree
Showing 32 changed files with 349 additions and 273 deletions.
3 changes: 2 additions & 1 deletion coloraide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
from .__meta__ import __version_info__, __version__ # noqa: F401
from .css import Color
from .colors import ColorMatch
from .util import NaN

__all__ = ("Color", "ColorMatch")
__all__ = ("Color", "ColorMatch", "NaN")
2 changes: 1 addition & 1 deletion coloraide/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,5 @@ def parse_version(ver):
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(0, 1, 0, "alpha", 3)
__version_info__ = Version(0, 1, 0, "alpha", 4)
__version__ = __version_info__._get_canonical()
29 changes: 7 additions & 22 deletions coloraide/colors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from .rec2020 import Rec2020
from .xyz import XYZ
from .. import util
from ._cylindrical import Cylindrical
import functools

DEF_FIT = "lch-chroma"
Expand Down Expand Up @@ -54,33 +53,19 @@ class Color:

CS_MAP = {obj.space(): obj for obj in SUPPORTED}

PRECISION = util.DEF_PREC
FIT = util.DEF_FIT
DELTA_E = util.DEF_DELTA_E

def __init__(self, color, data=None, alpha=util.DEF_ALPHA, *, filters=None, **kwargs):
"""Initialize."""

self.defaults = {
"fit": DEF_FIT,
"delta-e": DEF_DELTA_E
}
self._attach(self._parse(color, data, alpha, filters=filters, **kwargs))

def is_hue_null(self, space=None):
"""Check if hue is treated as null."""

if space is None:
space = self.space()
else:
space = space.lower()

this = self if self.space() == space else self.convert(space)
if isinstance(this._color, Cylindrical):
return this._color.is_hue_null()
else:
return False

def get_default(self, name):
"""Get default."""
def is_nan(self, name):
"""Check if channel is NaN."""

return self.defaults[name]
return util.is_nan(self.get(name))

def _attach(self, color):
"""Attach the this objects convert space to the color."""
Expand Down
4 changes: 3 additions & 1 deletion coloraide/colors/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Convert:
def _constrain_hue(cls, hue):
"""Constrain hue to 0 - 360."""

return hue % 360
return hue % 360 if not util.is_nan(hue) else hue

@classmethod
def _chromatic_adaption(cls, w1, w2, xyz):
Expand Down Expand Up @@ -110,6 +110,7 @@ def update(self, obj):
"""Update from color."""

if self is obj:
obj._coords = obj.null_adjust(obj._coords)
return

if not isinstance(obj, type(self)):
Expand All @@ -118,4 +119,5 @@ def update(self, obj):
for i, value in enumerate(obj.coords()):
self._coords[i] = value
self.alpha = obj.alpha
self._coords = self.null_adjust(self._coords)
return self
5 changes: 0 additions & 5 deletions coloraide/colors/_cylindrical.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
class Cylindrical:
"""Cylindrical space."""

def is_hue_null(self):
"""Test if hue is null."""

raise NotImplementedError("Base 'CylindricalSpace' class does not impolement 'is_hue_null'")

def hue_name(self):
"""Hue channel name."""

Expand Down
19 changes: 10 additions & 9 deletions coloraide/colors/_distance.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
from .. import util

G_CONST = math.pow(25, 7)
RAD2DEG = 180 / math.pi
Expand All @@ -13,8 +14,8 @@ def distance_euclidean(color1, color2, space="lab", **kwargs):
lab1 = color1.convert(space)
lab2 = color2.convert(space)

coords1 = lab1.coords()
coords2 = lab2.coords()
coords1 = util.no_nan(lab1.coords())
coords2 = util.no_nan(lab2.coords())

total = 0
for i, coord in enumerate(coords1):
Expand Down Expand Up @@ -42,9 +43,9 @@ def delta_e_94(color1, color2, kl=1, k1=0.045, k2=0.015):
lab1 = color1.convert("lab")
lab2 = color2.convert("lab")

l1, a1, b1 = lab1.coords()
l1, a1, b1 = util.no_nan(lab1.coords())
c1 = math.sqrt(math.pow(a1, 2) + math.pow(b1, 2))
l2, a2, b2 = lab2.coords()
l2, a2, b2 = util.no_nan(lab2.coords())
c2 = math.sqrt(math.pow(a2, 2) + math.pow(b2, 2))

dl = l1 - l2
Expand Down Expand Up @@ -83,9 +84,9 @@ def delta_e_cmc(color1, color2, l=2, c=1):
lab1 = color1.convert("lab")
lab2 = color2.convert("lab")

l1, a1, b1 = lab1.coords()
l1, a1, b1 = util.no_nan(lab1.coords())
c1 = math.sqrt(math.pow(a1, 2) + math.pow(b1, 2))
l2, a2, b2 = lab2.coords()
l2, a2, b2 = util.no_nan(lab2.coords())
c2 = math.sqrt(math.pow(a2, 2) + math.pow(b2, 2))

dl = l1 - l2
Expand Down Expand Up @@ -145,9 +146,9 @@ def delta_e_2000(color1, color2, kl=1, kc=1, kh=1, **kwargs):
lab1 = color1.convert("lab")
lab2 = color2.convert("lab")

l1, a1, b1 = lab1.coords()
l1, a1, b1 = util.no_nan(lab1.coords())
c1 = math.sqrt(math.pow(a1, 2) + math.pow(b1, 2))
l2, a2, b2 = lab2.coords()
l2, a2, b2 = util.no_nan(lab2.coords())
c2 = math.sqrt(math.pow(a2, 2) + math.pow(b2, 2))

cm = (c1 + c2) / 2
Expand Down Expand Up @@ -229,7 +230,7 @@ def delta_e(self, color, method=None, **kwargs):
"""Delta E distance."""

if method is None:
method = self.get_default("delta-e")
method = self.parent.DELTA_E

algorithm = method.lower()
if algorithm not in SUPPORTED:
Expand Down
8 changes: 4 additions & 4 deletions coloraide/colors/_gamut.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def lch_chroma(base, color):
def clip(base, color):
"""Gamut clipping."""

channels = color.coords()
channels = util.no_nan(color.coords())
gamut = color._range
fit = []

Expand All @@ -97,7 +97,7 @@ def clip(base, color):
def norm_angles(color):
"""Normalize angles."""

channels = color.coords()
channels = util.no_nan(color.coords())
gamut = color._range
fit = []
for i, value in enumerate(channels):
Expand All @@ -120,7 +120,7 @@ def fit_coords(self, space=None, *, method=None):
"""Get coordinates within this space or fit to another space."""

if method is None:
method = self.get_default("fit")
method = self.parent.FIT

space = (self.space() if space is None else space).lower()
method = self.space() if method is None else method
Expand All @@ -134,7 +134,7 @@ def fit(self, space=None, *, method=None, in_place=False):
"""Fit the gamut using the provided method."""

if method is None:
method = self.get_default("fit")
method = self.parent.FIT

this = self if in_place else self.clone()

Expand Down
21 changes: 8 additions & 13 deletions coloraide/colors/_interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
def overlay(c1, c2, a1, a2, a0):
"""Overlay one color channel over the other."""

if math.isnan(c1) and math.isnan(c2):
if util.is_nan(c1) and util.is_nan(c2):
return 0.0
elif math.isnan(c1):
elif util.is_nan(c1):
return c2 * a2
elif math.isnan(c2):
elif util.is_nan(c2):
return c1 * a1

c0 = c1 * a1 + c2 * a2 * (1 - a1)
Expand All @@ -39,11 +39,11 @@ def interpolate(p, coords1, coords2, create, progress, outspace, premultiplied):
coords = []
for i, c1 in enumerate(coords1):
c2 = coords2[i]
if math.isnan(c1) and math.isnan(c2):
if util.is_nan(c1) and util.is_nan(c2):
value = 0.0
elif math.isnan(c1):
elif util.is_nan(c1):
value = c2
elif math.isnan(c2):
elif util.is_nan(c2):
value = c1
else:
value = c1 + (c2 - c1) * (p if progress is None else progress(p))
Expand All @@ -63,17 +63,12 @@ def prepare_coords(color, adjust=None):
then we need to set all other channels to NaN.
"""

if isinstance(color, Cylindrical):
if color.is_hue_null():
name = color.hue_name()
color.set(name, util.NAN)

if adjust:
to_adjust = adjust & color.CHANNEL_NAMES
to_avoid = color.CHANNEL_NAMES - adjust
if to_adjust:
for channel in to_avoid:
color.set(channel, util.NAN)
color.set(channel, util.NaN)


def postdivide(color):
Expand Down Expand Up @@ -132,7 +127,7 @@ def adjust_hues(color1, color2, hue):
c1 = c1 % 360
c2 = c2 % 360

if math.isnan(c1) or math.isnan(c2):
if util.is_nan(c1) or util.is_nan(c2):
color1.set(name, c1)
color2.set(name, c2)
return
Expand Down
36 changes: 23 additions & 13 deletions coloraide/colors/_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def split_channels(cls, color):
diff = cls.NUM_COLOR_CHANNELS - len(channels)
channels.extend([0.0] * diff)
channels.append(alpha if alpha is not None else 1.0)
return channels
return cls.null_adjust(channels)


class Space(contrast.Contrast, interpolate.Interpolate, distance.Distance, gamut.Gamut, convert.Convert):
Expand Down Expand Up @@ -94,8 +94,8 @@ def __repr__(self):

return 'color({} {} / {})'.format(
self.space(),
' '.join([util.fmt_float(c, util.DEF_PREC) for c in self.coords()]),
util.fmt_float(self.alpha, util.DEF_PREC)
' '.join([util.fmt_float(c, util.DEF_PREC) for c in util.no_nan(self.coords())]),
util.fmt_float(util.no_nan(self.alpha), util.DEF_PREC)
)

__str__ = __repr__
Expand All @@ -105,12 +105,7 @@ def _handle_input(self, value):

if not util.is_number(value):
raise TypeError("Value should be a number not type '{}'".format(type(value)))
return float(value)

def get_default(self, name):
"""Get default."""

return self.parent.get_default(name)
return float(value) if not util.is_nan(value) else value

def coords(self):
"""Coordinates."""
Expand Down Expand Up @@ -153,6 +148,11 @@ def alpha(self, value):

self._alpha = util.clamp(self._handle_input(value), 0.0, 1.0)

def is_nan(self, name):
"""Check if the channel is NaN."""

return util.is_nan(self.get(name))

def set(self, name, value): # noqa: A003
"""Set the given channel."""

Expand All @@ -170,24 +170,34 @@ def get(self, name):
return getattr(self, name)

def to_string(
self, *, alpha=None, precision=util.DEF_PREC, fit=True, **kwargs
self, *, alpha=None, precision=None, fit=True, **kwargs
):
"""Convert to CSS 'color' string: `color(space coords+ / alpha)`."""

alpha = alpha is not False and (alpha is True or self.alpha < 1.0)
if precision is None:
precision = self.parent.PRECISION

coords = self.fit_coords() if fit else self.coords()
a = util.no_nan(self.alpha)
alpha = alpha is not False and (alpha is True or a < 1.0)

coords = util.no_nan(self.fit_coords() if fit else self.coords())
template = "color({} {} {} {} / {})" if alpha else "color({} {} {} {})"
values = [
util.fmt_float(coords[0], precision),
util.fmt_float(coords[1], precision),
util.fmt_float(coords[2], precision)
]
if alpha:
values.append(util.fmt_float(self.alpha, max(precision, util.DEF_PREC)))
values.append(util.fmt_float(a, max(precision, util.DEF_PREC)))

return template.format(self.space(), *values)

@classmethod
def null_adjust(cls, coords):
"""Process coordinates and adjust any channels to null/NaN if required."""

return coords

@classmethod
def translate_channel(cls, channel, value):
"""Set a non-alpha color channel."""
Expand Down
Loading

0 comments on commit 35ffbfe

Please sign in to comment.