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:
		
							
								
								
									
										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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user