Skip to content

Commit

Permalink
Precision can be controlled per channel (#438)
Browse files Browse the repository at this point in the history
* Precision can be controlled per channel

This reverts the precision_alpha change made in a previous commit for a
more flexible approach.

* Fix lint
  • Loading branch information
facelessuser authored Sep 16, 2024
1 parent 658d78d commit c09c794
Show file tree
Hide file tree
Showing 18 changed files with 158 additions and 88 deletions.
78 changes: 54 additions & 24 deletions coloraide/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def __init__(
def __len__(self) -> int:
"""Get number of channels."""

return len(self._space.CHANNELS) + 1
return len(self._space.channels)

@overload
def __getitem__(self, i: str | int) -> float:
Expand Down Expand Up @@ -531,14 +531,14 @@ def to_dict(
self,
*,
nans: bool = True,
precision: int | None = None,
precision_alpha: int | None = None
precision: int | Sequence[int] | None = None
) -> Mapping[str, Any]:
"""Return color as a data object."""

# Assume precision of other coordinates for alpha if only precision is specified
if precision is not None and precision_alpha is None:
if precision is None or isinstance(precision, int):
precision_alpha = precision
else:
precision_alpha = util.get_index(precision, len(self._space.channels) - 1, self.PRECISION)

return {
'space': self.space(),
Expand Down Expand Up @@ -1293,58 +1293,72 @@ def contrast(self, color: ColorInput, method: str | None = None) -> float:
return contrast.contrast(method, self, color)

@overload
def get(self, name: str, *, nans: bool = True, precision: int | None = None) -> float:
def get(self,
name: str,
*,
nans: bool = True,
precision: int | Sequence[int] | None = None
) -> float:
...

@overload
def get(self, name: list[str] | tuple[str, ...], *, nans: bool = True, precision: int | None = None) -> Vector:
def get(
self,
name: list[str] | tuple[str, ...],
*,
nans: bool = True,
precision: int | Sequence[int] | None = None
) -> Vector:
...

def get(
self, name: str | list[str] | tuple[str, ...],
*,
nans: bool = True,
precision: int | None = None
precision: int | Sequence[int] | None = None
) -> float | Vector:
"""Get channel."""

is_plist = precision is not None and not isinstance(precision, int)

# Handle single channel
if isinstance(name, str):
# Handle space.channel
if '.' in name:
space, channel = name.split('.', 1)
obj = self.convert(space, norm=nans)
if nans:
v = self.convert(space)[channel]
v = obj[channel]
else:
obj = self.convert(space, norm=nans)
i = obj._space.get_channel_index(channel)
v = obj._space.resolve_channel(i, obj._coords)
elif nans:
v = self[name]
else:
i = self._space.get_channel_index(name)
v = self._space.resolve_channel(i, self._coords)
return v if precision is None else alg.round_to(v, precision)
v = self._space.resolve_channel(self._space.get_channel_index(name), self._coords)
return v if precision is None else alg.round_to(v, util.get_index(precision, 0) if is_plist else precision) # type: ignore[arg-type]

# Handle list of channels
else:
original_space = current_space = self.space()
obj = self
values = []

for n in name:
for e, n in enumerate(name):
# Handle space.channel
space, channel = n.split('.', 1) if '.' in n else (original_space, n)
if space != current_space:
obj = self if space == original_space else self.convert(space, norm=nans)
current_space = space
if nans:
v = obj[channel]
values.append(v if precision is None else alg.round_to(v, precision))
else:
i = obj._space.get_channel_index(channel)
v = obj._space.resolve_channel(i, obj._coords)
values.append(v if precision is None else alg.round_to(v, precision))
values.append(
v if precision is None else alg.round_to(v, util.get_index(precision, e) if is_plist else precision) # type: ignore[arg-type]
)
return values

def set( # noqa: A003
Expand Down Expand Up @@ -1406,22 +1420,38 @@ def set( # noqa: A003

return self

def coords(self, *, nans: bool = True, precision: int | None = None) -> Vector:
def coords(self, *, nans: bool = True, precision: int | Sequence[int] | None = None) -> Vector:
"""Get the color channels and optionally remove undefined values."""

if nans:
value = self[:-1]
# Full precision
if precision is None:
if nans:
return self[:-1]
else:
return [
self._space.resolve_channel(index, self._coords)
for index in range(len(self._coords) - 1)
]
# Specific precision requested
elif isinstance(precision, int):
return [
alg.round_to(self[index] if nans else self._space.resolve_channel(index, self._coords), precision)
for index in range(len(self._coords) - 1)
]
# Channel specific list of precision
else:
value = [self._space.resolve_channel(index, self._coords) for index in range(len(self._coords) - 1)]
return value if precision is None else [alg.round_to(v, precision) for v in value]
return [
alg.round_to(
self[index] if nans else self._space.resolve_channel(index, self._coords),
util.get_index(precision, index, self.PRECISION)
)
for index in range(len(self._coords) - 1)
]

def alpha(self, *, nans: bool = True, precision: int | None = None) -> float:
"""Get the alpha channel."""

if nans:
value = self[-1]
else:
value = self._space.resolve_channel(-1, self._coords)
value = self[-1] if nans else self._space.resolve_channel(-1, self._coords)
return value if precision is None else alg.round_to(value, precision)


Expand Down
25 changes: 10 additions & 15 deletions coloraide/css/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ def color_function(
obj: Color,
func: str | None,
alpha: bool | None,
precision: int,
precision_alpha: int,
precision: int | Sequence[int],
fit: str | bool | dict[str, Any],
none: bool,
percent: bool | Sequence[bool],
Expand Down Expand Up @@ -70,10 +69,10 @@ def color_function(
# - A list of booleans will attempt formatting the associated channel as percent,
# anything not specified is assumed `False`.
if isinstance(percent, bool):
plist = obj._space._percents if percent else []
else:
diff = l - len(percent)
plist = list(percent) + ([False] * diff) if diff > 0 else list(percent)
percent = obj._space._percents if percent else []

# Ensure precision list is filled
is_precision_list = not isinstance(precision, int)

# Iterate the coordinates formatting them by scaling the values, formatting for percent, etc.
for idx, value in enumerate(coords):
Expand All @@ -84,7 +83,7 @@ def color_function(
string.append(COMMA if legacy else SPACE)
channel = channels[idx]

if not (channel.flags & FLG_ANGLE) and plist and plist[idx]:
if not (channel.flags & FLG_ANGLE) and percent and util.get_index(percent, idx, False):
span, offset = channel.span, channel.offset
else:
span = offset = 0.0
Expand All @@ -94,7 +93,7 @@ def color_function(
string.append(
util.fmt_float(
value,
precision_alpha if is_last else precision,
util.get_index(precision, idx, obj.PRECISION) if is_precision_list else precision, # type: ignore[arg-type]
span,
offset
)
Expand Down Expand Up @@ -178,8 +177,7 @@ def serialize_css(
func: str = '',
color: bool = False,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
percent: bool | Sequence[bool] = False,
Expand All @@ -195,12 +193,9 @@ def serialize_css(
if precision is None:
precision = obj.PRECISION

if precision_alpha is None:
precision_alpha = precision

# Color format
if color:
return color_function(obj, None, alpha, precision, precision_alpha, fit, none, percent, False, 1.0)
return color_function(obj, None, alpha, precision, fit, none, percent, False, 1.0)

# CSS color names
if name:
Expand All @@ -214,6 +209,6 @@ def serialize_css(

# Normal CSS named function format
if func:
return color_function(obj, func, alpha, precision, precision_alpha, fit, none, percent, legacy, scale)
return color_function(obj, func, alpha, precision, fit, none, percent, legacy, scale)

raise RuntimeError('Could not identify a CSS format to serialize to') # pragma: no cover
4 changes: 1 addition & 3 deletions coloraide/spaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: str | bool | dict[str, Any] = True,
none: bool = False,
percent: bool | Sequence[bool] = False,
Expand All @@ -272,7 +271,6 @@ def to_string(
color=True,
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
percent=percent
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/hsl/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -45,7 +44,6 @@ def to_string(
func='hsl',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/hwb/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -36,7 +35,6 @@ def to_string(
func='hwb',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/lab/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -33,7 +32,6 @@ def to_string(
func='lab',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/lch/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -33,7 +32,6 @@ def to_string(
func='lch',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/oklab/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -33,7 +32,6 @@ def to_string(
func='oklab',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/oklch/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -33,7 +32,6 @@ def to_string(
func='oklch',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
4 changes: 1 addition & 3 deletions coloraide/spaces/srgb/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def to_string(
parent: Color,
*,
alpha: bool | None = None,
precision: int | None = None,
precision_alpha: int | None = None,
precision: int | Sequence[int] | None = None,
fit: bool | str | dict[str, Any] = True,
none: bool = False,
color: bool = False,
Expand All @@ -38,7 +37,6 @@ def to_string(
func='rgb',
alpha=alpha,
precision=precision,
precision_alpha=precision_alpha,
fit=fit,
none=none,
color=color,
Expand Down
Loading

0 comments on commit c09c794

Please sign in to comment.