Here's how I've been reading BMP files for POV on a Uno.

Here’s how I’ve been reading BMP files for POV on a Uno. It still has a lot of debug printing stuff. This is just a fragment copy-pasted from my code, not refactored to be stand alone, so you will need to define FILENAME and dbg and such, include the sdfatlib etc.

I currently use standard BMP files which go from bottom to top; you can create them with top to bottom order (length in that case is negative), but I wanted easy compatibility with most software.

rowlen is the offset between rows (may need to be be rounded up to a multiple of 4 if you image width in pixels is not a multiple of 4, see commented out line). readlen is the actual bytes read from each row to the leds[] array, and is a multiple of 3. I generate images with just the number of pixels I will need in width - but it would be easy to adjust this to extract just the columns needed from a wider image by adding an initial offset at the beginning of the display loop.

The other alternative I was considering was using the 8 bits per pixel BMP format, which uses a Color Look Up Table (CLUT) to translate each of 256 colors into a 24 bit RGB value. (or even a 4 bit/pixel format with 16 values in the CLUT) For now I’ve been satisfied with this format, tho.

(Let me know if there’s a better way to format the code here)

//------ code begins ----
myFile = SD.open(FILENAME);
if (!myFile) {
// if the file didn’t open, print an error:
Serial.print("error opening file: ");
Serial.println(FILENAME);
return;
}

Serial.println(FILENAME);

long elapsed;
uint8_t <b>buf = (uint8_t</b>) malloc(0x40);
int got;

elapsed = millis();
// read BMP file
// note that the BMP default is to scan from bottom to top row
// left to right within row
// and BMP is in BGR order
got = myFile.read(buf, 0x36);
if(got < 0x36) {
  Serial.println("Error reading header");
  return;
}
if(0x4D42 != *(uint16_t *)buf) {
  Serial.println("Did not start with BM");
  return;
}
int bpp  = *(int *)(buf+0x1C);    
int wide = *(int *)(buf+0x12); // really a long but LSB first
int high = *(int *)(buf+0x16); // likewise

high = abs(high); // can be negative

long data_offset = *(long *)(buf + 0x0A);

Serial.print("Size ");
Serial.print(wide);
Serial.print("w x ");
Serial.print(high);
Serial.print("h x ");
Serial.print(bpp);
Serial.println(" bpp");

if(bpp != 24) {
  Serial.print("Not 24 bpp");
  return;
}

int rowlen = wide * 3;
// rowlen = ~3 & (rowlen + 3);  // round up to multiple of 4
int readlen = NUM_LEDS * 3;
if(readlen > rowlen) readlen = rowlen;
      
free(buf);
//buf = (uint8_t*) malloc(rowlen);
buf = (uint8_t *) &leds[0];

while(1) {
  long nextrow = data_offset;
  myFile.seek(nextrow);
  
  for(int row = 0; row < high; row++) {
    got = myFile.read(buf, readlen);
    if(got < readlen) {
      Serial.print("Failed to read row ");
      Serial.print(row);
      Serial.print(", got ");
      Serial.print(got);
      Serial.print(" of ");
      Serial.print(rowlen);
      Serial.println(" bytes");
      return;
    }
    if(rowlen > readlen) {
      nextrow += rowlen;
      myFile.seek(nextrow);
    }
    
    if(dbg) {
      if(row < 10) { Serial.print("  "); }
      else if(row < 100) { Serial.print(" "); }
      Serial.print(row);
      for(int j = 0; j < 30; j++) {
        Serial.print(" ");
        if(leds[j].r < 16) Serial.print("0");
        Serial.print(leds[j].r, HEX);
        if(leds[j].g < 16) Serial.print("0");
        Serial.print(leds[j].g, HEX);
        if(leds[j].b < 16) Serial.print("0");
        Serial.print(leds[j].b, HEX);
      }
      Serial.println("");
    
      while(!Serial.available()); // wait for input
      while(Serial.available()) Serial.read(); // empty input buffer
    }
  
    LEDS.show();
    
  } // for row

One other option I’ve been considering for using BMPs for Christmas light sequences, is embedding the desired timing (eg: 100ms, 50ms, 25ms) in the BMP, using either (1) some of the non-standard fields in the header (defined differenty by different software or OSs), or (2) reserving the first row for metadata like this. Also, I would allow monochrome format with each monocrhome BMP pixel being one control channel (eg: one DMX channel); these might or might not be RGB associated.

This allow compact binary encoding, and also give a visual overview of a given display.

For those not familiar with C, I thought I might explain the round up to multiple of 4 line (commented out above since my BMP images have been a multiple of 4 wide).

Look at rowlen as 16 binary bits in two parts: (msbit first):
xxxxxxxxxxxxxx yy

if yy is not 00 and thus a multiple of 4, we need to increase rowlen just enough to make it so.

after adding 3, if yy = 0:
xxxxxxxxxxxx 11
or if yy = 1…3:
zzzzzzzzzzzz ?? where Z = X+1
if we and these with 0xFFFE (or ~3) we get:
xxxxxxxxxxxx 00 or
zzzzzzzzzzzz 00
which is either the same as original (IFF it was already a multiple of 4), or that value rounded to the next higher multiple of 4

I think I’m going to play with this later today. It would eliminate part of the pre-processing I’m doing on my images. Just open the file, resize if needed, and save it back as a BMP. The only thing I can’t put in the image file itself is the length of time I want it displayed but that could go in a control file somewhere. Thanks for the code snippet. May have questions later when I dive into it.

You can obviously comment out the print statements (especially in the loop) for more speed once everything it working for you, but overall I think it works pretty well.