pitrix/src/Animation.cpp

296 lines
8.8 KiB
C++

/**
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"
#include "Window.h"
#include <FS.h>
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() < 0 || file.available() + 6 != size) {
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();
DBG("Animation * width: %d", _width);
_height = file.read();
DBG("Animation * height: %d", _height);
_frame_count = file.read();
DBG("Animation * frame_count: %d", _frame_count);
_color_count = file.read();
DBG("Animation * color_count: %d", _color_count);
DBG("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;
DBG("Animation * Color loading done.");
DBG("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();
}
DBG(" done.");
DBG("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);
DBG("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;
DBG("Animation * Frame length loading done.");
DBG("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];
DBG("Animation * Loading frame %d/%d with %d bytes...", i, _frame_count, fl);
_frame_data[i] = new uint8_t[fl];
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;
}
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;
}
}
void Animation::setFgColor(uint32_t c) {
if (this->fgColor) delete this->fgColor;
this->fgColor = new CRGB(c);
}
void Animation::setBgColor(uint32_t c) {
if (this->bgColor) delete this->bgColor;
this->bgColor = new CRGB(c);
}
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) {
if (_data_valid && sf <= _endFrame && sf < _frame_count ) _startFrame = sf;
}
void Animation::setEndFrame(uint8_t ef) {
if (_data_valid && ef >= _startFrame && ef < _frame_count) _endFrame = ef;
}
void Animation::setFrameRange(uint8_t sf, uint8_t ef) {
if (_data_valid && sf<=ef && ef < _frame_count) {
_startFrame = sf;
_endFrame = ef;
}
}
void Animation::setSingleFrame(uint8_t frame) {
if (_data_valid && frame < _frame_count) {
_startFrame = frame;
_endFrame = frame;
}
}
Animation::~Animation() {
for (int i=0; i<_color_count; i++) delete _colors[i];
DBG("Animation * Deleting _colors...");
if (_colors) delete[] _colors;
DBG("Animation * Deleting fgColor...");
if (fgColor != NULL) delete fgColor;
DBG("Animation * Deleting bgColor...");
if (bgColor != NULL) delete bgColor;
DBG("Animation * Deleting _frame_data_lengths...");
if (_frame_data_lengths) delete[] _frame_data_lengths;
DBG("Animation * Deleting _frame_times...");
if (_frame_times) delete[] _frame_times;
for (int i=0; i<_frame_count; i++) {
delete[] _frame_data[i];
}
DBG("Animation * Deleting _frame_data...");
if (_frame_data) delete[] _frame_data;
LOGln("Animation * Deletion done.");
}
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;
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 + y) % 2 ? &red : &black);
}
return;
}
for (uint16_t i=0; i<_frame_data_lengths[frame_index]; i++) {
uint8_t color_index;
uint8_t count = 1;
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];
i += 2;
} else {
color_index = _frame_data[frame_index][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 (!_data_valid) return false;
if (this->currentFrameSince == 0) {
this->currentFrameSince = millis();
} else if (this->currentFrameSince + this->getFrameDelay(currentFrame) < millis() || this->currentFrameSince > millis()) {
currentFrame++;
if (currentFrame > _endFrame) currentFrame = _startFrame;
this->currentFrameSince = millis();
return true;
}
return false;
}
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);
}
uint16_t Animation::getFrameDelay(int frame) {
return _frame_times[frame];
}
CRGB* Animation::getColor(uint8_t index) {
if (index==1) return this->bgColor;
else if (this->fgColor != NULL) return this->fgColor;
else return _colors[index - 2];
}