Skip to content

Commit

Permalink
Ensure that interpolation is in [0, 1], fixes #624 (#660)
Browse files Browse the repository at this point in the history
* Ensure that interpolation is in [0, 1], fixes #624

* Bump to 1.17.1

* Add assert

* Interpolate via HSV space

* Add test
  • Loading branch information
basnijholt authored Jul 25, 2023
1 parent 7650a1b commit f9b6753
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
2 changes: 1 addition & 1 deletion custom_components/adaptive_lighting/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues",
"requirements": ["ulid-transform"],
"version": "1.17.0"
"version": "1.17.1"
}
28 changes: 21 additions & 7 deletions custom_components/adaptive_lighting/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import asyncio
import base64
import bisect
import colorsys
import datetime
import functools
import logging
Expand Down Expand Up @@ -1549,18 +1550,31 @@ async def async_turn_off(self, **kwargs) -> None: # noqa: ARG002
self._state = False


def lerp_color(
def lerp_color_hsv(
rgb1: tuple[int, int, int],
rgb2: tuple[int, int, int],
t: float,
) -> tuple[int, int, int]:
"""Linearly interpolate between two RGB colors."""
return (
int(rgb1[0] + t * (rgb2[0] - rgb1[0])),
int(rgb1[1] + t * (rgb2[1] - rgb1[1])),
int(rgb1[2] + t * (rgb2[2] - rgb1[2])),
"""Linearly interpolate between two RGB colors in HSV color space."""
t = abs(t)
assert 0 <= t <= 1

# Convert RGB to HSV
hsv1 = colorsys.rgb_to_hsv(*[x / 255.0 for x in rgb1])
hsv2 = colorsys.rgb_to_hsv(*[x / 255.0 for x in rgb2])

# Linear interpolation in HSV space
hsv = (
hsv1[0] + t * (hsv2[0] - hsv1[0]),
hsv1[1] + t * (hsv2[1] - hsv1[1]),
hsv1[2] + t * (hsv2[2] - hsv1[2]),
)

# Convert back to RGB
rgb = tuple(int(round(x * 255)) for x in colorsys.hsv_to_rgb(*hsv))
assert all(0 <= x <= 255 for x in rgb), f"Invalid RGB color: {rgb}"
return rgb


@dataclass(frozen=True)
class SunLightSettings:
Expand Down Expand Up @@ -1748,7 +1762,7 @@ def get_settings(
# This will result in a perceptible jump in color at sunset and sunrise
# because the `color_temperature_to_rgb` function is not 100% accurate.
min_color_rgb = color_temperature_to_rgb(self.min_color_temp)
rgb_color = lerp_color(min_color_rgb, self.sleep_rgb_color, percent)
rgb_color = lerp_color_hsv(min_color_rgb, self.sleep_rgb_color, percent)
color_temp_kelvin = self.calc_color_temp_kelvin(percent)
force_rgb_color = True
else:
Expand Down
17 changes: 17 additions & 0 deletions tests/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
create_context,
is_our_context,
is_our_context_id,
lerp_color_hsv,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -1701,3 +1702,19 @@ async def patch_time_and_update(time):
await switch._update_attrs_and_maybe_adapt_lights(context=context)
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_SLEEP_BRIGHTNESS
assert switch._settings["rgb_color"] == DEFAULT_SLEEP_RGB_COLOR


def test_lerp_color_hsv():
assert lerp_color_hsv((255, 0, 0), (0, 255, 0), 0) == (255, 0, 0)
assert lerp_color_hsv((255, 0, 0), (0, 255, 0), 1) == (0, 255, 0)
assert lerp_color_hsv((255, 0, 0), (0, 255, 0), 0.5) == (255, 255, 0)
assert lerp_color_hsv((0, 0, 255), (255, 255, 255), 0.5) == (128, 255, 128)

# Tests that the interpolation is consistent
for t in [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]:
color = lerp_color_hsv((255, 0, 0), (0, 255, 0), t)
inverted_color = lerp_color_hsv((0, 255, 0), (255, 0, 0), 1 - t)
assert color == inverted_color

with pytest.raises(AssertionError):
lerp_color_hsv((255, 0, 0), (0, 255, 0), 1.1)

0 comments on commit f9b6753

Please sign in to comment.