/** 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 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; icurrentFrame; 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; jdrawPixel(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]; }