302 lines
10 KiB
Plaintext
302 lines
10 KiB
Plaintext
# PalettePattern animation effect for Berry Animation Framework
|
|
#
|
|
# This animation applies colors from a color provider to specific patterns or regions.
|
|
# It allows for more complex visual effects by combining palette colors with patterns.
|
|
#
|
|
# This version supports both RichPaletteAnimation and ColorProvider instances as color sources,
|
|
# allowing for more flexible usage of color providers.
|
|
|
|
import "./core/param_encoder" as encode_constraints
|
|
|
|
#@ solidify:PalettePatternAnimation,weak
|
|
class PalettePatternAnimation : animation.animation
|
|
var value_buffer # Buffer to store values for each pixel (bytes object)
|
|
|
|
# Static definitions of parameters with constraints
|
|
static var PARAMS = animation.enc_params({
|
|
# Palette pattern-specific parameters
|
|
"color_source": {"default": nil, "type": "instance"},
|
|
"pattern_func": {"default": nil, "type": "function"}
|
|
})
|
|
|
|
# Initialize a new PalettePattern animation
|
|
#
|
|
# @param engine: AnimationEngine - Required animation engine reference
|
|
def init(engine)
|
|
# Call parent constructor with engine
|
|
super(self).init(engine)
|
|
|
|
# Initialize non-parameter instance variables only
|
|
self.value_buffer = bytes()
|
|
|
|
# Initialize value buffer with default frame width
|
|
self._initialize_value_buffer()
|
|
end
|
|
|
|
# Initialize the value buffer based on current strip length
|
|
def _initialize_value_buffer()
|
|
var strip_length = self.engine.strip_length
|
|
self.value_buffer.resize(strip_length)
|
|
|
|
# Initialize with zeros
|
|
var i = 0
|
|
while i < strip_length
|
|
self.value_buffer[i] = 0
|
|
i += 1
|
|
end
|
|
end
|
|
|
|
# Update the value buffer based on the current time
|
|
#
|
|
# @param time_ms: int - Current time in milliseconds
|
|
def _update_value_buffer(time_ms, strip_length)
|
|
var pattern_func = self.pattern_func
|
|
if pattern_func == nil
|
|
return
|
|
end
|
|
|
|
# Calculate values for each pixel
|
|
var i = 0
|
|
while i < strip_length
|
|
var pattern_value = pattern_func(i, time_ms, self)
|
|
# Pattern function should return values in 0-255 range, clamp to byte range
|
|
var byte_value = int(pattern_value)
|
|
if byte_value < 0 byte_value = 0 end
|
|
if byte_value > 255 byte_value = 255 end
|
|
self.value_buffer[i] = byte_value
|
|
i += 1
|
|
end
|
|
end
|
|
|
|
# Update animation state based on current time
|
|
#
|
|
# @param time_ms: int - Current time in milliseconds
|
|
# @return bool - True if animation is still running, false if completed
|
|
def update(time_ms)
|
|
# Call parent update method first
|
|
if !super(self).update(time_ms)
|
|
return false
|
|
end
|
|
|
|
# Auto-fix time_ms and start_time
|
|
time_ms = self._fix_time_ms(time_ms)
|
|
|
|
# Calculate elapsed time since animation started
|
|
var elapsed = time_ms - self.start_time
|
|
|
|
var strip_length = self.engine.strip_length
|
|
|
|
# Resize buffer if strip length changed
|
|
if size(self.value_buffer) != strip_length
|
|
self.value_buffer.resize(strip_length)
|
|
end
|
|
|
|
# Update the value buffer
|
|
self._update_value_buffer(elapsed, strip_length)
|
|
|
|
return true
|
|
end
|
|
|
|
# Render the pattern to the provided frame buffer
|
|
#
|
|
# @param frame: FrameBuffer - The frame buffer to render to
|
|
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
|
# @return bool - True if frame was modified, false otherwise
|
|
def render(frame, time_ms)
|
|
# Auto-fix time_ms and start_time
|
|
time_ms = self._fix_time_ms(time_ms)
|
|
|
|
# Get current parameter values (cached for performance)
|
|
var color_source = self.get_param('color_source') # use get_param to avoid resolving of color_provider
|
|
if color_source == nil
|
|
return false
|
|
end
|
|
|
|
# Calculate elapsed time since animation started
|
|
var elapsed = time_ms - self.start_time
|
|
|
|
# Apply colors from the color source to each pixel based on its value
|
|
var strip_length = self.engine.strip_length
|
|
var i = 0
|
|
while (i < strip_length)
|
|
var byte_value = self.value_buffer[i]
|
|
|
|
# Use the color_source to get color for the byte value (0-255)
|
|
var color = color_source.get_color_for_value(byte_value, elapsed)
|
|
|
|
frame.set_pixel_color(i, color)
|
|
i += 1
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
# Handle parameter changes
|
|
def on_param_changed(name, value)
|
|
super(self).on_param_changed(name, value)
|
|
if name == "pattern_func" || name == "color_source"
|
|
# Reinitialize value buffer when pattern or color source changes
|
|
self._initialize_value_buffer()
|
|
end
|
|
end
|
|
|
|
# String representation of the animation
|
|
def tostring()
|
|
var strip_length = self.engine.strip_length
|
|
return f"{classname(self)}(strip_length={strip_length}, priority={self.priority}, running={self.is_running})"
|
|
end
|
|
end
|
|
|
|
# Wave pattern animation - creates sine wave patterns
|
|
#@ solidify:PaletteWaveAnimation,weak
|
|
class PaletteWaveAnimation : PalettePatternAnimation
|
|
# Static definitions of parameters with constraints
|
|
static var PARAMS = animation.enc_params({
|
|
# Wave-specific parameters only
|
|
"wave_period": {"min": 1, "default": 5000},
|
|
"wave_length": {"min": 1, "default": 10}
|
|
})
|
|
|
|
# Initialize a new wave pattern animation
|
|
#
|
|
# @param engine: AnimationEngine - Required animation engine reference
|
|
def init(engine)
|
|
# Call parent constructor
|
|
super(self).init(engine)
|
|
|
|
# Set default name
|
|
self.name = "palette_wave"
|
|
end
|
|
|
|
# Override _update_value_buffer to generate wave pattern directly
|
|
def _update_value_buffer(time_ms, strip_length)
|
|
# Cache parameter values for performance
|
|
var wave_period = self.wave_period
|
|
var wave_length = self.wave_length
|
|
|
|
# Calculate the wave position using scale_uint for better precision
|
|
# var position = tasmota.scale_uint(time_ms % wave_period, 0, wave_period, 0, 1000) / 1000.0
|
|
# var offset = int(position * wave_length)
|
|
var offset = tasmota.scale_uint(time_ms % wave_period, 0, wave_period, 0, wave_length)
|
|
|
|
# Calculate values for each pixel
|
|
var i = 0
|
|
while i < strip_length
|
|
# Calculate the wave value (0-255) using scale_uint
|
|
var pos_in_wave = (i + offset) % wave_length
|
|
var angle = tasmota.scale_uint(pos_in_wave, 0, wave_length, 0, 32767) # 0 to 2π in fixed-point
|
|
var sine_value = tasmota.sine_int(angle) # -4096 to 4096
|
|
|
|
# Map sine value from -4096..4096 to 0..255
|
|
var byte_value = tasmota.scale_int(sine_value, -4096, 4096, 0, 255)
|
|
self.value_buffer[i] = byte_value
|
|
i += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
# Gradient pattern animation - creates shifting gradient patterns
|
|
#@ solidify:PaletteGradientAnimation,weak
|
|
class PaletteGradientAnimation : PalettePatternAnimation
|
|
# Static definitions of parameters with constraints
|
|
static var PARAMS = animation.enc_params({
|
|
# Gradient-specific parameters only
|
|
"shift_period": {"min": 0, "default": 0}, # Time for one complete shift cycle in ms (0 = static)
|
|
"spatial_period": {"min": 0, "default": 0}, # Spatial period in pixels (0 = full strip)
|
|
"phase_shift": {"min": 0, "max": 100, "default": 0} # Phase shift as percentage (0-100)
|
|
})
|
|
|
|
# Initialize a new gradient pattern animation
|
|
#
|
|
# @param engine: AnimationEngine - Required animation engine reference
|
|
def init(engine)
|
|
# Call parent constructor
|
|
super(self).init(engine)
|
|
|
|
# Set default name
|
|
self.name = "palette_gradient"
|
|
end
|
|
|
|
# Override _update_value_buffer to generate gradient pattern directly
|
|
def _update_value_buffer(time_ms, strip_length)
|
|
# Cache parameter values for performance
|
|
var shift_period = self.shift_period
|
|
var spatial_period = self.spatial_period
|
|
var phase_shift = self.phase_shift
|
|
|
|
# Determine effective spatial period (0 means full strip)
|
|
var effective_spatial_period = spatial_period > 0 ? spatial_period : strip_length
|
|
|
|
# Calculate the temporal shift position (how much the pattern has moved over time)
|
|
var temporal_offset = 0
|
|
if shift_period > 0
|
|
temporal_offset = tasmota.scale_uint(time_ms % shift_period, 0, shift_period, 0, effective_spatial_period)
|
|
end
|
|
|
|
# Calculate the phase shift offset in pixels
|
|
var phase_offset = tasmota.scale_uint(phase_shift, 0, 100, 0, effective_spatial_period)
|
|
|
|
# Calculate values for each pixel
|
|
var i = 0
|
|
while i < strip_length
|
|
# Calculate position within the spatial period, including temporal and phase offsets
|
|
var spatial_pos = (i + temporal_offset + phase_offset) % effective_spatial_period
|
|
|
|
# Map spatial position to gradient value (0-255)
|
|
var byte_value = tasmota.scale_uint(int(spatial_pos), 0, effective_spatial_period - 1, 0, 255)
|
|
self.value_buffer[i] = byte_value
|
|
i += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
# Value meter pattern animation - creates meter/bar patterns based on a value function
|
|
#@ solidify:PaletteMeterAnimation,weak
|
|
class PaletteMeterAnimation : PalettePatternAnimation
|
|
# Static definitions of parameters with constraints
|
|
static var PARAMS = animation.enc_params({
|
|
# Meter-specific parameters only
|
|
"value_func": {"default": nil, "type": "function"}
|
|
})
|
|
|
|
# Initialize a new meter pattern animation
|
|
#
|
|
# @param engine: AnimationEngine - Required animation engine reference
|
|
def init(engine)
|
|
# Call parent constructor
|
|
super(self).init(engine)
|
|
|
|
# Set default name
|
|
self.name = "palette_meter"
|
|
end
|
|
|
|
# Override _update_value_buffer to generate meter pattern directly
|
|
def _update_value_buffer(time_ms, strip_length)
|
|
# Cache parameter values for performance
|
|
var value_func = self.value_func
|
|
if value_func == nil
|
|
return
|
|
end
|
|
|
|
# Get the current value
|
|
var current_value = value_func(time_ms, self)
|
|
|
|
# Calculate the meter position using scale_uint for better precision
|
|
var meter_position = tasmota.scale_uint(current_value, 0, 100, 0, strip_length)
|
|
|
|
# Calculate values for each pixel
|
|
var i = 0
|
|
while i < strip_length
|
|
# Return 255 if pixel is within the meter, 0 otherwise
|
|
self.value_buffer[i] = i < meter_position ? 255 : 0
|
|
i += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return {
|
|
'palette_pattern_animation': PalettePatternAnimation,
|
|
'palette_wave_animation': PaletteWaveAnimation,
|
|
'palette_gradient_animation': PaletteGradientAnimation,
|
|
'palette_meter_animation': PaletteMeterAnimation
|
|
} |