Moved the animation display code into its own class which can and will also be used to display static images and sprites.
This commit is contained in:
parent
e8cf0e7ea2
commit
b2ff3bdc54
76
include/Animation.h
Normal file
76
include/Animation.h
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
Animations are structured in AnimationData as follows:
|
||||
|
||||
.colors contains .color_count*3 uint8_t values for R, G and B.
|
||||
|
||||
.offsets contains the frame start offsets within .data + the length of the data. (So
|
||||
you can always do something like `for (int i=anim.offsets[i]; i<anim.offsets[i+1]; i++)`.
|
||||
Accordingly it has a length of .frame_count+1.
|
||||
|
||||
.data contains all frames data with a run-length compression with escape char 255.
|
||||
This data references the index of one or multiple pixels in .colors. It starts at the
|
||||
top left pixel and walks each row to the right before starting the next row.
|
||||
To decode it: Start at a frame (indicated by .offsets). Get one byte as x.
|
||||
If x is <255: This is a single pixel of color x.
|
||||
if x is 255: Run-length-encoding. The next byte indicates of often the byte after that
|
||||
will be repeated. So, {255, 4, 10} is equal to {10, 10, 10, 10}.
|
||||
A special case that may happen in larger GIFs is that there are more than 255 repetitions
|
||||
of a color. Those will be split, so 355*color #10 will be: {255, 255, 10, 255, 100, 10},
|
||||
e.g. 255*10 + 100*10. Usually this shouldn't need special handling within a decoder.
|
||||
Regarding colors in .data:
|
||||
Color 0 means "keep the color from the previous frame". This color should never appear on frame #0.
|
||||
Color 1 means "show the background color".
|
||||
All other color values point to a color in .colors - with an offset of 2.
|
||||
So if in .data there's a color 3, paint this pixel in .colors[1].
|
||||
|
||||
.individual_delays contains either 1 or .frame_count delays. They are given in ms and
|
||||
indicate how long the matching frame should be displayed. If all times are equal, then
|
||||
it contains only one entry and .individual_delays will be false.
|
||||
|
||||
.w and .h contain the dimensions of the image.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "prototypes.h"
|
||||
#include "my_fastled.h"
|
||||
|
||||
class Animation {
|
||||
protected:
|
||||
AnimationData* data;
|
||||
unsigned long currentFrameSince;
|
||||
uint8_t currentFrame = 0;
|
||||
uint8_t* animation_data;
|
||||
CRGB* colors;
|
||||
CRGB* fgColor;
|
||||
CRGB* bgColor;
|
||||
uint16_t xOffset = 0;
|
||||
uint16_t yOffset = 0;
|
||||
|
||||
virtual CRGB* getColor(uint8_t color_index);
|
||||
void drawFrame(uint8_t frame_index);
|
||||
void drawPixel(int index, CRGB* color);
|
||||
uint16_t getFrameDelay(int frame);
|
||||
public:
|
||||
Animation(AnimationData* d) : Animation(d, new CRGB(0x000000), 0, 0) {};
|
||||
Animation(AnimationData* d, CRGB* bg_color): Animation(d, bg_color, 0, 0) {};
|
||||
Animation(AnimationData* d, CRGB* bg_color, uint16_t xOffset, uint16_t yOffset);
|
||||
virtual ~Animation();
|
||||
void draw();
|
||||
void drawFrame();
|
||||
virtual bool advance();
|
||||
};
|
||||
|
||||
class Image : public Animation {
|
||||
protected:
|
||||
virtual bool advance();
|
||||
};
|
||||
|
||||
class Sprite : public Image {
|
||||
protected:
|
||||
virtual CRGB* getColor(uint8_t color_index);
|
||||
public:
|
||||
void setFgColor(CRGB c);
|
||||
void setBgColor(CRGB c);
|
||||
void setColors(CRGB c1, CRGB c2);
|
||||
};
|
@ -30,8 +30,7 @@
|
||||
.w and .h contain the dimensions of the image.
|
||||
**/
|
||||
|
||||
#ifndef Animations_H
|
||||
#define Animations_H
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "prototypes.h"
|
||||
|
||||
@ -39,5 +38,3 @@ extern AnimationData animation_koopa;
|
||||
extern AnimationData animation_couple_rain;
|
||||
extern AnimationData animation_couple_snow;
|
||||
extern AnimationData animation_heart;
|
||||
|
||||
#endif
|
||||
|
@ -1,23 +1,22 @@
|
||||
#ifndef effect_animation_H
|
||||
#define effect_animation_H
|
||||
#pragma once
|
||||
|
||||
#include "Effect.h"
|
||||
#include "prototypes.h"
|
||||
#include "my_fastled.h"
|
||||
#include "Animation.h"
|
||||
|
||||
class AnimationEffect : public Effect {
|
||||
private:
|
||||
AnimationData *animation;
|
||||
int frame = 0;
|
||||
CRGB background_color;
|
||||
int xOffset, yOffset;
|
||||
unsigned long frameSince = 0;
|
||||
Animation *animation;
|
||||
AnimationData *animation_data;
|
||||
CRGB *bg_color;
|
||||
uint16_t xOffset;
|
||||
uint16_t yOffset;
|
||||
public:
|
||||
AnimationEffect(AnimationData *anim);
|
||||
AnimationEffect(AnimationData *anim, CRGB background_color);
|
||||
AnimationEffect(AnimationData *anim, CRGB bg_color, int x, int y);
|
||||
AnimationEffect(AnimationData* anim) : AnimationEffect(anim, new CRGB(0x000000), 0, 0) {}
|
||||
AnimationEffect(AnimationData* anim, CRGB* background_color) : AnimationEffect(anim, background_color, 0, 0) {}
|
||||
AnimationEffect(AnimationData* anim, CRGB* bg_color, int x, int y);
|
||||
void start();
|
||||
void stop();
|
||||
void loop();
|
||||
void set(int i, CRGB* color);
|
||||
uint16_t frameDelay(AnimationData* animation, int frame);
|
||||
};
|
||||
#endif
|
||||
|
110
src/Animation.cpp
Normal file
110
src/Animation.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
Animations are structured in AnimationData as follows:
|
||||
|
||||
.colors contains .color_count*3 uint8_t values for R, G and B.
|
||||
|
||||
.offsets contains the frame start offsets within .data + the length of the data. (So
|
||||
you can always do something like `for (int i=anim.offsets[i]; i<anim.offsets[i+1]; i++)`.
|
||||
Accordingly it has a length of .frame_count+1.
|
||||
|
||||
.data contains all frames data with a run-length compression with escape char 255.
|
||||
This data references the index of one or multiple pixels in .colors. It starts at the
|
||||
top left pixel and walks each row to the right before starting the next row.
|
||||
To decode it: Start at a frame (indicated by .offsets). Get one byte as x.
|
||||
If x is <255: This is a single pixel of color x.
|
||||
if x is 255: Run-length-encoding. The next byte indicates of often the byte after that
|
||||
will be repeated. So, {255, 4, 10} is equal to {10, 10, 10, 10}.
|
||||
A special case that may happen in larger GIFs is that there are more than 255 repetitions
|
||||
of a color. Those will be split, so 355*color #10 will be: {255, 255, 10, 255, 100, 10},
|
||||
e.g. 255*10 + 100*10. Usually this shouldn't need special handling within a decoder.
|
||||
Regarding colors in .data:
|
||||
Color 0 means "keep the color from the previous frame". This color should never appear on frame #0.
|
||||
Color 1 means "show the background color".
|
||||
All other color values point to a color in .colors - with an offset of 2.
|
||||
So if in .data there's a color 3, paint this pixel in .colors[1].
|
||||
|
||||
.individual_delays contains either 1 or .frame_count delays. They are given in ms and
|
||||
indicate how long the matching frame should be displayed. If all times are equal, then
|
||||
it contains only one entry and .individual_delays will be false.
|
||||
|
||||
.w and .h contain the dimensions of the image.
|
||||
**/
|
||||
|
||||
#include "Animation.h"
|
||||
#include "functions.h"
|
||||
|
||||
Animation::Animation(AnimationData *d, CRGB *bg_color, uint16_t x, uint16_t y) {
|
||||
this->data = d;
|
||||
this->bgColor = bg_color;
|
||||
this->xOffset = x;
|
||||
this->yOffset = y;
|
||||
|
||||
this->colors = new CRGB[this->data->color_count];
|
||||
uint8_t *color_data = new uint8_t[this->data->color_count * 3];
|
||||
memcpy_P(color_data, this->data->colors, this->data->color_count * 3);
|
||||
for (int i = 0; i<this->data->color_count; i++) colors[i] = CRGB(color_data[i * 3], color_data[i * 3 + 1], color_data[i * 3 + 2]);
|
||||
delete color_data;
|
||||
int length = this->data->offsets[this->data->frame_count];
|
||||
this->animation_data = new uint8_t[length];
|
||||
memcpy_P(this->animation_data, this->data->data, length);
|
||||
}
|
||||
|
||||
Animation::~Animation() {
|
||||
delete this->colors;
|
||||
delete this->animation_data;
|
||||
}
|
||||
|
||||
void Animation::draw() {
|
||||
for (uint16_t i=0; i<this->currentFrame; i++) this->drawFrame(i);
|
||||
}
|
||||
|
||||
void Animation::drawFrame() {
|
||||
this->drawFrame(currentFrame);
|
||||
}
|
||||
|
||||
void Animation::drawFrame(uint8_t frame_index) {
|
||||
uint16_t led_index = 0;
|
||||
for (uint16_t i=this->data->offsets[frame_index]; i<this->data->offsets[frame_index + 1]; i++) {
|
||||
uint8_t color_index;
|
||||
uint8_t count = 1;
|
||||
if (this->animation_data[i]==255) { // Run-length encoded data
|
||||
color_index = this->animation_data[i + 2];
|
||||
count = this->animation_data[i + 1];
|
||||
i += 2;
|
||||
} else {
|
||||
color_index = this->animation_data[i];
|
||||
}
|
||||
|
||||
if (color_index == 0) { // color #0 = skip this pixels
|
||||
led_index += count;
|
||||
} else {
|
||||
CRGB* color = this->getColor(color_index);
|
||||
for (int j=0; j<count; j++) this->drawPixel(led_index++, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Animation::advance() {
|
||||
if (this->currentFrameSince == 0) {
|
||||
this->currentFrameSince = millis();
|
||||
} else if (this->currentFrameSince + this->getFrameDelay(currentFrame) < millis() || this->currentFrameSince > millis()) {
|
||||
currentFrame = (currentFrame + 1) % this->data->frame_count;
|
||||
this->currentFrameSince = millis();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Animation::drawPixel(int index, CRGB* color) {
|
||||
setPixel(this->xOffset + (index % this->data->w), this->yOffset + (index / this->data->h), *color);
|
||||
}
|
||||
|
||||
uint16_t Animation::getFrameDelay(int frame) {
|
||||
if (this->data->individual_delays) return this->data->delays[frame];
|
||||
return this->data->delays[0];
|
||||
}
|
||||
|
||||
CRGB* Animation::getColor(uint8_t index) {
|
||||
if (index==1) return this->bgColor;
|
||||
else return &this->colors[index - 2];
|
||||
}
|
@ -1,63 +1,22 @@
|
||||
#include "effect_animation.h"
|
||||
#include "functions.h"
|
||||
|
||||
AnimationEffect::AnimationEffect(AnimationData *anim) : AnimationEffect(anim, CRGB(0), 0, 0) {}
|
||||
|
||||
AnimationEffect::AnimationEffect(AnimationData *anim, CRGB background_color) : AnimationEffect(anim, background_color, 0, 0) {}
|
||||
|
||||
AnimationEffect::AnimationEffect(AnimationData *anim, CRGB bg_color, int x, int y) {
|
||||
animation = anim;
|
||||
background_color = bg_color;
|
||||
xOffset = x;
|
||||
yOffset = y;
|
||||
AnimationEffect::AnimationEffect(AnimationData* anim, CRGB* bg, int x, int y) {
|
||||
this->animation_data = anim;
|
||||
this->bg_color = bg;
|
||||
this->xOffset = x;
|
||||
this->yOffset = y;
|
||||
}
|
||||
|
||||
void AnimationEffect::start() {
|
||||
this->animation = new Animation(this->animation_data, this->bg_color, this->xOffset, this->yOffset);
|
||||
}
|
||||
|
||||
void AnimationEffect::stop() {
|
||||
delete this->animation;
|
||||
}
|
||||
|
||||
void AnimationEffect::loop() {
|
||||
Serial.printf("Animation.loop. Animation is %p.", (void *)animation);
|
||||
CRGB colors[animation->color_count];
|
||||
int led_index = 0;
|
||||
uint8_t *color_data = new uint8_t[animation->color_count * 3];
|
||||
memcpy_P(color_data, animation->colors, animation->color_count * 3);
|
||||
for (int i = 0; i < animation->color_count; i++) colors[i] = CRGB(color_data[i * 3], color_data[i * 3 + 1], color_data[i * 3 + 2]);
|
||||
free(color_data);
|
||||
// Data is stored in progmem, so get it from there.
|
||||
int length = animation->offsets[frame + 1] - animation->offsets[frame];
|
||||
uint8_t *data = new uint8_t[length];
|
||||
memcpy_P(data, animation->data + animation->offsets[frame], length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
uint8_t color_index;
|
||||
uint8_t count;
|
||||
if (data[i] == 255) { // Run-length encoded data
|
||||
color_index = data[i + 2];
|
||||
count = data[i + 1];
|
||||
i += 2;
|
||||
} else {
|
||||
color_index = data[i];
|
||||
count = 1;
|
||||
}
|
||||
|
||||
if (color_index == 0) { // color #0 = skip this pixels
|
||||
led_index += count;
|
||||
} else {
|
||||
CRGB* color;
|
||||
if (color_index == 1) {
|
||||
color = &background_color;
|
||||
} else if (color_index >= 2) {
|
||||
color = &colors[color_index - 2];
|
||||
}
|
||||
|
||||
for (int j = 0; j < count; j++) set(led_index++, color);
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
if (frameSince == 0 || frameSince + frameDelay(animation, frame) < millis() || frameSince > millis()) {
|
||||
frame = (frame + 1) % animation->frame_count;
|
||||
frameSince = millis();
|
||||
}
|
||||
}
|
||||
void AnimationEffect::set(int i, CRGB* color) {
|
||||
setPixel(xOffset + (i % animation->w), yOffset + (i / animation->h), *color);
|
||||
}
|
||||
uint16_t AnimationEffect::frameDelay(AnimationData* animation, int frame) {
|
||||
if (animation->individual_delays) return animation->delays[frame];
|
||||
return animation->delays[0];
|
||||
this->animation->drawFrame();
|
||||
this->animation->advance();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user