-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add different brightness ramping mechanisms (#699)
* feat: add different brightness ramping mechanisms * Rephrase * Fix curves * update images * link to other graphs * update images
- Loading branch information
1 parent
9a7cff9
commit a1cec19
Showing
7 changed files
with
534 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
"""Helper functions for the Adaptive Lighting custom components.""" | ||
|
||
from __future__ import annotations | ||
|
||
import base64 | ||
import colorsys | ||
import logging | ||
import math | ||
from typing import cast | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def clamp(value: float, minimum: float, maximum: float) -> float: | ||
"""Clamp value between minimum and maximum.""" | ||
return max(minimum, min(value, maximum)) | ||
|
||
|
||
def find_a_b(x1: float, x2: float, y1: float, y2: float) -> tuple[float, float]: | ||
"""Compute the values of 'a' and 'b' for a scaled and shifted tanh function. | ||
Given two points (x1, y1) and (x2, y2), this function calculates the coefficients 'a' and 'b' | ||
for a tanh function of the form y = 0.5 * (tanh(a * (x - b)) + 1) that passes through these points. | ||
The derivation is as follows: | ||
1. Start with the equation of the tanh function: | ||
y = 0.5 * (tanh(a * (x - b)) + 1) | ||
2. Rearrange the equation to isolate tanh: | ||
tanh(a * (x - b)) = 2*y - 1 | ||
3. Take the inverse tanh (or artanh) on both sides to solve for 'a' and 'b': | ||
a * (x - b) = artanh(2*y - 1) | ||
4. Plug in the points (x1, y1) and (x2, y2) to get two equations. | ||
Using these, we can solve for 'a' and 'b' as: | ||
a = (artanh(2*y2 - 1) - artanh(2*y1 - 1)) / (x2 - x1) | ||
b = x1 - (artanh(2*y1 - 1) / a) | ||
Parameters | ||
---------- | ||
x1 | ||
x-coordinate of the first point. | ||
x2 | ||
x-coordinate of the second point. | ||
y1 | ||
y-coordinate of the first point (should be between 0 and 1). | ||
y2 | ||
y-coordinate of the second point (should be between 0 and 1). | ||
Returns | ||
------- | ||
a | ||
Coefficient 'a' for the tanh function. | ||
b | ||
Coefficient 'b' for the tanh function. | ||
Notes | ||
----- | ||
The values of y1 and y2 should lie between 0 and 1, inclusive. | ||
""" | ||
a = (math.atanh(2 * y2 - 1) - math.atanh(2 * y1 - 1)) / (x2 - x1) | ||
b = x1 - (math.atanh(2 * y1 - 1) / a) | ||
return a, b | ||
|
||
|
||
def scaled_tanh( | ||
x: float, | ||
a: float, | ||
b: float, | ||
y_min: float = 0.0, | ||
y_max: float = 100.0, | ||
) -> float: | ||
"""Apply a scaled and shifted tanh function to a given input. | ||
This function represents a transformation of the tanh function that scales and shifts | ||
the output to lie between y_min and y_max. For values of 'x' close to 'x1' and 'x2' | ||
(used to calculate 'a' and 'b'), the output of this function will be close to 'y_min' | ||
and 'y_max', respectively. | ||
The equation of the function is as follows: | ||
y = y_min + (y_max - y_min) * 0.5 * (tanh(a * (x - b)) + 1) | ||
Parameters | ||
---------- | ||
x | ||
The input to the function. | ||
a | ||
The scale factor for the tanh function, found using 'find_a_b' function. | ||
b | ||
The shift factor for the tanh function, found using 'find_a_b' function. | ||
y_min | ||
The minimum value of the output range. Defaults to 0. | ||
y_max | ||
The maximum value of the output range. Defaults to 100. | ||
Returns | ||
------- | ||
float: The output of the function, which lies in the range [y_min, y_max]. | ||
""" | ||
return y_min + (y_max - y_min) * 0.5 * (math.tanh(a * (x - b)) + 1) | ||
|
||
|
||
def lerp_color_hsv( | ||
rgb1: tuple[float, float, float], | ||
rgb2: tuple[float, float, float], | ||
t: float, | ||
) -> tuple[int, int, int]: | ||
"""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 cast(tuple[int, int, int], rgb) | ||
|
||
|
||
def lerp(x, x1, x2, y1, y2): | ||
"""Linearly interpolate between two values.""" | ||
return y1 + (x - x1) * (y2 - y1) / (x2 - x1) | ||
|
||
|
||
def int_to_base36(num: int) -> str: | ||
"""Convert an integer to its base-36 representation using numbers and uppercase letters. | ||
Base-36 encoding uses digits 0-9 and uppercase letters A-Z, providing a case-insensitive | ||
alphanumeric representation. The function takes an integer `num` as input and returns | ||
its base-36 representation as a string. | ||
Parameters | ||
---------- | ||
num | ||
The integer to convert to base-36. | ||
Returns | ||
------- | ||
str | ||
The base-36 representation of the input integer. | ||
Examples | ||
-------- | ||
>>> num = 123456 | ||
>>> base36_num = int_to_base36(num) | ||
>>> print(base36_num) | ||
'2N9' | ||
""" | ||
alphanumeric_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
|
||
if num == 0: | ||
return alphanumeric_chars[0] | ||
|
||
base36_str = "" | ||
base = len(alphanumeric_chars) | ||
|
||
while num: | ||
num, remainder = divmod(num, base) | ||
base36_str = alphanumeric_chars[remainder] + base36_str | ||
|
||
return base36_str | ||
|
||
|
||
def short_hash(string: str, length: int = 4) -> str: | ||
"""Create a hash of 'string' with length 'length'.""" | ||
return base64.b32encode(string.encode()).decode("utf-8").zfill(length)[:length] | ||
|
||
|
||
def remove_vowels(input_str: str, length: int = 4) -> str: | ||
"""Remove vowels from a string and return a string of length 'length'.""" | ||
vowels = "aeiouAEIOU" | ||
output_str = "".join([char for char in input_str if char not in vowels]) | ||
return output_str.zfill(length)[:length] | ||
|
||
|
||
def color_difference_redmean( | ||
rgb1: tuple[float, float, float], | ||
rgb2: tuple[float, float, float], | ||
) -> float: | ||
"""Distance between colors in RGB space (redmean metric). | ||
The maximal distance between (255, 255, 255) and (0, 0, 0) ≈ 765. | ||
Sources: | ||
- https://en.wikipedia.org/wiki/Color_difference#Euclidean | ||
- https://www.compuphase.com/cmetric.htm | ||
""" | ||
r_hat = (rgb1[0] + rgb2[0]) / 2 | ||
delta_r, delta_g, delta_b = ( | ||
(col1 - col2) for col1, col2 in zip(rgb1, rgb2, strict=True) | ||
) | ||
red_term = (2 + r_hat / 256) * delta_r**2 | ||
green_term = 4 * delta_g**2 | ||
blue_term = (2 + (255 - r_hat) / 256) * delta_b**2 | ||
return math.sqrt(red_term + green_term + blue_term) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.