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:
Fabian Schlenz 2019-06-05 06:25:40 +02:00
parent e8cf0e7ea2
commit b2ff3bdc54
5 changed files with 215 additions and 74 deletions

76
include/Animation.h Normal file
View 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);
};

View File

@ -30,8 +30,7 @@
.w and .h contain the dimensions of the image. .w and .h contain the dimensions of the image.
**/ **/
#ifndef Animations_H #pragma once
#define Animations_H
#include <Arduino.h> #include <Arduino.h>
#include "prototypes.h" #include "prototypes.h"
@ -39,5 +38,3 @@ extern AnimationData animation_koopa;
extern AnimationData animation_couple_rain; extern AnimationData animation_couple_rain;
extern AnimationData animation_couple_snow; extern AnimationData animation_couple_snow;
extern AnimationData animation_heart; extern AnimationData animation_heart;
#endif

View File

@ -1,23 +1,22 @@
#ifndef effect_animation_H #pragma once
#define effect_animation_H
#include "Effect.h" #include "Effect.h"
#include "prototypes.h" #include "prototypes.h"
#include "my_fastled.h" #include "my_fastled.h"
#include "Animation.h"
class AnimationEffect : public Effect { class AnimationEffect : public Effect {
private: private:
AnimationData *animation; Animation *animation;
int frame = 0; AnimationData *animation_data;
CRGB background_color; CRGB *bg_color;
int xOffset, yOffset; uint16_t xOffset;
unsigned long frameSince = 0; uint16_t yOffset;
public: public:
AnimationEffect(AnimationData *anim); AnimationEffect(AnimationData* anim) : AnimationEffect(anim, new CRGB(0x000000), 0, 0) {}
AnimationEffect(AnimationData *anim, CRGB background_color); AnimationEffect(AnimationData* anim, CRGB* background_color) : AnimationEffect(anim, background_color, 0, 0) {}
AnimationEffect(AnimationData *anim, CRGB bg_color, int x, int y); AnimationEffect(AnimationData* anim, CRGB* bg_color, int x, int y);
void start();
void stop();
void loop(); void loop();
void set(int i, CRGB* color);
uint16_t frameDelay(AnimationData* animation, int frame);
}; };
#endif

110
src/Animation.cpp Normal file
View 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];
}

View File

@ -1,63 +1,22 @@
#include "effect_animation.h" #include "effect_animation.h"
#include "functions.h" #include "functions.h"
AnimationEffect::AnimationEffect(AnimationData *anim) : AnimationEffect(anim, CRGB(0), 0, 0) {} AnimationEffect::AnimationEffect(AnimationData* anim, CRGB* bg, int x, int y) {
this->animation_data = anim;
AnimationEffect::AnimationEffect(AnimationData *anim, CRGB background_color) : AnimationEffect(anim, background_color, 0, 0) {} this->bg_color = bg;
this->xOffset = x;
AnimationEffect::AnimationEffect(AnimationData *anim, CRGB bg_color, int x, int y) { this->yOffset = y;
animation = anim;
background_color = bg_color;
xOffset = x;
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() { void AnimationEffect::loop() {
Serial.printf("Animation.loop. Animation is %p.", (void *)animation); this->animation->drawFrame();
CRGB colors[animation->color_count]; this->animation->advance();
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];
} }