Refactor animation API to allow for processing entire frames at once

This commit is contained in:
Kuba Birecki 2021-12-19 15:28:21 +01:00
parent ae518fabc9
commit 2fa6063c9b
14 changed files with 179 additions and 207 deletions

13
app/dts/animation.dtsi Normal file
View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/ {
/omit-if-no-ref/ pixel: animation_pixel {
compatible = "zmk,animation-pixel";
label = "PIXEL";
#pixel-cells = <2>;
};
};

View file

@ -1,6 +1,23 @@
# Copyright (c) 2020, The ZMK Contributors
# SPDX-License-Identifier: MIT
pixel-cells:
- position_x
- position_y
properties:
pixels:
type: array
required: true
description: |
Pixel positions to which the animation should apply.
blending-mode:
type: int
required: false
enum:
- 0
- 1
- 2
- 3
- 4
- 5
default: 0
description: |
Blending mode for the animation to use during render.

View file

@ -7,21 +7,9 @@ description: |
compatible: "zmk,animation-compose"
include: animation_base.yaml
properties:
label:
type: string
required: true
animations:
type: phandles
required: true
description: |
Animations to be combined.
blending-modes:
type: array
required: true
description: |
Blending modes for each animation.

View file

@ -9,10 +9,6 @@ compatible: "zmk,animation-ripple"
include: animation_base.yaml
properties:
label:
type: string
required: true
duration:
type: int
required: false

View file

@ -9,10 +9,6 @@ compatible: "zmk,animation-solid"
include: animation_base.yaml
properties:
label:
type: string
required: true
duration:
type: int
required: false

View file

@ -0,0 +1,10 @@
# Copyright (c) 2020, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Pixel configuration
compatible: "zmk,animation-pixel"
pixel-cells:
- position_x
- position_y

View file

@ -13,12 +13,6 @@ properties:
This array should contain all driver devices responsible for illuminating animated LEDs.
The devices must implement Zephyr's LED Strip Interface and expose a chain-lenght devicetree property.
animations:
type: phandles
required: true
description: |
Handles to all active animations.
pixels:
type: phandle-array
required: true

View file

@ -21,11 +21,10 @@
*/
struct animation_pixel {
const size_t id;
const uint8_t position_x;
const uint8_t position_y;
const struct device *animation;
struct zmk_color_rgb value;
};
#ifdef __cplusplus
@ -33,63 +32,23 @@ extern "C" {
#endif
/**
* @typedef animation_api_prep_next_frame
* @brief Callback run before every frame is rendered.
*
* @see animation_api_before_frame() for argument descriptions.
*/
typedef void (*animation_api_on_before_frame)(const struct device *dev);
/**
* @typedef animation_api_prep_next_frame
* @brief Callback run after every frame is rendered.
*
* @see animation_api_before_frame() for argument descriptions.
*/
typedef void (*animation_api_on_after_frame)(const struct device *dev);
/**
* @typedef animation_api_prep_next_frame
* @typedef animation_render_frame
* @brief Callback API for generating the next animation frame
*
* @see animation_prep_next_frame() for argument descriptions.
* @see animation_render_frame() for argument descriptions.
*/
typedef void (*animation_api_render_pixel)(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value);
typedef void (*animation_api_render_frame)(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels);
struct animation_api {
animation_api_on_before_frame on_before_frame;
animation_api_on_after_frame on_after_frame;
animation_api_render_pixel render_pixel;
animation_api_render_frame render_frame;
};
static inline void animation_on_before_frame(const struct device *dev) {
static inline void animation_render_frame(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels) {
const struct animation_api *api = (const struct animation_api *)dev->api;
if (api->on_before_frame == NULL) {
return;
}
api->on_before_frame(dev);
}
static inline void animation_on_after_frame(const struct device *dev) {
const struct animation_api *api = (const struct animation_api *)dev->api;
if (api->on_after_frame == NULL) {
return;
}
api->on_after_frame(dev);
}
static inline void animation_render_pixel(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value) {
const struct animation_api *api = (const struct animation_api *)dev->api;
return api->render_pixel(dev, pixel, value);
return api->render_frame(dev, pixels, num_pixels);
}
#ifdef __cplusplus

View file

@ -9,6 +9,13 @@
#include <device.h>
#include <drivers/led_strip.h>
#define ZMK_ANIMATION_BLENDING_MODE_NORMAL 0
#define ZMK_ANIMATION_BLENDING_MODE_MULTIPLY 1
#define ZMK_ANIMATION_BLENDING_MODE_LIGHTEN 2
#define ZMK_ANIMATION_BLENDING_MODE_DARKEN 3
#define ZMK_ANIMATION_BLENDING_MODE_SCREEN 4
#define ZMK_ANIMATION_BLENDING_MODE_SUBTRACT 5
struct zmk_color_rgb {
float r;
float g;
@ -30,7 +37,7 @@ static inline size_t zmk_animation_get_pixel_by_key_position(size_t key_position
#endif
#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1)
uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx);
uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx);
#endif
/**
@ -70,3 +77,16 @@ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b);
*/
void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to,
struct zmk_color_hsl *result, float step);
struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value,
struct zmk_color_rgb blend_value, uint8_t mode);
static inline struct zmk_color_rgb zmk_apply_blending_mode(struct zmk_color_rgb base_value,
struct zmk_color_rgb blend_value,
uint8_t mode) {
if (mode == ZMK_ANIMATION_BLENDING_MODE_NORMAL) {
return blend_value;
}
return __zmk_apply_blending_mode(base_value, blend_value, mode);
}

View file

@ -35,10 +35,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define PHANDLE_TO_PIXEL(idx, node_id, prop) \
{ \
.id = idx, \
.position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \
.position_y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \
.animation = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \
},
/**
@ -59,20 +57,14 @@ static const uint8_t pixels_per_driver[] = {
ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)};
/**
* Pointers to all active animation devices.
* Pointer to the root animation
*/
static const struct device *animations[] = {
ZMK_DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE)};
/**
* Size of the animation device pointers array.
*/
static const size_t animations_size = DT_INST_PROP_LEN(0, animations);
static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animation));
/**
* Pixel configuration.
*/
static const struct animation_pixel pixels[] = {
static struct animation_pixel pixels[] = {
ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)};
/**
@ -102,29 +94,19 @@ size_t zmk_animation_get_pixel_by_key_position(size_t key_position) {
/**
* Lookup table for distance between any two pixels.
*/
static uint16_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)];
static uint8_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)];
uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) {
uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) {
return pixel_distance[pixel_idx][other_pixel_idx];
}
#endif
static void zmk_animation_tick(struct k_work *work) {
for (size_t i = 0; i < animations_size; ++i) {
animation_on_before_frame(animations[i]);
}
animation_render_frame(animation_root, &pixels[0], pixels_size);
for (size_t i = 0; i < pixels_size; ++i) {
struct zmk_color_rgb rgb = {
.r = 0,
.g = 0,
.b = 0,
};
animation_render_pixel(pixels[i].animation, &pixels[i], &rgb);
zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]);
zmk_rgb_to_led_rgb(&pixels[i].value, &px_buffer[i]);
}
size_t pixels_updated = 0;
@ -134,10 +116,6 @@ static void zmk_animation_tick(struct k_work *work) {
pixels_updated += (size_t)pixels_per_driver;
}
for (size_t i = 0; i < animations_size; ++i) {
animation_on_after_frame(animations[i]);
}
}
K_WORK_DEFINE(animation_work, zmk_animation_tick);
@ -151,8 +129,11 @@ static int zmk_animation_init(const struct device *dev) {
// Prefill the pixel distance lookup table
for (size_t i = 0; i < pixels_size; ++i) {
for (size_t j = 0; j < pixels_size; ++j) {
// Distances are normalized to fit inside 0-255 range to fit inside uint8_t
// for better space efficiency
pixel_distance[i][j] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) +
pow(pixels[i].position_y - pixels[j].position_y, 2));
pow(pixels[i].position_y - pixels[j].position_y, 2)) *
255 / 360;
}
}
#endif

View file

@ -12,7 +12,6 @@
#include <logging/log.h>
#include <zmk/animation.h>
#include <dt-bindings/zmk/animation_compose.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -26,77 +25,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct animation_compose_config {
const struct device **animations;
const size_t animations_size;
const uint8_t *blending_modes;
};
static void animation_compose_render_pixel(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value) {
static void animation_compose_render_frame(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels) {
const struct animation_compose_config *config = dev->config;
const struct device **animations = config->animations;
const uint8_t *blending_modes = config->blending_modes;
struct zmk_color_rgb rgb = {
.r = 0,
.g = 0,
.b = 0,
};
for (size_t i = 0; i < config->animations_size; ++i) {
animation_render_pixel(animations[i], pixel,
blending_modes[i] == BLENDING_MODE_NORMAL ? value : &rgb);
switch (blending_modes[i]) {
case BLENDING_MODE_MULTIPLY:
value->r = value->r * rgb.r;
value->g = value->g * rgb.g;
value->b = value->b * rgb.b;
break;
case BLENDING_MODE_LIGHTEN:
value->r = value->r > rgb.r ? value->r : rgb.r;
value->g = value->g > rgb.g ? value->g : rgb.g;
value->b = value->b > rgb.b ? value->b : rgb.b;
break;
case BLENDING_MODE_DARKEN:
value->r = value->r > rgb.r ? rgb.r : value->r;
value->g = value->g > rgb.g ? rgb.g : value->g;
value->b = value->b > rgb.b ? rgb.b : value->b;
break;
case BLENDING_MODE_SCREEN:
value->r = value->r + (1.0f - value->r) * rgb.r;
value->g = value->g + (1.0f - value->g) * rgb.g;
value->b = value->b + (1.0f - value->b) * rgb.b;
break;
case BLENDING_MODE_SUBTRACT:
value->r = value->r - value->r * rgb.r;
value->g = value->g - value->g * rgb.g;
value->b = value->b - value->b * rgb.b;
break;
}
animation_render_frame(config->animations[i], pixels, num_pixels);
}
}
static int animation_compose_init(const struct device *dev) { return 0; }
static const struct animation_api animation_compose_api = {
.on_before_frame = NULL,
.on_after_frame = NULL,
.render_pixel = animation_compose_render_pixel,
.render_frame = animation_compose_render_frame,
};
#define ANIMATION_COMPOSE_DEVICE(idx) \
\
static const uint8_t animation_compose_##idx##_blending_modes[] = \
DT_INST_PROP(idx, blending_modes); \
\
static const struct device *animation_compose_##idx##_animations[] = { \
ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \
\
static struct animation_compose_config animation_compose_##idx##_config = { \
.animations = animation_compose_##idx##_animations, \
.animations_size = DT_INST_PROP_LEN(idx, animations), \
.blending_modes = animation_compose_##idx##_blending_modes, \
}; \
\
DEVICE_DT_INST_DEFINE(idx, &animation_compose_init, NULL, NULL, \

View file

@ -30,7 +30,10 @@ struct animation_ripple_event {
struct animation_ripple_config {
struct zmk_color_hsl *color_hsl;
size_t *pixel_map;
size_t pixel_map_size;
size_t event_buffer_size;
uint8_t blending_mode;
uint8_t distance_per_frame;
uint8_t ripple_width;
uint8_t event_frames;
@ -76,15 +79,38 @@ static int animation_ripple_on_key_press(const struct device *dev, const zmk_eve
return 0;
}
static void animation_ripple_on_after_frame(const struct device *dev) {
static void animation_ripple_render_frame(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
size_t *pixel_map = config->pixel_map;
size_t i = data->events_start;
while (i != data->events_end) {
struct animation_ripple_event *event = &data->event_buffer[i];
// Render all pixels for each event
for (int j = 0; j < config->pixel_map_size; ++j) {
uint8_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, j);
if (config->ripple_width > abs(pixel_distance - event->distance)) {
float intensity =
(float)abs(pixel_distance - event->distance) / (float)config->ripple_width;
struct zmk_color_rgb color = {
.r = intensity * data->color_rgb.r,
.g = intensity * data->color_rgb.g,
.b = intensity * data->color_rgb.b,
};
pixels[pixel_map[j]].value = zmk_apply_blending_mode(pixels[pixel_map[j]].value,
color, config->blending_mode);
}
}
// Update event counter
if (event->counter < config->event_frames) {
event->distance += config->distance_per_frame;
event->counter += 1;
@ -99,34 +125,6 @@ static void animation_ripple_on_after_frame(const struct device *dev) {
}
}
static void animation_ripple_render_pixel(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
size_t i = data->events_start;
while (i != data->events_end) {
const struct animation_ripple_event *event = &data->event_buffer[i];
uint16_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, pixel->id);
if (config->ripple_width > abs(pixel_distance - event->distance)) {
float intensity =
(float)abs(pixel_distance - event->distance) / (float)config->ripple_width;
value->r = intensity * data->color_rgb.r;
value->g = intensity * data->color_rgb.g;
value->b = intensity * data->color_rgb.b;
}
if (++i == config->event_buffer_size) {
i = 0;
}
}
}
static int animation_ripple_init(const struct device *dev) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
@ -137,9 +135,7 @@ static int animation_ripple_init(const struct device *dev) {
}
static const struct animation_api animation_ripple_api = {
.on_before_frame = NULL,
.on_after_frame = animation_ripple_on_after_frame,
.render_pixel = animation_ripple_render_pixel,
.render_frame = animation_ripple_render_frame,
};
#define ANIMATION_RIPPLE_DEVICE(idx) \
@ -154,16 +150,21 @@ static const struct animation_api animation_ripple_api = {
.num_events = 0, \
}; \
\
static size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \
\
static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \
\
static struct animation_ripple_config animation_ripple_##idx##_config = { \
.color_hsl = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \
.pixel_map = &animation_ripple_##idx##_pixel_map[0], \
.pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \
.event_buffer_size = DT_INST_PROP(idx, buffer_size), \
.blending_mode = DT_INST_PROP(idx, blending_mode), \
.distance_per_frame = \
(255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \
.ripple_width = DT_INST_PROP(idx, ripple_width) / 2, \
.event_frames = \
360 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \
255 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \
}; \
\
DEVICE_DT_INST_DEFINE(idx, &animation_ripple_init, NULL, &animation_ripple_##idx##_data, \

View file

@ -16,6 +16,8 @@
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct animation_solid_config {
size_t *pixel_map;
size_t pixel_map_size;
struct zmk_color_hsl *colors;
uint8_t num_colors;
uint16_t duration;
@ -31,7 +33,7 @@ struct animation_solid_data {
struct zmk_color_rgb current_rgb;
};
static void animation_solid_on_before_frame(const struct device *dev) {
static void animation_solid_tick(const struct device *dev) {
const struct animation_solid_config *config = dev->config;
struct animation_solid_data *data = dev->data;
@ -57,14 +59,16 @@ static void animation_solid_on_before_frame(const struct device *dev) {
data->counter = (data->counter + 1) % config->duration;
}
static void animation_solid_render_pixel(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value) {
const struct animation_solid_data *data = dev->data;
static void animation_solid_render_frame(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels) {
const struct animation_solid_config *config = dev->config;
struct animation_solid_data *data = dev->data;
value->r = data->current_rgb.r;
value->g = data->current_rgb.g;
value->b = data->current_rgb.b;
animation_solid_tick(dev);
for (size_t i = 0; i < config->pixel_map_size; ++i) {
pixels[config->pixel_map[i]].value = data->current_rgb;
}
}
static int animation_solid_init(const struct device *dev) {
@ -76,23 +80,30 @@ static int animation_solid_init(const struct device *dev) {
zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb);
// if (config->num_colors == 1) {
// return 0;
// }
// start timer here, so the only option is inline
return 0;
}
static const struct animation_api animation_solid_api = {
.on_before_frame = animation_solid_on_before_frame,
.on_after_frame = NULL,
.render_pixel = animation_solid_render_pixel,
.render_frame = animation_solid_render_frame,
};
#define ANIMATION_SOLID_DEVICE(idx) \
\
static struct animation_solid_data animation_solid_##idx##_data; \
\
static uint32_t animation_solid_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = \
DT_INST_PROP(idx, colors); \
static size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \
\
static uint32_t animation_solid_##idx##_colors[] = DT_INST_PROP(idx, colors); \
\
static struct animation_solid_config animation_solid_##idx##_config = { \
.pixel_map = &animation_ripple_##idx##_pixel_map[0], \
.pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \
.colors = (struct zmk_color_hsl *)animation_solid_##idx##_colors, \
.num_colors = DT_INST_PROP_LEN(idx, colors), \
.duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \

View file

@ -103,3 +103,36 @@ void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_colo
result->s = from->s - (from->s - to->s) * step;
result->l = from->l - (from->l - to->l) * step;
}
struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value,
struct zmk_color_rgb blend_value, uint8_t mode) {
switch (mode) {
case ZMK_ANIMATION_BLENDING_MODE_MULTIPLY:
base_value.r = base_value.r * blend_value.r;
base_value.g = base_value.g * blend_value.g;
base_value.b = base_value.b * blend_value.b;
break;
case ZMK_ANIMATION_BLENDING_MODE_LIGHTEN:
base_value.r = base_value.r > blend_value.r ? base_value.r : blend_value.r;
base_value.g = base_value.g > blend_value.g ? base_value.g : blend_value.g;
base_value.b = base_value.b > blend_value.b ? base_value.b : blend_value.b;
break;
case ZMK_ANIMATION_BLENDING_MODE_DARKEN:
base_value.r = base_value.r > blend_value.r ? blend_value.r : base_value.r;
base_value.g = base_value.g > blend_value.g ? blend_value.g : base_value.g;
base_value.b = base_value.b > blend_value.b ? blend_value.b : base_value.b;
break;
case ZMK_ANIMATION_BLENDING_MODE_SCREEN:
base_value.r = base_value.r + (1.0f - base_value.r) * blend_value.r;
base_value.g = base_value.g + (1.0f - base_value.g) * blend_value.g;
base_value.b = base_value.b + (1.0f - base_value.b) * blend_value.b;
break;
case ZMK_ANIMATION_BLENDING_MODE_SUBTRACT:
base_value.r = base_value.r - base_value.r * blend_value.r;
base_value.g = base_value.g - base_value.g * blend_value.g;
base_value.b = base_value.b - base_value.b * blend_value.b;
break;
}
return base_value;
}