diff --git a/panther_lights/src/animation/battery_animation.py b/panther_lights/src/animation/battery_animation.py index 009086ce7..c4716f43f 100644 --- a/panther_lights/src/animation/battery_animation.py +++ b/panther_lights/src/animation/battery_animation.py @@ -13,9 +13,12 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._anim = np.zeros((self._anim_len, self.num_led)) - self._h_min = 0.0 - self._h_max = 120.0 + self._h_min = 0.0 # red color + self._h_max = 120.0 / 360.0 # green color self._resolution = 100 + self._end_anim = 0.75 * self._resolution + self._start_fade = 0.80 * self._resolution + self._end_fade = 0.95 * self._resolution self._gaussian_blur_radius = 1.5 def _update_frame(self) -> list: @@ -31,51 +34,64 @@ def set_param(self, value: str) -> None: def _create_anim(self, battery_percent: float) -> None: battery_percent = np.clip(battery_percent, 0.0, 1.0) - # define basic HSV color - h = self._h_min - s = 1.0 - v = 1.0 - frame = np.zeros((self._num_led, 3), dtype=np.uint8) anim = np.zeros((self._resolution, self._num_led, 3), dtype=np.uint8) - if self._num_led % 2 == 0: - percent_point = int(round(battery_percent * self._num_led / 2.0)) - else: - percent_point = int(np.ceil(battery_percent * self._num_led / 2.0)) - - # 0.9 was added to hold for some time the final frame of the animation when percentage is 1.0 - display_iterations = battery_percent * self._resolution * 0.9 - - if percent_point < 1: - percent_point = 1 - display_iterations = 1 - - for i in range(self._resolution): - if i <= display_iterations: - anim_ind = round(i / self._resolution * self._num_led / 0.9) - - if anim_ind < percent_point: - frame.fill(0) - ind_1 = int(np.ceil(self._num_led / 2.0 - anim_ind - 1.0)) - ind_2 = int(np.floor(self._num_led / 2.0 + anim_ind)) - h = (self._h_max - self._h_min) * np.sin( - max(0, battery_percent) * np.pi / 2.0 - ) * i / (display_iterations / 2.0) + self._h_min - rgb = np.array(hsv_to_rgb(h / 360.0, s, v)) - frame[ind_1] = np.uint8(rgb * 255.0) - frame[ind_2] = np.uint8(rgb * 255.0) - elif percent_point <= anim_ind < 2 * percent_point: - ind_1 = int(np.ceil(self._num_led / 2.0 - 2.0 * percent_point + anim_ind)) - ind_2 = int( - np.floor(self._num_led / 2.0 + 2.0 * percent_point - anim_ind - 1.0) - ) - frame[ind_1] = np.uint8(rgb * 255.0) - frame[ind_2] = np.uint8(rgb * 255.0) + color_rising_duration = int(round(battery_percent * self._end_anim / 2.0)) + + if color_rising_duration < 1: + color_rising_duration = 1 + + for i in range(1, self._resolution): + if i <= color_rising_duration: + progress = i / color_rising_duration + frame = self._color_rising(battery_percent, progress) + + elif i <= 2 * color_rising_duration: + progress = (i - color_rising_duration) / color_rising_duration + frame = self._fill_frame(frame, battery_percent, progress) anim[i] = frame + if i > self._start_fade: + progress = (i - self._start_fade) / (self._end_fade - self._start_fade) + progress = min(1.0, progress) + anim[i] = (1 - progress) * frame + # filter to smooth animation and resize to match duration img = Image.fromarray(anim) img = img.filter(ImageFilter.GaussianBlur(self._gaussian_blur_radius)) img = img.resize((self._num_led, self._anim_len)) self._anim = np.array(img) + + def _color_rising(self, battery_percent: float, progress: float) -> np.array: + frame = np.zeros((self._num_led, 3), dtype=np.uint8) + led_to_disp = battery_percent * self._num_led + + middle_led = (self._num_led - 1) / 2 + ind_1 = int(np.ceil(middle_led - progress * led_to_disp / 2.0)) + ind_2 = int(np.floor(middle_led + progress * led_to_disp / 2.0)) + + rgb = self._calculate_color(battery_percent, progress) + frame[ind_1] = rgb + frame[ind_2] = rgb + return frame + + def _fill_frame(self, frame: np.array, battery_percent: float, progress: float) -> np.array: + led_to_disp = battery_percent * self._num_led + + middle_led = (self._num_led - 1) / 2 + ind_1 = int(np.ceil(middle_led - (1 - progress) * led_to_disp / 2.0)) + ind_2 = int(np.floor(middle_led + (1 - progress) * led_to_disp / 2.0)) + + rgb = self._calculate_color(battery_percent) + frame[ind_1] = rgb + frame[ind_2] = rgb + return frame + + def _calculate_color(self, battery_percent: float, progress: float = 1.0) -> tuple: + h = (self._h_max - self._h_min) * np.sin( + battery_percent * np.pi / 2.0 + ) * progress + self._h_min + rgb = np.array(hsv_to_rgb(h, 1.0, 1.0)) + rgb = np.uint8(rgb * 255.0) + return rgb