2019-06-05 04:25:40 +00:00
|
|
|
/**
|
2019-06-07 04:24:16 +00:00
|
|
|
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.
|
|
|
|
**/
|
2019-06-05 04:25:40 +00:00
|
|
|
|
|
|
|
#include "Animation.h"
|
|
|
|
#include "functions.h"
|
2019-06-11 17:48:09 +00:00
|
|
|
#include "Window.h"
|
2019-06-18 16:10:58 +00:00
|
|
|
#include <FS.h>
|
2019-06-05 04:25:40 +00:00
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
bool Animation::_load_from_file(const char* filename) {
|
|
|
|
LOGln("Animation * Loading animation %s...", filename);
|
|
|
|
if (!SPIFFS.begin()) {
|
|
|
|
LOGln("Animation * Could not mount SPIFFS file system.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
File file = SPIFFS.open(filename, "r");
|
|
|
|
if (!file) {
|
|
|
|
LOGln("Animation * Could not open file.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t size = file.size();
|
|
|
|
LOGln("Animation * Opened %s with %d bytes.", filename, size);
|
|
|
|
|
|
|
|
if (file.available() < 6) {
|
|
|
|
LOGln("Animation * Less than 6 bytes available. Stopping.");
|
|
|
|
file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file.read() != 'P' || file.read() != 'I' || file.read() != 'A') {
|
|
|
|
LOGln("Animation * Magic number not found");
|
|
|
|
file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t version = file.read();
|
|
|
|
if (version!=1) {
|
|
|
|
LOGln("Animation * Unexpected version %d, expected version 1.", version);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t expected_size = (file.read() << 8) | file.read();
|
|
|
|
if (expected_size != size) {
|
|
|
|
LOGln("Animation * Expected file to have %d bytes, but found %d bytes.", expected_size, size);
|
|
|
|
file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file.available() != size - 6) {
|
|
|
|
LOGln("Animation * Expected file to have %d bytes available, but found %d bytes available.", size - 6, file.available());
|
|
|
|
file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can be sure to have the right amount of bytes available for reading
|
|
|
|
|
|
|
|
_width = file.read();
|
|
|
|
LOGln("Animation * width: %d", _width);
|
|
|
|
_height = file.read();
|
|
|
|
LOGln("Animation * height: %d", _height);
|
|
|
|
|
|
|
|
_frame_count = file.read();
|
|
|
|
LOGln("Animation * frame_count: %d", _frame_count);
|
|
|
|
_color_count = file.read();
|
|
|
|
LOGln("Animation * color_count: %d", _color_count);
|
|
|
|
|
|
|
|
LOGln("Animation * Loading colors...");
|
|
|
|
_colors = new CRGB*[_color_count];
|
|
|
|
char* temp = new char[_color_count*3];
|
|
|
|
int bytes_read = file.readBytes(temp, _color_count*3);
|
|
|
|
LOGln("Animation * Read %d bytes.", bytes_read);
|
|
|
|
for (int i=0; i<_color_count; i++) {
|
|
|
|
_colors[i] = new CRGB(temp[i*3], temp[i*3+1], temp[i*3+2]);
|
|
|
|
}
|
|
|
|
delete [] temp;
|
|
|
|
LOGln("Animation * Color loading done.");
|
|
|
|
|
|
|
|
LOG("Animation * Loading frame times...");
|
|
|
|
_frame_times = new uint16_t[_frame_count];
|
|
|
|
for (int i=0; i<_frame_count; i++) {
|
|
|
|
_frame_times[i] = (file.read() << 8) | file.read();
|
|
|
|
}
|
|
|
|
LOGln(" done.");
|
|
|
|
|
|
|
|
LOGln("Animation * Loading frame lengths...");
|
|
|
|
_frame_data_lengths = new uint16_t[_frame_count];
|
|
|
|
temp = new char[_frame_count*2];
|
|
|
|
bytes_read = file.readBytes(temp, _frame_count*2);
|
|
|
|
LOGln("Animation * Read %d bytes.", bytes_read);
|
|
|
|
for (int i=0; i<_frame_count; i++) {
|
|
|
|
//LOGln("Animation * Raw frame lengths: %d, %d", temp[i*2], temp[i*2+1]);
|
|
|
|
_frame_data_lengths[i] = (temp[i*2]<<8) | temp[i*2+1];
|
|
|
|
}
|
|
|
|
delete [] temp;
|
|
|
|
LOGln("Animation * Frame length loading done.");
|
|
|
|
|
|
|
|
LOGln("Animation * Loading frame data...");
|
|
|
|
_frame_data = new uint8_t*[_frame_count];
|
|
|
|
for (int i=0; i<_frame_count; i++) {
|
|
|
|
uint16_t fl = _frame_data_lengths[i];
|
|
|
|
LOGln("Animation * Loading frame %d with %d bytes...", i, fl);
|
|
|
|
_frame_data[i] = new uint8_t[fl];
|
|
|
|
/*for (int b=0; b<fl; b++) {
|
|
|
|
_frame_data[i][b] = file.read();
|
|
|
|
}*/
|
|
|
|
file.readBytes((char*)_frame_data[i], fl);
|
|
|
|
}
|
|
|
|
LOGln("Animation * Frame data loaded.");
|
|
|
|
|
|
|
|
if (file.position() != size) {
|
|
|
|
LOGln("Animation * Expected position to be %d, but are at %d.", size, file.position());
|
|
|
|
file.close();
|
|
|
|
return false;
|
|
|
|
}
|
2019-06-05 04:25:40 +00:00
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
file.close();
|
|
|
|
|
|
|
|
LOGln("Animation * Loading completed successfully.");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Animation::Animation(const char* filename, Window* win) {
|
|
|
|
_window = win;
|
|
|
|
if (_load_from_file(filename)) {
|
|
|
|
_endFrame = _frame_count-1;
|
|
|
|
_data_valid = true;
|
|
|
|
} else {
|
|
|
|
_endFrame = 0;
|
|
|
|
_data_valid = false;
|
|
|
|
}
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-07 04:07:29 +00:00
|
|
|
void Animation::setFgColor(CRGB* fg_color) {
|
|
|
|
this->fgColor = fg_color;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setBgColor(CRGB* bg_color) {
|
|
|
|
this->bgColor = bg_color;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Animation::invert() {
|
|
|
|
if (this->fgColor == NULL) return false;
|
|
|
|
CRGB* temp = this->fgColor;
|
|
|
|
this->fgColor = this->bgColor;
|
|
|
|
this->bgColor = temp;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setOffsets(int8_t x, int8_t y) {
|
|
|
|
this->xOffset = x;
|
|
|
|
this->yOffset = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setStartFrame(uint8_t sf) {
|
2019-06-18 16:10:58 +00:00
|
|
|
if (_data_valid && sf <= _endFrame && sf < _frame_count ) _startFrame = sf;
|
2019-06-07 04:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setEndFrame(uint8_t ef) {
|
2019-06-18 16:10:58 +00:00
|
|
|
if (_data_valid && ef >= _startFrame && ef < _frame_count) _endFrame = ef;
|
2019-06-07 04:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setFrameRange(uint8_t sf, uint8_t ef) {
|
2019-06-18 16:10:58 +00:00
|
|
|
if (_data_valid && sf<=ef && ef < _frame_count) {
|
|
|
|
_startFrame = sf;
|
|
|
|
_endFrame = ef;
|
2019-06-07 04:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::setSingleFrame(uint8_t frame) {
|
2019-06-18 16:10:58 +00:00
|
|
|
if (_data_valid && frame < _frame_count) {
|
|
|
|
_startFrame = frame;
|
|
|
|
_endFrame = frame;
|
2019-06-07 04:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-05 04:25:40 +00:00
|
|
|
Animation::~Animation() {
|
2019-06-18 16:10:58 +00:00
|
|
|
for (int i=0; i<_color_count; i++) delete _colors[i];
|
|
|
|
delete [] _colors;
|
|
|
|
if (fgColor) delete fgColor;
|
|
|
|
delete bgColor;
|
|
|
|
delete [] _frame_data_lengths;
|
|
|
|
delete [] _frame_times;
|
|
|
|
for (int i=0; i<_frame_count; i++) {
|
|
|
|
delete [] _frame_data[i];
|
|
|
|
}
|
|
|
|
delete [] _frame_data;
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
void Animation::draw() {
|
|
|
|
for (uint16_t i=0; i<this->currentFrame; i++) this->drawFrame(i);
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
void Animation::drawFrame() {
|
|
|
|
this->drawFrame(currentFrame);
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
void Animation::drawFrame(uint8_t frame_index) {
|
2019-06-05 04:25:40 +00:00
|
|
|
uint16_t led_index = 0;
|
2019-06-18 16:10:58 +00:00
|
|
|
if (!_data_valid) {
|
|
|
|
CRGB red(0xFF0000);
|
|
|
|
CRGB black(0x000000);
|
|
|
|
for (int x=0; x<_window->width; x++) for (int y=0; y<_window->height; y++) {
|
|
|
|
_window->setPixel(x, y, (y*_window->width+x) % 2 ? &red : &black);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (uint16_t i=0; i<_frame_data_lengths[frame_index]; i++) {
|
2019-06-05 04:25:40 +00:00
|
|
|
uint8_t color_index;
|
|
|
|
uint8_t count = 1;
|
2019-06-18 16:10:58 +00:00
|
|
|
if (_frame_data[frame_index][i]==255) { // Run-length encoded data
|
|
|
|
color_index = _frame_data[frame_index][i+2];
|
|
|
|
count = _frame_data[frame_index][i+1];
|
2019-06-05 04:25:40 +00:00
|
|
|
i += 2;
|
|
|
|
} else {
|
2019-06-18 16:10:58 +00:00
|
|
|
color_index = _frame_data[frame_index][i];
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (color_index == 0) { // color #0 = skip this pixels
|
|
|
|
led_index += count;
|
|
|
|
} else {
|
|
|
|
CRGB* color = this->getColor(color_index);
|
2019-06-18 16:10:58 +00:00
|
|
|
for (int j=0; j<count; j++) this->drawPixel(led_index++, color);
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Animation::advance() {
|
2019-06-18 16:10:58 +00:00
|
|
|
if (!_data_valid) return false;
|
2019-06-05 04:25:40 +00:00
|
|
|
if (this->currentFrameSince == 0) {
|
|
|
|
this->currentFrameSince = millis();
|
|
|
|
} else if (this->currentFrameSince + this->getFrameDelay(currentFrame) < millis() || this->currentFrameSince > millis()) {
|
2019-06-06 04:40:30 +00:00
|
|
|
currentFrame++;
|
2019-06-18 16:10:58 +00:00
|
|
|
if (currentFrame > _endFrame) currentFrame = _startFrame;
|
2019-06-05 04:25:40 +00:00
|
|
|
this->currentFrameSince = millis();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-18 16:10:58 +00:00
|
|
|
void Animation::drawPixel(int index, CRGB* color) {
|
|
|
|
uint8_t x = this->xOffset + (index % _width);
|
|
|
|
uint8_t y = this->yOffset + (index / _height);
|
|
|
|
_window->setPixel(x, y, color);
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t Animation::getFrameDelay(int frame) {
|
2019-06-18 16:10:58 +00:00
|
|
|
return _frame_times[frame];
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CRGB* Animation::getColor(uint8_t index) {
|
|
|
|
if (index==1) return this->bgColor;
|
2019-06-12 04:30:26 +00:00
|
|
|
else if (this->fgColor != NULL) return this->fgColor;
|
2019-06-18 16:10:58 +00:00
|
|
|
else return _colors[index - 2];
|
2019-06-05 04:25:40 +00:00
|
|
|
}
|