Files
WLED/usermods/pov_display/bmpimage.cpp
Liliputech da7f107273 fix POV Display usermod (#4427)
* 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
2025-08-29 20:42:54 +02:00

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);
}