diff --git a/usermods/pov_display/bmpimage.cpp b/usermods/pov_display/bmpimage.cpp index 2aea5c8d6e..7e56cf4944 100644 --- a/usermods/pov_display/bmpimage.cpp +++ b/usermods/pov_display/bmpimage.cpp @@ -38,6 +38,11 @@ bool BMPimage::init(const char * fn) { return false; } + // Past this point image metadata is re-parsed, so any previously loaded + // image must be treated as unloaded: its pixel buffer no longer matches + // the new metadata, and load() may free the buffer even when it fails. + _loaded = false; + //read and ingnore file size read32(bmpFile); (void)read32(bmpFile); // Read & ignore creator bytes @@ -46,6 +51,13 @@ bool BMPimage::init(const char * fn) { read32(bmpFile); _width = read32(bmpFile); _height = read32(bmpFile); + // Reject non-positive widths: callers use width() as a divisor, so a + // zero width would cause a division by zero in the POV effect + if(_width <= 0) { + _valid=false; + bmpFile.close(); + return false; + } if(read16(bmpFile) != 1) { // # planes -- must be '1' _valid=false; bmpFile.close(); diff --git a/usermods/pov_display/pov.cpp b/usermods/pov_display/pov.cpp index ea5a43ed68..0e54fe4a7a 100644 --- a/usermods/pov_display/pov.cpp +++ b/usermods/pov_display/pov.cpp @@ -2,46 +2,42 @@ POV::POV() {} -void POV::showLine(const byte * line, uint16_t size){ - uint16_t i, pos; - uint8_t r, g, b; - if (!line) { - // All-black frame on null input - for (i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, CRGB::Black); - } - strip.show(); - lastLineUpdate = micros(); - return; - } - for (i = 0; i < SEGLEN; i++) { - if (i < size) { - pos = 3 * i; - // using bgr order - b = line[pos++]; - g = line[pos++]; - r = line[pos]; - SEGMENT.setPixelColor(i, CRGB(r, g, b)); - } else { - SEGMENT.setPixelColor(i, CRGB::Black); - } - } - strip.show(); - lastLineUpdate = micros(); -} - bool POV::loadImage(const char * filename){ if(!image.init(filename)) return false; if(!image.load()) return false; - currentLine=0; return true; } -int16_t POV::showNextLine(){ - if (!image.isLoaded()) return 0; - //move to next line - showLine(image.line(currentLine), image.width()); - currentLine++; - if (currentLine == image.height()) {currentLine=0;} - return currentLine; +// Display a column directly from image memory +// For each pixel in the column (from row 0 to row height-1), +// compute its position in the BMP buffer and read the BGR values +void POV::showColumn(uint16_t colIndex) { + // Ignore out-of-range columns: for widths == 2 (mod 4) the row-size check + // below would still read one byte past the buffer when colIndex == width + if (colIndex >= image.width()) return; + uint16_t imgHeight = image.height(); + int rowSize = image.rowSize(); + + // AI: below section was generated by an AI + for (uint16_t i = 0; i < SEGLEN; i++) { + if (i < imgHeight) { + // Get pointer to this row in the image + byte *rowStart = image.line(i); + if (rowStart) { + // Compute offset to the desired column (3 bytes per pixel: BGR) + uint16_t pixelOffset = colIndex * 3; + if (pixelOffset + 2 <= rowSize) { + // Read BGR values directly from image buffer + uint8_t b = rowStart[pixelOffset]; + uint8_t g = rowStart[pixelOffset + 1]; + uint8_t r = rowStart[pixelOffset + 2]; + SEGMENT.setPixelColor(i, CRGB(r, g, b)); + continue; + } + } + } + // No valid pixel for this row/column: write black + SEGMENT.setPixelColor(i, CRGB::Black); + } + // AI: end } diff --git a/usermods/pov_display/pov.h b/usermods/pov_display/pov.h index cb543d2ea7..93bc40f843 100644 --- a/usermods/pov_display/pov.h +++ b/usermods/pov_display/pov.h @@ -6,35 +6,21 @@ class POV { public: POV(); - - /* Shows one line. line should be pointer to array which holds pixel colors - * (3 bytes per pixel, in BGR order). Note: 3, not 4!!! - * size should be size of array (number of pixels, not number of bytes) + + /* Shows a column directly from image memory (for horizontal POV) + * Uses image width/height/rowSize to compute pixel positions */ - void showLine(const byte * line, uint16_t size); + void showColumn(uint16_t colIndex); /* Reads from file an image and making it current image */ bool loadImage(const char * filename); - - /* Show next line of active image - Retunrs the index of next line to be shown (not yet shown!) - If it retunrs 0, it means we have completed showing the image and - next call will start again - */ - int16_t showNextLine(); - - //time since strip was last updated, in micro sec - uint32_t timeSinceUpdate() {return (micros()-lastLineUpdate);} - - + BMPimage * currentImage() {return ℑ} char * getFilename() {return image.getFilename();} private: BMPimage image; - int16_t currentLine=0; //next line to be shown - uint32_t lastLineUpdate=0; //time in microseconds }; diff --git a/usermods/pov_display/pov_display.cpp b/usermods/pov_display/pov_display.cpp index c57f3d8d59..481364e9aa 100644 --- a/usermods/pov_display/pov_display.cpp +++ b/usermods/pov_display/pov_display.cpp @@ -5,45 +5,71 @@ static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;"; static POV s_pov; +// AI: below section was generated by an AI void mode_pov_image(void) { - Segment& mainseg = strip.getMainSegment(); - const char* segName = mainseg.name; + // This effect displays columns from a BMP image for horizontal POV + // All logic is handled here to ensure it only runs when this effect is selected + // The image name is read from the segment actually running this effect (SEGMENT), + // not from the main segment, so a secondary segment can display its image even + // when the main segment has no name. The playback position is kept per segment + // (aux0), so multiple segments running this effect advance their own column. + // Note: BMPimage has a single shared pixel buffer, so only one image can be + // loaded at a time; segments naming different images take turns (re)loading it. + Segment& seg = SEGMENT; + const char* segName = seg.name; if (!segName) { - return; - } + return; + } + // Only proceed for files ending with .bmp (case-insensitive) size_t segLen = strlen(segName); if (segLen < 4) return; const char* ext = segName + (segLen - 4); - // compare case-insensitive to ".bmp" - if (!((ext[0]=='.') && - (ext[1]=='b' || ext[1]=='B') && - (ext[2]=='m' || ext[2]=='M') && - (ext[3]=='p' || ext[3]=='P'))) { - return; + + if ((ext[0] == '.') && + (ext[1] == 'b' || ext[1] == 'B') && + (ext[2] == 'm' || ext[2] == 'M') && + (ext[3] == 'p' || ext[3] == 'P')) { + + const char* current = s_pov.getFilename(); + BMPimage* img = s_pov.currentImage(); + + // If image is already loaded and matches, show next column + if (current && strcmp(segName, current) == 0) { + s_pov.showColumn(seg.aux0); + seg.aux0 = (seg.aux0 + 1) % img->width(); + return; + } + + // Image is loaded but doesn't match, or not loaded yet + // If we have a different image loaded, keep displaying it while trying the new one + if (img->isLoaded()) { + s_pov.showColumn(seg.aux0); + seg.aux0 = (seg.aux0 + 1) % img->width(); + } + + // Try to load the new image (rate limited) + static unsigned long s_lastLoadAttemptMs = 0; + unsigned long nowMs = millis(); + // Try to load at most twice per second + if (nowMs - s_lastLoadAttemptMs >= 500) { + s_lastLoadAttemptMs = nowMs; + if (s_pov.loadImage(segName)) { + // Successfully loaded, show first column + seg.aux0 = 0; + s_pov.showColumn(seg.aux0); + seg.aux0 = (seg.aux0 + 1) % img->width(); + } + // If load fails, we'll keep displaying old image and retry on next call + } } - - const char* current = s_pov.getFilename(); - if (current && strcmp(segName, current) == 0) { - s_pov.showNextLine(); - return; - } - - static unsigned long s_lastLoadAttemptMs = 0; - unsigned long nowMs = millis(); - // Retry at most twice per second if the image is not yet loaded. - if (nowMs - s_lastLoadAttemptMs < 500) return; - s_lastLoadAttemptMs = nowMs; - s_pov.loadImage(segName); - return; } +// AI: end class PovDisplayUsermod : public Usermod { protected: bool enabled = false; //WLEDMM const char *_name; //WLEDMM - bool initDone = false; //WLEDMM - unsigned long lastTime = 0; //WLEDMM public: PovDisplayUsermod(const char *name, bool enabled) @@ -51,19 +77,9 @@ class PovDisplayUsermod : public Usermod { void setup() override { strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE); - //initDone removed (unused) } - void loop() override { - // if usermod is disabled or called during strip updating just exit - // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly - if (!enabled || strip.isUpdating()) return; - - // do your magic here - if (millis() - lastTime > 1000) { - lastTime = millis(); - } } uint16_t getId() override {