Ensure that interpolation is in [0, 1], fixes #624 (#660)

* Ensure that interpolation is in [0, 1], fixes #624

* Bump to 1.17.1

* Add assert

* Interpolate via HSV space

* Add test
This commit is contained in:
Bas Nijholt 2023-07-25 10:46:34 -07:00 committed by GitHub
commit f9b6753e6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 8 deletions

View file

@ -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"
}

View file

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
import base64
import bisect
import colorsys
import datetime
import functools
import logging
@ -1549,18 +1550,31 @@ class SimpleSwitch(SwitchEntity, RestoreEntity):
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:
@ -1748,7 +1762,7 @@ class SunLightSettings:
# 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:

View file

@ -92,6 +92,7 @@ from custom_components.adaptive_lighting.switch import (
create_context,
is_our_context,
is_our_context_id,
lerp_color_hsv,
)
_LOGGER = logging.getLogger(__name__)
@ -1701,3 +1702,19 @@ async def test_adapt_until_sleep_and_rgb_colors(hass):
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)