* POV Display usermod this usermod adds a new effect called "POV Image". To get it to work: - read the README :) - upload a bmp image to the ESP filesystem using "/edit" url. - select "POV Image" effect. - set the filename (ie: "/myimage.bmp") as segment name. - rotate the segment at approximately 20 RPM. - enjoy the show! * improve file extension checks * improve README, remove PNGdec reference, clean usermod * restrain to esp32 platform + reduce memory footprint with malloc
147 lines
3.4 KiB
C++
147 lines
3.4 KiB
C++
#include "bmpimage.h"
|
|
#define BUF_SIZE 64000
|
|
|
|
byte * _buffer = nullptr;
|
|
|
|
uint16_t read16(File &f) {
|
|
uint16_t result;
|
|
f.read((uint8_t *)&result,2);
|
|
return result;
|
|
}
|
|
|
|
uint32_t read32(File &f) {
|
|
uint32_t result;
|
|
f.read((uint8_t *)&result,4);
|
|
return result;
|
|
}
|
|
|
|
bool BMPimage::init(const char * fn) {
|
|
File bmpFile;
|
|
int bmpDepth;
|
|
//first, check if filename exists
|
|
if (!WLED_FS.exists(fn)) {
|
|
return false;
|
|
}
|
|
|
|
bmpFile = WLED_FS.open(fn);
|
|
if (!bmpFile) {
|
|
_valid=false;
|
|
return false;
|
|
}
|
|
|
|
//so, the file exists and is opened
|
|
// Parse BMP header
|
|
uint16_t header = read16(bmpFile);
|
|
if(header != 0x4D42) { // BMP signature
|
|
_valid=false;
|
|
bmpFile.close();
|
|
return false;
|
|
}
|
|
|
|
//read and ingnore file size
|
|
read32(bmpFile);
|
|
(void)read32(bmpFile); // Read & ignore creator bytes
|
|
_imageOffset = read32(bmpFile); // Start of image data
|
|
// Read DIB header
|
|
read32(bmpFile);
|
|
_width = read32(bmpFile);
|
|
_height = read32(bmpFile);
|
|
if(read16(bmpFile) != 1) { // # planes -- must be '1'
|
|
_valid=false;
|
|
bmpFile.close();
|
|
return false;
|
|
}
|
|
bmpDepth = read16(bmpFile); // bits per pixel
|
|
if((bmpDepth != 24) || (read32(bmpFile) != 0)) { // 0 = uncompressed {
|
|
_width=0;
|
|
_valid=false;
|
|
bmpFile.close();
|
|
return false;
|
|
}
|
|
// If _height is negative, image is in top-down order.
|
|
// This is not canon but has been observed in the wild.
|
|
if(_height < 0) {
|
|
_height = -_height;
|
|
}
|
|
//now, we have successfully got all the basics
|
|
// BMP rows are padded (if needed) to 4-byte boundary
|
|
_rowSize = (_width * 3 + 3) & ~3;
|
|
//check image size - if it is too large, it will be unusable
|
|
if (_rowSize*_height>BUF_SIZE) {
|
|
_valid=false;
|
|
bmpFile.close();
|
|
return false;
|
|
}
|
|
|
|
bmpFile.close();
|
|
// Ensure filename fits our buffer (segment name length constraint).
|
|
size_t len = strlen(fn);
|
|
if (len > WLED_MAX_SEGNAME_LEN) {
|
|
return false;
|
|
}
|
|
strncpy(filename, fn, sizeof(filename));
|
|
filename[sizeof(filename) - 1] = '\0';
|
|
_valid = true;
|
|
return true;
|
|
}
|
|
|
|
void BMPimage::clear(){
|
|
strcpy(filename, "");
|
|
_width=0;
|
|
_height=0;
|
|
_rowSize=0;
|
|
_imageOffset=0;
|
|
_loaded=false;
|
|
_valid=false;
|
|
}
|
|
|
|
bool BMPimage::load(){
|
|
const size_t size = (size_t)_rowSize * (size_t)_height;
|
|
if (size > BUF_SIZE) {
|
|
return false;
|
|
}
|
|
File bmpFile = WLED_FS.open(filename);
|
|
if (!bmpFile) {
|
|
return false;
|
|
}
|
|
|
|
if (_buffer != nullptr) free(_buffer);
|
|
_buffer = (byte*)malloc(size);
|
|
if (_buffer == nullptr) return false;
|
|
|
|
bmpFile.seek(_imageOffset);
|
|
const size_t readBytes = bmpFile.read(_buffer, size);
|
|
bmpFile.close();
|
|
if (readBytes != size) {
|
|
_loaded = false;
|
|
return false;
|
|
}
|
|
_loaded = true;
|
|
return true;
|
|
}
|
|
|
|
byte* BMPimage::line(uint16_t n){
|
|
if (_loaded) {
|
|
return (_buffer+n*_rowSize);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
uint32_t BMPimage::pixelColor(uint16_t x, uint16_t y){
|
|
uint32_t pos;
|
|
byte b,g,r; //colors
|
|
if (! _loaded) {
|
|
return 0;
|
|
}
|
|
if ( (x>=_width) || (y>=_height) ) {
|
|
return 0;
|
|
}
|
|
pos=y*_rowSize + 3*x;
|
|
//get colors. Note that in BMP files, they go in BGR order
|
|
b= _buffer[pos++];
|
|
g= _buffer[pos++];
|
|
r= _buffer[pos];
|
|
return (r<<16|g<<8|b);
|
|
}
|