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

Precision can be controlled per channel #438

Merged
merged 2 commits into from
Sep 16, 2024
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
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
Loading