Merge pull request #23 from rhooper/rainbowsparkle-fix
Rainbowsparkle fix
This commit is contained in:
commit
773c5b7658
13 changed files with 116 additions and 97 deletions
|
|
@ -54,14 +54,13 @@ class Animation:
|
|||
"""
|
||||
Base class for animations.
|
||||
"""
|
||||
cycle_complete_supported = False
|
||||
on_cycle_complete_supported = False
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None):
|
||||
self.pixel_object = pixel_object
|
||||
self.pixel_object.auto_write = False
|
||||
self.peers = peers if peers else []
|
||||
"""A sequence of animations to trigger .draw() on when this animation draws."""
|
||||
self._peers = [self] + peers if peers is not None else [self]
|
||||
self._speed_ns = 0
|
||||
self._color = None
|
||||
self._paused = paused
|
||||
|
|
@ -71,6 +70,7 @@ class Animation:
|
|||
self.speed = speed # sets _speed_ns
|
||||
self.color = color # Triggers _recompute_color
|
||||
self.name = name
|
||||
self.cycle_complete = False
|
||||
self.notify_cycles = 1
|
||||
"""Number of cycles to trigger additional cycle_done notifications after"""
|
||||
self.draw_count = 0
|
||||
|
|
@ -95,13 +95,19 @@ class Animation:
|
|||
if now < self._next_update:
|
||||
return False
|
||||
|
||||
self.draw()
|
||||
self.draw_count += 1
|
||||
|
||||
# Draw related animations together
|
||||
if self.peers:
|
||||
for peer in self.peers:
|
||||
peer.draw()
|
||||
for anim in self._peers:
|
||||
anim.draw()
|
||||
anim.after_draw()
|
||||
|
||||
for anim in self._peers:
|
||||
anim.show()
|
||||
|
||||
# Note that the main animation cycle_complete flag is used, not the peer flag.
|
||||
for anim in self._peers:
|
||||
if self.cycle_complete:
|
||||
anim.on_cycle_complete()
|
||||
anim.cycle_complete = False
|
||||
|
||||
self._next_update = now + self._speed_ns
|
||||
return True
|
||||
|
|
@ -109,16 +115,39 @@ class Animation:
|
|||
def draw(self):
|
||||
"""
|
||||
Animation subclasses must implement draw() to render the animation sequence.
|
||||
Draw must call show().
|
||||
Animations should not call show(), as animate() will do so, after after_draw().
|
||||
Animations should set .cycle_done = True when an animation cycle is completed.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def after_draw(self):
|
||||
"""
|
||||
Animation subclasses may implement after_draw() to do operations after the main draw()
|
||||
is called.
|
||||
"""
|
||||
|
||||
def show(self):
|
||||
"""
|
||||
Displays the updated pixels. Called during animates with changes.
|
||||
"""
|
||||
self.pixel_object.show()
|
||||
|
||||
@property
|
||||
def peers(self):
|
||||
"""
|
||||
Get the animation's peers. Peers are drawn, then shown together.
|
||||
"""
|
||||
return self._peers[1:]
|
||||
|
||||
@peers.setter
|
||||
def peers(self, peer_list):
|
||||
"""
|
||||
Set the animation's peers.
|
||||
:param list peer_list: List of peer animations.
|
||||
"""
|
||||
if peer_list is not None:
|
||||
self._peers = [self] + peer_list
|
||||
|
||||
def freeze(self):
|
||||
"""
|
||||
Stops the animation until resumed.
|
||||
|
|
@ -173,7 +202,7 @@ class Animation:
|
|||
Override as needed.
|
||||
"""
|
||||
|
||||
def cycle_complete(self):
|
||||
def on_cycle_complete(self):
|
||||
"""
|
||||
Called by some animations when they complete an animation cycle.
|
||||
Animations that support cycle complete notifications will have X property set to False.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class Chase(Animation):
|
|||
|
||||
super().__init__(pixel_object, speed, color, name=name)
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
@property
|
||||
def reverse(self):
|
||||
|
|
@ -115,10 +115,9 @@ class Chase(Animation):
|
|||
|
||||
colorgen = bar_colors()
|
||||
self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object]
|
||||
self.show()
|
||||
|
||||
if self.draw_count % len(self.pixel_object) == 0:
|
||||
self.cycle_complete()
|
||||
self.cycle_complete = True
|
||||
self._offset = (self._offset + self._direction) % self._repeat_width
|
||||
|
||||
def bar_color(self, n, pixel_no=0): # pylint: disable=unused-argument
|
||||
|
|
|
|||
|
|
@ -64,11 +64,10 @@ class ColorCycle(Animation):
|
|||
self._generator = self._color_generator()
|
||||
next(self._generator)
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def draw(self):
|
||||
self.pixel_object.fill(self.color)
|
||||
self.show()
|
||||
next(self._generator)
|
||||
|
||||
def _color_generator(self):
|
||||
|
|
@ -78,7 +77,7 @@ class ColorCycle(Animation):
|
|||
yield
|
||||
index = (index + 1) % len(self.colors)
|
||||
if index == 0:
|
||||
self.cycle_complete()
|
||||
self.cycle_complete = True
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class Comet(Animation):
|
|||
self._generator = self._comet_generator()
|
||||
super().__init__(pixel_object, speed, color, name=name)
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def _recompute_color(self, color):
|
||||
pass
|
||||
|
|
@ -130,13 +130,12 @@ class Comet(Animation):
|
|||
]
|
||||
else:
|
||||
self.pixel_object[start : start + end] = colors[0:end]
|
||||
self.show()
|
||||
yield
|
||||
cycle_passes += 1
|
||||
if self.bounce:
|
||||
self.reverse = not self.reverse
|
||||
if not self.bounce or cycle_passes == 2:
|
||||
self.cycle_complete()
|
||||
self.cycle_complete = True
|
||||
cycle_passes = 0
|
||||
|
||||
def draw(self):
|
||||
|
|
|
|||
|
|
@ -64,12 +64,11 @@ class Pulse(Animation):
|
|||
self._generator = None
|
||||
self.reset()
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def draw(self):
|
||||
color = next(self._generator)
|
||||
self.fill(color)
|
||||
self.show()
|
||||
self.pixel_object.fill(color)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
|
|
@ -78,8 +77,13 @@ class Pulse(Animation):
|
|||
white = len(self.pixel_object[0]) > 3 and isinstance(
|
||||
self.pixel_object[0][-1], int
|
||||
)
|
||||
dotstar = len(self.pixel_object[0]) == 4 and isinstance(
|
||||
self.pixel_object[0][-1], float
|
||||
)
|
||||
from adafruit_led_animation.helper import ( # pylint: disable=import-outside-toplevel
|
||||
pulse_generator,
|
||||
)
|
||||
|
||||
self._generator = pulse_generator(self._period, self, white)
|
||||
self._generator = pulse_generator(
|
||||
self._period, self, white, dotstar_pwm=dotstar
|
||||
)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class Rainbow(Animation):
|
|||
self.colors.append(colorwheel(int(i)))
|
||||
i += self._step
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def _color_wheel_generator(self):
|
||||
period = int(self._period * NANOS_PER_SECOND)
|
||||
|
|
@ -113,9 +113,8 @@ class Rainbow(Animation):
|
|||
colorwheel((i + wheel_index) % 255) for i in range(num_pixels)
|
||||
]
|
||||
self._wheel_index = wheel_index
|
||||
self.show()
|
||||
if cycle_completed:
|
||||
self.cycle_complete()
|
||||
self.cycle_complete = True
|
||||
yield
|
||||
|
||||
def _draw_precomputed(self, num_pixels, wheel_index):
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ class RainbowChase(Chase):
|
|||
super().__init__(pixel_object, speed, 0, size, spacing, reverse, name)
|
||||
|
||||
def bar_color(self, n, pixel_no=0):
|
||||
return self._colors[self._color_idx - n]
|
||||
return self._colors[self._color_idx - (n % len(self._colors))]
|
||||
|
||||
def cycle_complete(self):
|
||||
def on_cycle_complete(self):
|
||||
self._color_idx = (self._color_idx + self._direction) % len(self._colors)
|
||||
super().cycle_complete()
|
||||
super().on_cycle_complete()
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@ class RainbowSparkle(Rainbow):
|
|||
int(self._background_brightness * color[2]),
|
||||
)
|
||||
|
||||
def show(self):
|
||||
def after_draw(self):
|
||||
self.show()
|
||||
pixels = [
|
||||
random.randint(0, len(self.pixel_object) - 1)
|
||||
for n in range(self._num_sparkles)
|
||||
|
|
@ -115,4 +116,3 @@ class RainbowSparkle(Rainbow):
|
|||
self.pixel_object[pixel] = self._bright_colors[
|
||||
(self._wheel_index + pixel) % len(self._bright_colors)
|
||||
]
|
||||
super().show()
|
||||
|
|
|
|||
|
|
@ -64,9 +64,11 @@ class Sparkle(Animation):
|
|||
def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None):
|
||||
if len(pixel_object) < 2:
|
||||
raise ValueError("Sparkle needs at least 2 pixels")
|
||||
self._half_color = None
|
||||
self._dim_color = None
|
||||
self._half_color = color
|
||||
self._dim_color = color
|
||||
self._sparkle_color = color
|
||||
self._num_sparkles = num_sparkles
|
||||
self._pixels = []
|
||||
super().__init__(pixel_object, speed, color, name=name)
|
||||
|
||||
def _recompute_color(self, color):
|
||||
|
|
@ -79,16 +81,18 @@ class Sparkle(Animation):
|
|||
self.pixel_object[pixel] = dim_color
|
||||
self._half_color = half_color
|
||||
self._dim_color = dim_color
|
||||
self._sparkle_color = color
|
||||
|
||||
def draw(self):
|
||||
pixels = [
|
||||
self._pixels = [
|
||||
random.randint(0, (len(self.pixel_object) - 2))
|
||||
for n in range(self._num_sparkles)
|
||||
for _ in range(self._num_sparkles)
|
||||
]
|
||||
for pixel in pixels:
|
||||
self.pixel_object[pixel] = self._color
|
||||
for pixel in self._pixels:
|
||||
self.pixel_object[pixel] = self._sparkle_color
|
||||
|
||||
def after_draw(self):
|
||||
self.show()
|
||||
for pixel in pixels:
|
||||
for pixel in self._pixels:
|
||||
self.pixel_object[pixel] = self._half_color
|
||||
self.pixel_object[pixel + 1] = self._dim_color
|
||||
self.show()
|
||||
|
|
|
|||
|
|
@ -44,14 +44,13 @@ Implementation Notes
|
|||
|
||||
"""
|
||||
|
||||
import random
|
||||
from adafruit_led_animation import NANOS_PER_SECOND, monotonic_ns
|
||||
from adafruit_led_animation.animation import Animation
|
||||
from adafruit_led_animation.animation.sparkle import Sparkle
|
||||
from adafruit_led_animation.helper import pulse_generator
|
||||
|
||||
|
||||
class SparklePulse(Animation):
|
||||
class SparklePulse(Sparkle):
|
||||
"""
|
||||
Combination of the Spark and Pulse animations.
|
||||
Combination of the Sparkle and Pulse animations.
|
||||
|
||||
:param pixel_object: The initialised LED object.
|
||||
:param int speed: Animation refresh rate in seconds, e.g. ``0.1``.
|
||||
|
|
@ -63,48 +62,30 @@ class SparklePulse(Animation):
|
|||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(
|
||||
self, pixel_object, speed, color, period=5, max_intensity=1, min_intensity=0
|
||||
self,
|
||||
pixel_object,
|
||||
speed,
|
||||
color,
|
||||
period=5,
|
||||
max_intensity=1,
|
||||
min_intensity=0,
|
||||
name=None,
|
||||
):
|
||||
if len(pixel_object) < 2:
|
||||
raise ValueError("Sparkle needs at least 2 pixels")
|
||||
self.max_intensity = max_intensity
|
||||
self.min_intensity = min_intensity
|
||||
self._max_intensity = max_intensity
|
||||
self._min_intensity = min_intensity
|
||||
self._period = period
|
||||
self._intensity_delta = max_intensity - min_intensity
|
||||
self._half_period = period / 2
|
||||
self._position_factor = 1 / self._half_period
|
||||
self._bpp = len(pixel_object[0])
|
||||
self._last_update = monotonic_ns()
|
||||
self._cycle_position = 0
|
||||
self._half_color = None
|
||||
self._dim_color = None
|
||||
super().__init__(pixel_object, speed, color)
|
||||
|
||||
def _recompute_color(self, color):
|
||||
half_color = tuple(color[rgb] // 4 for rgb in range(len(color)))
|
||||
dim_color = tuple(color[rgb] // 10 for rgb in range(len(color)))
|
||||
for pixel in range(len(self.pixel_object)):
|
||||
if self.pixel_object[pixel] == self._half_color:
|
||||
self.pixel_object[pixel] = half_color
|
||||
elif self.pixel_object[pixel] == self._dim_color:
|
||||
self.pixel_object[pixel] = dim_color
|
||||
self._half_color = half_color
|
||||
self._dim_color = dim_color
|
||||
white = len(pixel_object) == 4 and isinstance(pixel_object[0][-1], int)
|
||||
dotstar = len(pixel_object) == 4 and isinstance(pixel_object[0][-1], float)
|
||||
super().__init__(
|
||||
pixel_object, speed=speed, color=color, num_sparkles=1, name=name
|
||||
)
|
||||
self._generator = pulse_generator(
|
||||
self._period, self, white, dotstar_pwm=dotstar
|
||||
)
|
||||
|
||||
def draw(self):
|
||||
pixel = random.randint(0, (len(self.pixel_object) - 2))
|
||||
self._sparkle_color = next(self._generator)
|
||||
super().draw()
|
||||
|
||||
now = monotonic_ns()
|
||||
time_since_last_draw = (now - self._last_update) / NANOS_PER_SECOND
|
||||
self._last_update = now
|
||||
pos = self._cycle_position = (
|
||||
self._cycle_position + time_since_last_draw
|
||||
) % self._period
|
||||
if pos > self._half_period:
|
||||
pos = self._period - pos
|
||||
intensity = self.min_intensity + (
|
||||
pos * self._intensity_delta * self._position_factor
|
||||
)
|
||||
color = [int(self.color[n] * intensity) for n in range(self._bpp)]
|
||||
self.pixel_object[pixel] = color
|
||||
def after_draw(self):
|
||||
self.show()
|
||||
|
|
|
|||
|
|
@ -79,15 +79,15 @@ class AnimationGroup:
|
|||
|
||||
# Catch cycle_complete on the last animation.
|
||||
self._members[-1].add_cycle_complete_receiver(self._group_done)
|
||||
self.cycle_complete_supported = self._members[-1].cycle_complete_supported
|
||||
self.on_cycle_complete_supported = self._members[-1].on_cycle_complete_supported
|
||||
|
||||
def __str__(self):
|
||||
return "<AnimationGroup %s: %s>" % (self.__class__.__name__, self.name)
|
||||
|
||||
def _group_done(self, animation): # pylint: disable=unused-argument
|
||||
self.cycle_complete()
|
||||
self.on_cycle_complete()
|
||||
|
||||
def cycle_complete(self):
|
||||
def on_cycle_complete(self):
|
||||
"""
|
||||
Called by some animations when they complete an animation cycle.
|
||||
Animations that support cycle complete notifications will have X property set to False.
|
||||
|
|
|
|||
|
|
@ -361,12 +361,13 @@ class PixelSubset:
|
|||
self._pixels.auto_write = value
|
||||
|
||||
|
||||
def pulse_generator(period: float, animation_object, white=False):
|
||||
def pulse_generator(period: float, animation_object, white=False, dotstar_pwm=False):
|
||||
"""
|
||||
Generates a sequence of colors for a pulse, based on the time period specified.
|
||||
:param period: Pulse duration in seconds.
|
||||
:param animation_object: An animation object to interact with.
|
||||
:param white: Whether the pixel strip has a white pixel.
|
||||
:param dotstar_pwm: Whether to use the dostar per pixel PWM value for brightness control.
|
||||
"""
|
||||
period = int(period * NANOS_PER_SECOND)
|
||||
half_period = period // 2
|
||||
|
|
@ -381,11 +382,15 @@ def pulse_generator(period: float, animation_object, white=False):
|
|||
last_update = now
|
||||
pos = cycle_position = (cycle_position + time_since_last_draw) % period
|
||||
if pos < last_pos:
|
||||
animation_object.cycle_complete()
|
||||
animation_object.on_cycle_complete()
|
||||
last_pos = pos
|
||||
if pos > half_period:
|
||||
pos = period - pos
|
||||
intensity = pos / half_period
|
||||
if dotstar_pwm:
|
||||
fill_color = (fill_color[0], fill_color[1], fill_color[2], intensity)
|
||||
yield fill_color
|
||||
continue
|
||||
if white:
|
||||
fill_color[3] = int(fill_color[3] * intensity)
|
||||
fill_color[0] = int(fill_color[0] * intensity)
|
||||
|
|
|
|||
|
|
@ -65,9 +65,9 @@ class AnimationSequence:
|
|||
Defaults to ``False``.
|
||||
:param bool random_order: Activate the animations in a random order. Defaults to ``False``.
|
||||
:param bool auto_reset: Automatically call reset() on animations when changing animations.
|
||||
:param bool advance_on_cycle_complete: Automatically advance when `cycle_complete` is triggered
|
||||
on member animations. All Animations must support
|
||||
cycle_complete to use this.
|
||||
:param bool advance_on_cycle_complete: Automatically advance when `on_cycle_complete` is
|
||||
triggered on member animations. All Animations must
|
||||
support on_cycle_complete to use this.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -126,14 +126,14 @@ class AnimationSequence:
|
|||
self._color = None
|
||||
for member in self._members:
|
||||
member.add_cycle_complete_receiver(self._sequence_complete)
|
||||
self.cycle_complete_supported = self._members[-1].cycle_complete_supported
|
||||
self.on_cycle_complete_supported = self._members[-1].on_cycle_complete_supported
|
||||
|
||||
cycle_complete_supported = True
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def __str__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.name)
|
||||
|
||||
def cycle_complete(self):
|
||||
def on_cycle_complete(self):
|
||||
"""
|
||||
Called by some animations when they complete an animation cycle.
|
||||
Animations that support cycle complete notifications will have X property set to False.
|
||||
|
|
@ -145,7 +145,7 @@ class AnimationSequence:
|
|||
callback(self)
|
||||
|
||||
def _sequence_complete(self, animation): # pylint: disable=unused-argument
|
||||
self.cycle_complete()
|
||||
self.on_cycle_complete()
|
||||
if self.advance_on_cycle_complete:
|
||||
self._advance()
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ class AnimationSequence:
|
|||
"""
|
||||
current = self._current
|
||||
if current > self._current:
|
||||
self.cycle_complete()
|
||||
self.on_cycle_complete()
|
||||
self.activate((self._current + 1) % len(self._members))
|
||||
|
||||
def random(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue