Create an image as an array of pixels, and save as BMP file

snowsh

Well-known member
Hi, im trying to save an array of pixel as a BMP.
i have this sketch running, but whenever i try to open the generated BMP in paint it reports the file format is not correct.
I have been over this for too long now and need some fresh eyes. anyone here with more knowledge of the BMP format?

Here is the sketch. use the serial monitor...



C++:
/*
   a test script to generate an image, dump it to serial and save / open from SD.

   script will generate a random color image using the defined colors.

   script will display a representation of the image over serial monitor mapping colors to chars

   script will store image as BMP to SD card

   script will open BMP from SD card and dump hex values for debug
*/


#include <SD.h>
#include <TimeLib.h>

bool cardStatus = false;        // to track if we have access to a SD card

#define FILENAME_SIZE 50

char lastFilename[FILENAME_SIZE];

#define DEBUG_BMP
#define BYTES_PER_PIXEL 4

#define NUMBER_OF_COLORS 10

#define COLOR_WHITE        0xFFFFFF
#define COLOR_BLACK        0x000000

#define COLOR_RAINBOW_RED          0xFF0000
#define COLOR_RAINBOW_ORANGE       0xFFA500
#define COLOR_RAINBOW_YELLOW       0xFFFF00
#define COLOR_RAINBOW_GREEN        0x00FF00
#define COLOR_RAINBOW_BLUE         0x0000FF
#define COLOR_RAINBOW_INDIGO       0x4B0082
#define COLOR_RAINBOW_VIOLET       0xEE82EE

#define IMAGE_HEIGHT 3  //48
#define IMAGE_WIDTH  5  //128

int imageAr[IMAGE_HEIGHT][IMAGE_WIDTH] = {0};

const int colorsAr[NUMBER_OF_COLORS] PROGMEM = {                    // used to colour note based on interval position
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_RAINBOW_RED,
  COLOR_RAINBOW_ORANGE,
  COLOR_RAINBOW_YELLOW,
  COLOR_RAINBOW_GREEN,
  COLOR_RAINBOW_BLUE,
  COLOR_RAINBOW_INDIGO,
  COLOR_RAINBOW_VIOLET,
  COLOR_RAINBOW_RED,
};

//------------------------------------ for BMP file save

struct Pixel {
  uint8_t r, g, b;
};

// Create BMP File Header
struct BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};

// Create BMP DIB Header
struct BMPDIBHeader {
  uint32_t headerSize;                                                                  // Size of the DIB header
  int32_t  width;                                                                       // Image width
  int32_t  height;                                                                      // Image height
  uint16_t planes;                                                                      // Number of color planes (must be 1)
  uint16_t bitsPerPixel;                                                                    // Number of bits per pixel
  uint32_t compression;                                                                 // Compression method
  uint32_t imageSize;                                                                   // Size of image data
  int32_t  xPixelsPerMeter;                                                             // Horizontal pixels per meter
  int32_t  yPixelsPerMeter;                                                             // Vertical pixels per meter
  uint32_t colorsUsed;                                                                  // Number of colors in the palette
  uint32_t colorsImportant;                                                             // Number of important colors
};

//----------------------- setup and loop

void setup()
{
  Serial.begin(9600); // Initialize serial communication with a baud rate of 9600

  Serial.println(F("---- image generator and SD save open example -------"));
  Serial.println(F("use the serial monitor to enter commands"));
  Serial.println(F("enter 'O' to open last generated file"));
  Serial.println(F("enter 'G' to generate a new image and save to SD"));
  Serial.println(F("enter 'C' to attempt to reconnect SD card"));
  mySdBegin();
}

void loop()
{
  serialListen();
}

//------------------------------ functions

void mySdBegin()                                                          // begin new SD connection
{
  cardStatus = SD.sdfs.begin(SdioConfig(FIFO_SDIO));
  if (!cardStatus)
  {
    Serial.println(F("SD card not present"));
  }
}

void debugImageAr()
{
  Serial.println(F("\n\n-------- generated image -------\n\n"));

  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++)
    {
      for (uint8_t i = 0; i < 2; i++)
      {
        Serial.print(getCharacter(imageAr[y][x]));
      }
    }
    Serial.println();
  }
  
  Serial.println(F("\n\n-------- end generated image -------\n\n"));
}


char getCharacter(int color)  // return a char based on the color.
{
  switch (color)
  {
    case COLOR_BLACK:
      return '1';
    case COLOR_WHITE:
      return ' ';
    case COLOR_RAINBOW_RED:
      return '2';
    case COLOR_RAINBOW_ORANGE:
      return '3';
    case COLOR_RAINBOW_YELLOW:
      return '4';
    case COLOR_RAINBOW_GREEN:
      return '5';
    case COLOR_RAINBOW_BLUE:
      return '6';
    case COLOR_RAINBOW_INDIGO:
      return '7';
    case COLOR_RAINBOW_VIOLET:
      return '8';
    default:
      return 'x';
  }
}

void generateImage()
{
  generateImageArray();

  debugImageAr();

  saveImageSD();
}

void serialListen()
{
  if (Serial.available() > 0)    // If there is data available in the serial buffer
  {
    char command = Serial.read(); // Read the incoming character

    switch (command)    // Perform actions based on the received command
    {
      case 'G':     // generate image and dump/store to SD
        // Do something for command 'G'
        Serial.println("generateImage() Command G received");
        generateImage();
        break;
      case 'C':     // conect SD
        // Do something for command 'C'
        Serial.println("mySdBegin() Command C received");
        mySdBegin();
        break;
      case 'O':     // open file from SD
        readAndDumpBMP();
        Serial.println("readAndDumpBMP() Command O received");
        break;
      // Add more cases for other commands as needed

      default:
        // Handle unknown commands
        Serial.println("Unknown command");
    }
  }
}

void generateImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = colorsAr[random(NUMBER_OF_COLORS)];
    }
  }
}

void clearImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = COLOR_BLACK;
    }
  }
}

// to convert to BMP and save to SD

Pixel getPixel(int x, int y)
{
  // Extract RGB components from the hex color value in imageAr
  uint32_t hexColor = imageAr[y][x];
  uint8_t red = (hexColor >> 16) & 0xFF;
  uint8_t green = (hexColor >> 8) & 0xFF;
  uint8_t blue = hexColor & 0xFF;

  Pixel pixel = {red, green, blue};                                                        // Create and return a Pixel with the extracted components
  return pixel;
}

time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

void saveImageSD()
{
  // get the info about the image

  // get the image from the buffer

  // get a filename for the image

  // store to SD card

  generateBMPFile("test-images");
}

void printHexLn(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.println(value, HEX);
}

void printHex(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.print(value, HEX);
}

FLASHMEM void generateBMPFile(const char* folderName)
{
  uint8_t width = IMAGE_WIDTH;
  uint8_t height = IMAGE_HEIGHT;
  uint32_t imageSize = ((width * height) * BYTES_PER_PIXEL);

  SD.mkdir(folderName);                                                                     // Create folder if it doesn't exist

  char fileName[FILENAME_SIZE];                                                                      // Adjust the size based on your needs

  time_t currentTime = getTeensy3Time();

  generateFilename(folderName, fileName, sizeof(fileName), currentTime);

  for (uint8_t i = 0; i < FILENAME_SIZE; i++)
  {
    lastFilename[i] = fileName[i];
  }

  File bmpFile = SD.open(fileName, FILE_WRITE);

  // BMP File Header
  BMPFileHeader fileHeader;
  fileHeader.fileType = 0x4D42;  // "BM"
  fileHeader.fileSize = sizeof(BMPFileHeader) + imageSize;
  fileHeader.reserved1 = 0;
  fileHeader.reserved2 = 0;
  fileHeader.offset = sizeof(BMPFileHeader) + sizeof(BMPDIBHeader);

  // BMP DIB Header
  BMPDIBHeader dibHeader;
  dibHeader.headerSize = sizeof(BMPDIBHeader);
  dibHeader.width = 128;//width;
  dibHeader.height = 48;//height;
  dibHeader.planes = 1;
  dibHeader.bitsPerPixel = 24;   // 24 bits per pixel for for our color standard 0xFF9900 - RRGGBB
  dibHeader.compression = 0;  // No compression
  dibHeader.imageSize = imageSize;
  dibHeader.xPixelsPerMeter = 0;
  dibHeader.yPixelsPerMeter = 0;
  dibHeader.colorsUsed = 256;  // Grayscale palette
  dibHeader.colorsImportant = 0;



#if defined(DEBUG_BMP)
  Serial.println(F("====================== generateBMPFile(const char* folderName): "));
  Serial.print(F("sizeof(BMPFileHeader): "));  printHexLn(sizeof(BMPFileHeader));
  Serial.print(F("((width * height) * BYTES_PER_PIXEL): "));  Serial.println(((width * height) * BYTES_PER_PIXEL));
  // Create and write BMP header
  Serial.println(F("---------------------- fileHeader: "));
  Serial.print(F("fileHeader.fileType: "));  printHexLn(fileHeader.fileType);
  Serial.print(F("fileHeader.fileSize: "));  printHexLn(fileHeader.fileSize);
  Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
  Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
  Serial.print(F("fileHeader.offset: "));  printHexLn(fileHeader.offset);

  Serial.println(F("---------------------- dibHeader: "));
  Serial.print(F("dibHeader.headerSize: "));  printHexLn(dibHeader.headerSize);
  Serial.print(F("dibHeader.width: "));  printHexLn(dibHeader.width);
  Serial.print(F("dibHeader.height: "));  printHexLn(dibHeader.height);
  Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
  Serial.print(F("dibHeader.bitsPerPixel: "));  printHexLn(dibHeader.bitsPerPixel);
  Serial.print(F("dibHeader.compression: "));  printHexLn(dibHeader.compression);
  Serial.print(F("dibHeader.imageSize: "));  printHexLn(dibHeader.imageSize);
  Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
  Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
  Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
  Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

  Serial.println(F("image data: "));
#endif

  if (bmpFile)
  {
    bmpFile.write((uint8_t*)&fileHeader, sizeof(fileHeader));                        // Write BMP header to the SD card
    bmpFile.write((uint8_t*)&dibHeader, sizeof(dibHeader));                        // Write BMP header to the SD card
    size_t rowSize = (BYTES_PER_PIXEL * width);

    uint8_t row[rowSize] = {0};                                                 // declare write image data.

#if defined(DEBUG_BMP)
    Serial.print(F("rowSize: "));
    Serial.println(rowSize);
    Serial.print(F("width: "));
    Serial.println(width);
    Serial.print(F("height: "));
    Serial.println(height);
    uint32_t byteCounter = 0;
    uint8_t yCounter = 0;
    uint32_t counter = 0;
#endif

    for (int y = 0; y < height; y++)                                            // populate the pixel data
    {
      for (int x = 0; x < width; x++)
      {
        if ((size_t)(x * BYTES_PER_PIXEL) > rowSize)
        {
          Serial.print(F("error rowSize: ((x * BYTES_PER_PIXEL) > rowSize): "));
          Serial.println(x * BYTES_PER_PIXEL);

          break;
        }

        Pixel pix = getPixel(x, y);                                             // get the pixel color

#if defined(DEBUG_BMPa)
        Serial.print(F("B: "));
        printHexLn(pix.b);
        Serial.print(F(", G: "));
        printHexLn(pix.g);
        Serial.print(F(", R: "));
        printHexLn(pix.r);
        Serial.print(F(" | "));
        if ((counter + 1) % 8 == 0) Serial.println();                                // Print a line break every 64 bytes
        counter++;
#endif

        // having a hard time determining where to go here. looking at the hex dumps how do we order these 4 byte sequences?

        row[(BYTES_PER_PIXEL * x)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3. 4th byte is 00
        row[(BYTES_PER_PIXEL * x) + 1] = pix.g;
        row[(BYTES_PER_PIXEL * x) + 2] = pix.r;
        row[(BYTES_PER_PIXEL * x) + 3] = 0x00;     // force the 4th byte

        //        // to handle endinness? No i dont think this is right.
        //        row[(BYTES_PER_PIXEL * x)] = 0x00;     // force the 4th byte
        //        row[(BYTES_PER_PIXEL * x + 1)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3, 4th is 00
        //        row[(BYTES_PER_PIXEL * x) + 2] = pix.g;
        //        row[(BYTES_PER_PIXEL * x) + 3] = pix.r;

      }

#if defined(DEBUG_BMP)
      // now dump the row data

      counter = 0;

      Serial.print(F("\n-------- begin row  "));
      Serial.print(yCounter);
      Serial.println(F(" -------- "));

      for (uint32_t i = 0; i < rowSize; i++)
      {
        if (counter % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes
        if (counter % 16 == 0) Serial.print(F("  "));                                 // Print a double space every 16 bytes

        if (row[i] < 0x10) Serial.print(F("0"));
        Serial.print(row[i], HEX);
        Serial.print(F(" "));

        if ((counter + 1) % 64 == 0) Serial.println();                                // Print a line break every 64 bytes

        counter++;
        byteCounter++;
      }

      Serial.print(F("\n--- "));
      Serial.print(yCounter);
      Serial.println(F(" -------- end row ----- "));
      yCounter++;
#endif

      bmpFile.write(row, sizeof row);                                           // write the pixel
    }

#if defined(DEBUG_BMP)
    Serial.println(F("============ end BMP file data =============="));
    Serial.print(F("\n-------- total image bytes processed: "));
    Serial.print(byteCounter);
    Serial.println(F(" ------------ end report ----------- "));
#endif

    bmpFile.close();                                                            // Close the file
  }
  else
  {

#if defined(DEBUG_BMP)
    Serial.println("Error opening file for writing");
#endif

    return;
  }
}

FLASHMEM void generateFilename(const char* folderName, char fileName[], size_t fileSize, time_t currentTime)
{
  struct tm* timeinfo = gmtime(&currentTime);  // Get broken down time directly

  int offset = 0;

 
  while (*folderName)                         // Copy folderName to fileName
  {
    fileName[offset++] = *(folderName++);
  }
  fileName[offset++] = '/';

  // Year
  int year = 1900 + timeinfo->tm_year;
  fileName[offset++] = '0' + year / 1000 % 10;
  fileName[offset++] = '0' + year / 100 % 10;
  fileName[offset++] = '0' + year / 10 % 10;
  fileName[offset++] = '0' + year % 10;

  // Month
  int month = 1 + timeinfo->tm_mon;
  fileName[offset++] = '0' + month / 10 % 10;
  fileName[offset++] = '0' + month % 10;

  // Day
  int day = timeinfo->tm_mday;
  fileName[offset++] = '0' + day / 10 % 10;
  fileName[offset++] = '0' + day % 10;

  // Hour
  int hour = timeinfo->tm_hour;
  fileName[offset++] = '0' + hour / 10 % 10;
  fileName[offset++] = '0' + hour % 10;

  // Minute
  int minute = timeinfo->tm_min;
  fileName[offset++] = '0' + minute / 10 % 10;
  fileName[offset++] = '0' + minute % 10;

  // Second
  int second = timeinfo->tm_sec;
  fileName[offset++] = '0' + second / 10 % 10;
  fileName[offset++] = '0' + second % 10;

  // Extension
  fileName[offset++] = '.';
  fileName[offset++] = 'b';
  fileName[offset++] = 'm';
  fileName[offset++] = 'p';

  // Null-terminate the string
  fileName[offset] = '\0';

  Serial.print("Generated filename: ");
  Serial.print(fileName);
  Serial.print(" - offset: ");
  Serial.println(offset);
}

void readAndDumpBMP()
{
  File bmpFile = SD.open(lastFilename, FILE_READ);

  if (bmpFile)
  {
    // Read BMP header
    BMPFileHeader fileHeader;
    bmpFile.read((uint8_t*)&fileHeader, sizeof(fileHeader));

    BMPDIBHeader dibHeader;
    bmpFile.read((uint8_t*)&dibHeader, sizeof(dibHeader));

    Serial.println("BMP Header:");
    Serial.print("File Type: "); printHexLn(fileHeader.fileType);
    Serial.print("File Size: "); printHexLn(fileHeader.fileSize);
    Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
    Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
    Serial.print("Offset: "); printHexLn(fileHeader.offset);

    Serial.println("DIB Header:");
    Serial.print("Header Size: "); printHexLn(dibHeader.headerSize);
    Serial.print("Width: "); printHexLn(dibHeader.width);
    Serial.print("Height: "); printHexLn(dibHeader.height);
    Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
    Serial.print("Bits per Pixel: "); printHexLn(dibHeader.bitsPerPixel);
    Serial.print("Compression: "); printHexLn(dibHeader.compression);
    Serial.print("Image Size: "); printHexLn(dibHeader.imageSize);
    Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
    Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
    Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
    Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

    // HEX Dump of Image Data
    Serial.println("Image Data HEX Dump:");
    uint8_t buffer[16];
    size_t bytesRead = 0;

    while (bmpFile.available()) 
    {
      size_t count = bmpFile.read(buffer, sizeof(buffer));

      for (size_t i = 0; i < count; i++)
      {
        if (count % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes
        printHex(buffer[i]);
        Serial.print(" ");
      }

      bytesRead += count;
      Serial.println();
    }

    bmpFile.close();
  }
  else
  {
    Serial.println("Error opening file for reading");
  }
}
 
Last edited:
C++:
// Create BMP File Header
struct BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};
This structure needs to be declared as packed ("__attribute__((packed))") else the compiler will add unexpected padding between the fields.
 
Can you post one of the BMP files? You'll probably have to store it in a ZIP archive. I don't think the forum will allow you to upload a BMP.

Pete
 
This structure needs to be declared as packed ("__attribute__((packed))") else the compiler will add unexpected padding between the fields.
I have updated the struct, is this right?


Code:
// Create BMP File Header
struct __packed BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};

// Create BMP DIB Header
struct __packed BMPDIBHeader {
  uint32_t headerSize;                                                                  // Size of the DIB header
  int32_t  width;                                                                       // Image width
  int32_t  height;                                                                      // Image height
  uint16_t planes;                                                                      // Number of color planes (must be 1)
  uint16_t bitsPerPixel;                                                                    // Number of bits per pixel
  uint32_t compression;                                                                 // Compression method
  uint32_t imageSize;                                                                   // Size of image data
  int32_t  xPixelsPerMeter;                                                             // Horizontal pixels per meter
  int32_t  yPixelsPerMeter;                                                             // Vertical pixels per meter
  uint32_t colorsUsed;                                                                  // Number of colors in the palette
  uint32_t colorsImportant;                                                             // Number of important colors
};
no difference. the BMP files still wont open.



Can you post one of the BMP files? You'll probably have to store it in a ZIP archive. I don't think the forum will allow you to upload a BMP.

Pete
here is a hex dump, maybe you can use this. if not I will zip something up:

Code:
42 4d 0e 01 00 00 00 00 00 00 36 00 00 00 28 00 00 00 80 00 00 00 30 00 00 00 01 00 18 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ee 82 ee 00 82 00 4b 00 00 ff ff 00 00 ff 00 00 00 a5 ff 00 00 00 ff 00 00 00 ff 00 00 a5 ff 00 ee 82 ee 00 00 ff 00 00 ee 82 ee 00 ff ff ff 00 00 a5 ff 00 ff 00 00 00 ff ff ff 00 ee 82 ee 00 ff ff ff 00 82 00 4b 00 00 00 00 00 00 ff ff 00 00 ff ff 00 00 ff ff 00 ff ff ff 00 00 ff 00 00 00 ff ff 00 ee 82 ee 00 00 00 ff 00 00 ff 00 00 00 a5 ff 00 00 ff 00 00 ff ff ff 00 82 00 4b 00 00 ff 00 00 82 00 4b 00 ff 00 00 00 00 a5 ff 00 ee 82 ee 00 82 00 4b 00 00 00 00 00 00 ff 00 00 00 ff ff 00 00 ff 00 00 00 ff ff 00 82 00 4b 00 00 ff 00 00 ee 82 ee 00 00 00 00 00 00 00 00 00 82 00 4b 00 00 00 ff 00 00 ff 00 00 00 00 00 00 00 00 ff 00 00 ff ff 00 00 00 00 00 ee 82 ee 00 00 a5 ff 00 00 a5 ff 00 00 a5 ff 00 ff ff ff 00 00 00 00 00 00 a5 ff 00 ff ff ff 00 ee 82 ee 00
 
zip file containing some generated BMP files.
 

Attachments

  • zipped bmp for forum.zip
    1.3 KB · Views: 19
here is an update to my sketch. Lots more control via the serial monitor. press h enter for help. All seems to function right, but these BMPs just wont load on my pc..

C++:
/*
   a test script to generate an image, dump it to serial and save / open from SD.

   script will generate a random color image using the defined colors.

   script will display a representation of the image over serial monitor mapping colors to chars

   script will store image as BMP to SD card

   script will open BMP from SD card and dump hex values for debug

   useful table describing the header and data: https://elcharolin.wordpress.com/2018/11/28/read-and-write-bmp-files-in-c-c/
*/


#include <SD.h>
#include <TimeLib.h>

bool cardStatus = false;        // to track if we have access to a SD card

#define FILENAME_SIZE 50

char lastFilename[FILENAME_SIZE];

#define DEBUG_BMP
#define BYTES_PER_PIXEL 4

#define NUMBER_OF_COLORS 10

#define COLOR_WHITE        0xFFFFFF
#define COLOR_BLACK        0x000000

#define COLOR_RAINBOW_RED          0xFF0000
#define COLOR_RAINBOW_ORANGE       0xFFA500
#define COLOR_RAINBOW_YELLOW       0xFFFF00
#define COLOR_RAINBOW_GREEN        0x00FF00
#define COLOR_RAINBOW_BLUE         0x0000FF
#define COLOR_RAINBOW_INDIGO       0x4B0082
#define COLOR_RAINBOW_VIOLET       0xEE82EE

#define IMAGE_HEIGHT 4  //48
#define IMAGE_WIDTH  16  //128

#define READ_CHUNKS 16                                              // to read data from the SD in chunks to rebuild imageAr[][]

int imageAr[IMAGE_HEIGHT][IMAGE_WIDTH] = {0};
int tempImageAr[IMAGE_HEIGHT][IMAGE_WIDTH] = {0};

const int colorsAr[NUMBER_OF_COLORS] PROGMEM = {                    // used to colour note based on interval position
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_RAINBOW_RED,
  COLOR_RAINBOW_ORANGE,
  COLOR_RAINBOW_YELLOW,
  COLOR_RAINBOW_GREEN,
  COLOR_RAINBOW_BLUE,
  COLOR_RAINBOW_INDIGO,
  COLOR_RAINBOW_VIOLET,
  COLOR_RAINBOW_RED,
};

//------------------------------------ for BMP file save

struct Pixel {
  uint8_t r, g, b;
};

// Create BMP File Header
struct __packed BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};

// Create BMP DIB Header
struct __packed BMPDIBHeader {
  uint32_t headerSize;                                                                  // Size of the DIB header
  int32_t  width;                                                                       // Image width
  int32_t  height;                                                                      // Image height
  uint16_t planes;                                                                      // Number of color planes (must be 1)
  uint16_t bitsPerPixel;                                                                    // Number of bits per pixel
  uint32_t compression;                                                                 // Compression method
  uint32_t imageSize;                                                                   // Size of image data
  int32_t  xPixelsPerMeter;                                                             // Horizontal pixels per meter
  int32_t  yPixelsPerMeter;                                                             // Vertical pixels per meter
  uint32_t colorsUsed;                                                                  // Number of colors in the palette
  uint32_t colorsImportant;                                                             // Number of important colors
};

//----------------------- setup and loop

void setup()
{
  Serial.begin(9600); // Initialize serial communication with a baud rate of 9600

  displayHelp();

  mySdBegin();
}

void loop()
{
  serialListen();
}

//------------------------------ functions

void displayHelp()
{
  Serial.println(F("---- image generator and SD save open example -------"));
  Serial.println(F("use the serial monitor to enter comands"));
  Serial.println(F("enter 'O' or 'o' to open last generated file"));
  Serial.println(F("enter 'G' or 'g' to generate a new image and save to SD"));
  Serial.println(F("enter 'C' or 'c' to attempt to reconnect SD card"));
  Serial.println(F("enter 'H' or 'h' to show this help"));
  Serial.println(F("enter 'R' or 'r' to rebuild imageAr[][] from tempImageAr[][]"));
  Serial.println(F("enter 'P' or 'p' to debug imageAr[][]"));
  Serial.println(F("enter 'T' or 't' to debug tempImageAr[][]"));
}

void mySdBegin()                                                          // begin new SD connection
{
  cardStatus = SD.sdfs.begin(SdioConfig(FIFO_SDIO));
  if (!cardStatus)
  {
    Serial.println(F("SD card not present"));
  }
}

void debugImageAr(bool temp)
{
  if (temp)
  {
    Serial.println(F("\n-------- tempImageAr -------"));
  }
  else
  {
    Serial.println(F("\n-------- imageAr -------"));
  }
 
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++)
    {
      for (uint8_t i = 0; i < 2; i++)
      {
        if (temp)
        {
          Serial.print(getCharacter(tempImageAr[y][x]));
        }
        else
        {
          Serial.print(getCharacter(imageAr[y][x]));
        }
      }
    }
    Serial.println();
  }

  Serial.println(F("\n-------- end generated image -------"));
}

char getCharacter(int color)  // return a char based on the color.
{
  switch (color)
  {
    case COLOR_BLACK:
      return '1';
    case COLOR_WHITE:
      return ' ';
    case COLOR_RAINBOW_RED:
      return '2';
    case COLOR_RAINBOW_ORANGE:
      return '3';
    case COLOR_RAINBOW_YELLOW:
      return '4';
    case COLOR_RAINBOW_GREEN:
      return '5';
    case COLOR_RAINBOW_BLUE:
      return '6';
    case COLOR_RAINBOW_INDIGO:
      return '7';
    case COLOR_RAINBOW_VIOLET:
      return '8';
    default:
      return 'x';
  }
}

void generateImage()
{
  generateImageArray();

  debugImageAr(false);

  saveImageSD();
}

void serialListen()
{
  if (Serial.available() > 0)    // If there is data available in the serial buffer
  {
    char command = Serial.read(); // Read the incoming character

    if (command)
    {
      switch (command)    // Perform actions based on the received command
      {
        case 'G':     // generate image and dump/store to SD
        case 'g':
          Serial.println("generateImage() Command G received");
          generateImage();
          return;
        case 'C':     // conect SD
        case 'c':
          Serial.println("mySdBegin() Command C received");
          mySdBegin();
          return;
        case 'O':     // open file from SD
        case 'o':
          Serial.println("readAndDumpBMP() Command O received");
          readAndDumpBMP();
          return;
        case 'H':
        case 'h':
          displayHelp();    // display help
          return;
        case 'R':
        case 'r':
          Serial.println("rebuildImageAr() Command R received");
          rebuildImageAr();
          return;
        case 'P':
        case 'p':
          Serial.println("debugImageAr() imageAr Command p received");
          debugImageAr(false);
          return;
        case 'T':
        case 't':
          Serial.println("debugImageAr() tempImageAr Command t received");
          debugImageAr(true);
          return;
        // Add more cases for other commands as needed
        default:        // Handle unknown commands
          Serial.print(command);
          Serial.println(" - Unknown command");
          break;
      }
    }
  }
}

void generateImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = colorsAr[random(NUMBER_OF_COLORS)];
    }
  }
}

void clearImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = COLOR_BLACK;
    }
  }
}

// to convert to BMP and save to SD

Pixel getPixel(int x, int y)
{
  // Extract RGB components from the hex color value in imageAr
  uint32_t hexColor = imageAr[y][x];
  uint8_t red = (hexColor >> 16) & 0xFF;
  uint8_t green = (hexColor >> 8) & 0xFF;
  uint8_t blue = hexColor & 0xFF;

  Pixel pixel = {red, green, blue};                                                        // Create and return a Pixel with the extracted components
  return pixel;
}

time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

void printHexLn(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.println(value, HEX);
}

void printHex(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.print(value, HEX);
}

void saveImageSD()
{
  // get the info about the image
  // get the image from the buffer
  // get a filename for the image
  // store to SD card
  generateBMPFile("test-images");
}

FLASHMEM void generateBMPFile(const char* folderName)
{
  uint8_t width = IMAGE_WIDTH;
  uint8_t height = IMAGE_HEIGHT;
  uint32_t imageSize = ((width * height) * BYTES_PER_PIXEL);

  SD.mkdir(folderName);                                                                     // Create folder if it doesn't exist

  char fileName[FILENAME_SIZE];                                                                      // Adjust the size based on your needs

  time_t currentTime = getTeensy3Time();

  generateFilename(folderName, fileName, sizeof(fileName), currentTime);

  for (uint8_t i = 0; i < FILENAME_SIZE; i++)
  {
    lastFilename[i] = fileName[i];
  }

  File bmpFile = SD.open(fileName, FILE_WRITE);

  // BMP File Header
  BMPFileHeader fileHeader;
  fileHeader.fileType = 0x4D42;  // "BM"
  fileHeader.fileSize = sizeof(BMPFileHeader) + imageSize;
  fileHeader.reserved1 = 0;
  fileHeader.reserved2 = 0;
  fileHeader.offset = sizeof(BMPFileHeader) + sizeof(BMPDIBHeader);

  // BMP DIB Header
  BMPDIBHeader dibHeader;
  dibHeader.headerSize = sizeof(BMPDIBHeader);
  dibHeader.width = 128;//width;
  dibHeader.height = 48;//height;
  dibHeader.planes = 1;
  dibHeader.bitsPerPixel = 24;   // 24 bits per pixel for for our color standard 0xFF9900 - RRGGBB
  dibHeader.compression = 0;  // No compression
  dibHeader.imageSize = imageSize;
  dibHeader.xPixelsPerMeter = 0;
  dibHeader.yPixelsPerMeter = 0;
  dibHeader.colorsUsed = 256;  // Grayscale palette
  dibHeader.colorsImportant = 0;



#if defined(DEBUG_BMP)
  Serial.println(F("====================== generateBMPFile(const char* folderName): "));
  Serial.print(F("sizeof(BMPFileHeader): "));  printHexLn(sizeof(BMPFileHeader));
  Serial.print(F("((width * height) * BYTES_PER_PIXEL): "));  Serial.println(((width * height) * BYTES_PER_PIXEL));
  // Create and write BMP header
  Serial.println(F("---------------------- fileHeader: "));
  Serial.print(F("fileHeader.fileType: "));  printHexLn(fileHeader.fileType);
  Serial.print(F("fileHeader.fileSize: "));  printHexLn(fileHeader.fileSize);
  Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
  Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
  Serial.print(F("fileHeader.offset: "));  printHexLn(fileHeader.offset);

  Serial.println(F("---------------------- dibHeader: "));
  Serial.print(F("dibHeader.headerSize: "));  printHexLn(dibHeader.headerSize);
  Serial.print(F("dibHeader.width: "));  printHexLn(dibHeader.width);
  Serial.print(F("dibHeader.height: "));  printHexLn(dibHeader.height);
  Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
  Serial.print(F("dibHeader.bitsPerPixel: "));  printHexLn(dibHeader.bitsPerPixel);
  Serial.print(F("dibHeader.compression: "));  printHexLn(dibHeader.compression);
  Serial.print(F("dibHeader.imageSize: "));  printHexLn(dibHeader.imageSize);
  Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
  Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
  Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
  Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

  Serial.println(F("image data: "));
#endif


  if (bmpFile)
  {
    bmpFile.write((uint8_t*)&fileHeader, sizeof(fileHeader));                        // Write BMP header to the SD card
    bmpFile.write((uint8_t*)&dibHeader, sizeof(dibHeader));                        // Write BMP header to the SD card
  }
  else
  {

#if defined(DEBUG_BMP)
    Serial.println("Error opening file for writing");
#endif

    // allow the script to continue for debug  return;
  }

  size_t rowSize = (BYTES_PER_PIXEL * width);
  uint8_t row[rowSize] = {0};                                                 // declare write image data.

#if defined(DEBUG_BMP)
  Serial.print(F("rowSize: "));
  Serial.println(rowSize);
  Serial.print(F("width: "));
  Serial.println(width);
  Serial.print(F("height: "));
  Serial.println(height);
  uint32_t byteCounter = 0;
  uint8_t yCounter = 0;
  uint32_t counter = 0;
#endif

  for (int y = 0; y < height; y++)                                            // populate the pixel data
  {
    for (int x = 0; x < width; x++)
    {
      if ((size_t)(x * BYTES_PER_PIXEL) > rowSize)
      {
        Serial.print(F("error rowSize: ((x * BYTES_PER_PIXEL) > rowSize): "));
        Serial.println(x * BYTES_PER_PIXEL);

        break;
      }

      Pixel pix = getPixel(x, y);                                             // get the pixel color

#if defined(DEBUG_BMPa)
      Serial.print(F("B: "));
      printHexLn(pix.b);
      Serial.print(F(", G: "));
      printHexLn(pix.g);
      Serial.print(F(", R: "));
      printHexLn(pix.r);
      Serial.print(F(" | "));
      if ((counter + 1) % 8 == 0) Serial.println();                                // Print a line break every 64 bytes
      counter++;
#endif

      // having a hard time determining where to go here. looking at the hex dumps how do we order these 4 byte sequences?

      row[(BYTES_PER_PIXEL * x)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3. 4th byte is 00
      row[(BYTES_PER_PIXEL * x) + 1] = pix.g;
      row[(BYTES_PER_PIXEL * x) + 2] = pix.r;
      row[(BYTES_PER_PIXEL * x) + 3] = 0x00;     // force the 4th byte

      //        // to handle endianness? No i dont think this is right.
      //        row[(BYTES_PER_PIXEL * x)] = 0x00;     // force the 4th byte
      //        row[(BYTES_PER_PIXEL * x + 1)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3, 4th is 00
      //        row[(BYTES_PER_PIXEL * x) + 2] = pix.g;
      //        row[(BYTES_PER_PIXEL * x) + 3] = pix.r;

    }

#if defined(DEBUG_BMP)
    // now dump the row data

    counter = 0;

    Serial.print(F("\n-------- begin row  "));
    Serial.print(yCounter);
    Serial.println(F(" -------- "));

    for (uint32_t i = 0; i < rowSize; i++)
    {
      if (counter % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes
      if (counter % 16 == 0) Serial.print(F("  "));                                 // Print a double space every 16 bytes

      if (row[i] < 0x10) Serial.print(F("0"));
      Serial.print(row[i], HEX);
      Serial.print(F(" "));

      if ((counter + 1) % 64 == 0) Serial.println();                                // Print a line break every 64 bytes

      counter++;
      byteCounter++;
    }

    Serial.print(F("\n--- "));
    Serial.print(yCounter);
    Serial.println(F(" -------- end row ----- "));
    yCounter++;
#endif

    if (bmpFile) bmpFile.write(row, sizeof row);                                           // write the pixel
  }

#if defined(DEBUG_BMP)
  Serial.println(F("============ end BMP file data =============="));
  Serial.print(F("\n-------- total image bytes processed: "));
  Serial.print(byteCounter);
  Serial.println(F(" ------------ end report ----------- "));
#endif

  if (bmpFile) bmpFile.close();                                                            // Close the file
}

FLASHMEM void generateFilename(const char* folderName, char fileName[], size_t fileSize, time_t currentTime)
{
  struct tm* timeinfo = gmtime(&currentTime);  // Get broken down time directly

  int offset = 0;

  while (*folderName)                         // Copy folderName to fileName
  {
    fileName[offset++] = *(folderName++);
  }
  fileName[offset++] = '/';

  // Year
  int year = 1900 + timeinfo->tm_year;
  fileName[offset++] = '0' + year / 1000 % 10;
  fileName[offset++] = '0' + year / 100 % 10;
  fileName[offset++] = '0' + year / 10 % 10;
  fileName[offset++] = '0' + year % 10;

  // Month
  int month = 1 + timeinfo->tm_mon;
  fileName[offset++] = '0' + month / 10 % 10;
  fileName[offset++] = '0' + month % 10;

  // Day
  int day = timeinfo->tm_mday;
  fileName[offset++] = '0' + day / 10 % 10;
  fileName[offset++] = '0' + day % 10;

  // Hour
  int hour = timeinfo->tm_hour;
  fileName[offset++] = '0' + hour / 10 % 10;
  fileName[offset++] = '0' + hour % 10;

  // Minute
  int minute = timeinfo->tm_min;
  fileName[offset++] = '0' + minute / 10 % 10;
  fileName[offset++] = '0' + minute % 10;

  // Second
  int second = timeinfo->tm_sec;
  fileName[offset++] = '0' + second / 10 % 10;
  fileName[offset++] = '0' + second % 10;

  // Extension
  fileName[offset++] = '.';
  fileName[offset++] = 'b';
  fileName[offset++] = 'm';
  fileName[offset++] = 'p';

  // Null-terminate the string
  fileName[offset] = '\0';

  Serial.print("Generated filename: ");
  Serial.print(fileName);
  Serial.print(" - offset: ");
  Serial.println(offset);
}

void readAndDumpBMP()
{
  File bmpFile = SD.open(lastFilename, FILE_READ);

  Serial.println(F("readAndDumpBMP()1  "));

  if (bmpFile)
  {
    // Read BMP header
    BMPFileHeader fileHeader;
    bmpFile.read((uint8_t*)&fileHeader, sizeof(fileHeader));

    BMPDIBHeader dibHeader;
    bmpFile.read((uint8_t*)&dibHeader, sizeof(dibHeader));

    Serial.println("BMP Header:");
    Serial.print("File Type: "); printHexLn(fileHeader.fileType);
    Serial.print("File Size: "); printHexLn(fileHeader.fileSize);
    Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
    Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
    Serial.print("Offset: "); printHexLn(fileHeader.offset);

    Serial.println("DIB Header:");
    Serial.print("Header Size: "); printHexLn(dibHeader.headerSize);
    Serial.print("Width: "); printHexLn(dibHeader.width);
    Serial.print("Height: "); printHexLn(dibHeader.height);
    Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
    Serial.print("Bits per Pixel: "); printHexLn(dibHeader.bitsPerPixel);
    Serial.print("Compression: "); printHexLn(dibHeader.compression);
    Serial.print("Image Size: "); printHexLn(dibHeader.imageSize);
    Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
    Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
    Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
    Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

    // HEX Dump of Image Data
    Serial.println("Image Data HEX Dump:");
    uint8_t buffer[READ_CHUNKS];                                                                       // run the data in 16 byte chunks
    size_t bytesRead = 0;

    int xCounter = 0;
    int yCounter = 0;
    size_t imageByteCounter = 0;                                                                // dibHeader.imageSize;
    while (bmpFile.available())
    {
      size_t count = bmpFile.read(buffer, sizeof(buffer));

      size_t counter = 0;

      for (size_t i = 0; i < count; i += 4 )
      {
        if (imageByteCounter >= dibHeader.imageSize) break;
        imageByteCounter++;

        if (!bmpFile.available()) break;

        //        if (count % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes

        if (i % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes

        printHex(buffer[i]);
        Serial.print(" ");

//        int color = (buffer[counter] << 16) | (buffer[counter + 1] << 8) | buffer[counter + 2];        // rebuild the image array
//
//        tempImageAr[yCounter][xCounter] = color;

        int color = (buffer[counter + 2] << 16) | (buffer[counter + 1] << 8) | buffer[counter];
        
        tempImageAr[yCounter][xCounter] = color;
        

        counter += 4;                        // skip the 4th byte padding

        xCounter++;

        if (xCounter >= dibHeader.width)
        {
          yCounter++;
          xCounter = 0;
        }
      }

      bytesRead += count;
      Serial.println();
    }

    bmpFile.close();

    debugImageAr(true);

    debugImageAr(false);
  }
  else
  {
    Serial.println("Error opening file for reading");
  }
}

void rebuildImageAr()
{
  // Ensure that imageAr has enough space for the data
  //  static_assert(sizeof(imageAr) == sizeof(tempImageAr),
  //                "Array sizes must be the same for memcpy");

  // Use memcpy to copy the contents
  memcpy(imageAr, tempImageAr, sizeof(tempImageAr));

  debugImageAr(false);
}
 
Looking at sources this shows perhaps a way to ensure the packing is properly atributed:
Code:
T:\T_Drive\arduino-1.8.19\hardware\teensy\avr\cores\teensy4\cmsis_gcc.h:
   65    #define __WEAK                                 __attribute__((weak))
   66  #endif
   67: #ifndef   __PACKED
   68:   #define __PACKED                               __attribute__((packed, aligned(1)))
   69  #endif
   70: #ifndef   __PACKED_STRUCT
   71:   #define __PACKED_STRUCT                        struct __attribute__((packed, aligned(1)))
 
Looking at sources this shows perhaps a way to ensure the packing is properly atributed:
Code:
T:\T_Drive\arduino-1.8.19\hardware\teensy\avr\cores\teensy4\cmsis_gcc.h:
   65    #define __WEAK                                 __attribute__((weak))
   66  #endif
   67: #ifndef   __PACKED
   68:   #define __PACKED                               __attribute__((packed, aligned(1)))
   69  #endif
   70: #ifndef   __PACKED_STRUCT
   71:   #define __PACKED_STRUCT                        struct __attribute__((packed, aligned(1)))
i will admit i am a bit lost here. Can you give examples of how to use this. This is what i did before:

C++:
struct __packed BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};
 
I think this is part of the problem:
Code:
  dibHeader.width = 128;//width;
  dibHeader.height = 48;//height;
The header you've written says that the image is 128x48 but what you actually write is what is defined here:
Code:
#define IMAGE_HEIGHT 3  //48
#define IMAGE_WIDTH  5  //128

You need to make sure that you define the dimensions only in one place.

And FYI, if number of bytes per row in the image is odd, you must round that up to the next multiple of 4 bytes.
If the width is 5, then you must write 5x3+1 = 16 bytes per row.

Pete
 
i will admit i am a bit lost here. Can you give examples of how to use this. This is what i did before:

C++:
struct __packed BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};
The syntax I use for a BMP header is:

Code:
# pragma pack (push)
# pragma pack (2)
// save previous packing, then set packing for 16-bits
typedef struct  tBMPHDR565VGA{
  uint16_t  bfType = 0x4d42;   //'bm';
  uint32_t  bfSize = 614466;// 614400 pixels + 66 header
  uint16_t  bfReserved1 = 0;
  uint16_t  bfReserved2 = 0;
  uint32_t  bfOffBits =  66; // 14 bytes to here
  uint32_t  biSize = 40;
  int32_t   biWidth = 640;
  int32_t   biHeight = -480;  // windows wants negative for top-down image
  int16_t   biPlanes = 1;
  uint16_t  biBitCount = 16 ;
  uint32_t  biCompression = 3;  // bitfields used needs color masks
  uint32_t  biSizeImage = 614400;  // 640 * 480 * 2
  int32_t   biXPelsPerMeter = 0;
  int32_t   biYPelsPerMeter = 0;
  uint32_t  biClrUsed  = 0;
  uint32_t  biClrImportant = 0;// 54 bytes
  uint32_t  rmask = 0x0000F800;
  uint32_t  gmask = 0x000007E0;
  uint32_t  bmask = 0x0000001F;  //66 bytes
} BMPHDR565VGA;

// restore previous packing
# pragma pack (pop)
Note the negative value for height---windows wants it that way. The example is for RGB565, so you will have to look elsewhere for an example for your bitmap encoding. If you are saving 24 bits per pixel in your image, I don't think you are using a color palette and colors used should be zero.
 
This writes a BMP that is read without complaint by Paint Shop Pro.

Code:
// https://forum.pjrc.com/index.php?threads/create-an-image-as-an-array-of-pixels-and-save-as-bmp-file.74006/post-334757

/*
   a test script to generate an image, dump it to serial and save / open from SD.

   script will generate a random color image using the defined colors.

   script will display a representation of the image over serial monitor mapping colors to chars

   script will store image as BMP to SD card

   script will open BMP from SD card and dump hex values for debug
*/


#include <SD.h>
#include <TimeLib.h>

bool cardStatus = false;        // to track if we have access to a SD card

#define FILENAME_SIZE 50

char lastFilename[FILENAME_SIZE];

#define DEBUG_BMP
#define BYTES_PER_PIXEL 4

#define NUMBER_OF_COLORS 10

#define COLOR_WHITE        0xFFFFFF
#define COLOR_BLACK        0x000000

#define COLOR_RAINBOW_RED          0xFF0000
#define COLOR_RAINBOW_ORANGE       0xFFA500
#define COLOR_RAINBOW_YELLOW       0xFFFF00
#define COLOR_RAINBOW_GREEN        0x00FF00
#define COLOR_RAINBOW_BLUE         0x0000FF
#define COLOR_RAINBOW_INDIGO       0x4B0082
#define COLOR_RAINBOW_VIOLET       0xEE82EE

#define IMAGE_HEIGHT 48 //PAH 3
#define IMAGE_WIDTH  128 //PAH 5

int imageAr[IMAGE_HEIGHT][IMAGE_WIDTH] = {0};

const int colorsAr[NUMBER_OF_COLORS] PROGMEM = {                    // used to colour note based on interval position
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_RAINBOW_RED,
  COLOR_RAINBOW_ORANGE,
  COLOR_RAINBOW_YELLOW,
  COLOR_RAINBOW_GREEN,
  COLOR_RAINBOW_BLUE,
  COLOR_RAINBOW_INDIGO,
  COLOR_RAINBOW_VIOLET,
  COLOR_RAINBOW_RED,
};

//------------------------------------ for BMP file save

struct Pixel {
  uint8_t r, g, b;
};

#pragma pack (1)
// Create BMP File Header
struct BMPFileHeader {
  uint16_t fileType;                                                                        // "BM" for Bitmap
  uint32_t fileSize;                                                                        // Size of the BMP file
  uint16_t reserved1;                                                                   // Reserved field
  uint16_t reserved2;                                                                   // Reserved field
  uint32_t offset;                                                                      // Offset to the start of image data
};
#pragma pack (0)
// Create BMP DIB Header
#pragma pack (1)
struct BMPDIBHeader {
  uint32_t headerSize;                                                                  // Size of the DIB header
  int32_t  width;                                                                       // Image width
  int32_t  height;                                                                      // Image height
  uint16_t planes;                                                                      // Number of color planes (must be 1)
  uint16_t bitsPerPixel;                                                                    // Number of bits per pixel
  uint32_t compression;                                                                 // Compression method
  uint32_t imageSize;                                                                   // Size of image data
  int32_t  xPixelsPerMeter;                                                             // Horizontal pixels per meter
  int32_t  yPixelsPerMeter;                                                             // Vertical pixels per meter
  uint32_t colorsUsed;                                                                  // Number of colors in the palette
  uint32_t colorsImportant;                                                             // Number of important colors
};
#pragma pack (0)
//----------------------- setup and loop

void setup()
{
  Serial.begin(9600); // Initialize serial communication with a baud rate of 9600

  Serial.println(F("---- image generator and SD save open example -------"));
  Serial.println(F("use the serial monitor to enter commands"));
  Serial.println(F("enter 'O' to open last generated file"));
  Serial.println(F("enter 'G' to generate a new image and save to SD"));
  Serial.println(F("enter 'C' to attempt to reconnect SD card"));
  mySdBegin();
}

void loop()
{
  serialListen();
}

//------------------------------ functions

void mySdBegin()                                                          // begin new SD connection
{
  cardStatus = SD.sdfs.begin(SdioConfig(FIFO_SDIO));
  if (!cardStatus)
  {
    Serial.println(F("SD card g"));
  }
}

void debugImageAr()
{
  Serial.println(F("\n\n-------- generated image -------\n\n"));

  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++)
    {
      for (uint8_t i = 0; i < 2; i++)
      {
        Serial.print(getCharacter(imageAr[y][x]));
      }
    }
    Serial.println();
  }
  
  Serial.println(F("\n\n-------- end generated image -------\n\n"));
}


char getCharacter(int color)  // return a char based on the color.
{
  switch (color)
  {
    case COLOR_BLACK:
      return '1';
    case COLOR_WHITE:
      return ' ';
    case COLOR_RAINBOW_RED:
      return '2';
    case COLOR_RAINBOW_ORANGE:
      return '3';
    case COLOR_RAINBOW_YELLOW:
      return '4';
    case COLOR_RAINBOW_GREEN:
      return '5';
    case COLOR_RAINBOW_BLUE:
      return '6';
    case COLOR_RAINBOW_INDIGO:
      return '7';
    case COLOR_RAINBOW_VIOLET:
      return '8';
    default:
      return 'x';
  }
}

void generateImage()
{
  generateImageArray();

  debugImageAr();

  saveImageSD();
}

void serialListen()
{
  if (Serial.available() > 0)    // If there is data available in the serial buffer
  {
    char command = Serial.read(); // Read the incoming character

    switch (command)    // Perform actions based on the received command
    {
      case 'G':     // generate image and dump/store to SD
        // Do something for command 'G'
        Serial.println("generateImage() Command G received");
        generateImage();
        break;
      case 'C':     // conect SD
        // Do something for command 'C'
        Serial.println("mySdBegin() Command C received");
        mySdBegin();
        break;
      case 'O':     // open file from SD
        readAndDumpBMP();
        Serial.println("readAndDumpBMP() Command O received");
        break;
      // Add more cases for other commands as needed

      default:
        // Handle unknown commands
        Serial.println("Unknown command");
    }
  }
}

void generateImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = colorsAr[random(NUMBER_OF_COLORS)];
    }
  }
}

void clearImageArray()
{
  for (uint8_t y = 0; y < IMAGE_HEIGHT; y++)
  {
    for (uint8_t x = 0; x < IMAGE_WIDTH; x++ )
    {
      imageAr[y][x] = COLOR_BLACK;
    }
  }
}

// to convert to BMP and save to SD

Pixel getPixel(int x, int y)
{
  // Extract RGB components from the hex color value in imageAr
  uint32_t hexColor = imageAr[y][x];
  uint8_t red = (hexColor >> 16) & 0xFF;
  uint8_t green = (hexColor >> 8) & 0xFF;
  uint8_t blue = hexColor & 0xFF;

  Pixel pixel = {red, green, blue};                                                        // Create and return a Pixel with the extracted components
  return pixel;
}

time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

void saveImageSD()
{
  // get the info about the image

  // get the image from the buffer

  // get a filename for the image

  // store to SD card

  generateBMPFile("test-images");
}

void printHexLn(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.println(value, HEX);
}

void printHex(uint32_t value)
{
  Serial.print(F("0x"));
  if (value < 0x10) Serial.print(F("0"));
  Serial.print(value, HEX);
}

FLASHMEM void generateBMPFile(const char* folderName)
{
  uint8_t width = IMAGE_WIDTH;
  uint8_t height = IMAGE_HEIGHT;
  uint32_t imageSize = ((width * height) * BYTES_PER_PIXEL);

  SD.mkdir(folderName);                                                                     // Create folder if it doesn't exist

  char fileName[FILENAME_SIZE];                                                                      // Adjust the size based on your needs

  time_t currentTime = getTeensy3Time();

  generateFilename(folderName, fileName, sizeof(fileName), currentTime);

  for (uint8_t i = 0; i < FILENAME_SIZE; i++)
  {
    lastFilename[i] = fileName[i];
  }

  File bmpFile = SD.open(fileName, FILE_WRITE);

  // BMP File Header
  BMPFileHeader fileHeader;
  fileHeader.fileType = 0x4D42;  // "BM"
  fileHeader.fileSize = sizeof(BMPFileHeader) + imageSize;
  fileHeader.reserved1 = 0;
  fileHeader.reserved2 = 0;
  fileHeader.offset = sizeof(BMPFileHeader) + sizeof(BMPDIBHeader);

  // BMP DIB Header
  BMPDIBHeader dibHeader;
  dibHeader.headerSize = sizeof(BMPDIBHeader);
  dibHeader.width = width;
  dibHeader.height = height;
  dibHeader.planes = 1;
  dibHeader.bitsPerPixel = 32; //PAH 24;   // 24 bits per pixel for for our color standard 0xFF9900 - RRGGBB
  dibHeader.compression = 0;  // No compression
  dibHeader.imageSize = imageSize;
  dibHeader.xPixelsPerMeter = 0;
  dibHeader.yPixelsPerMeter = 0;
  //PAH 0 means there's no colour map information.
  //PAH If bitsPerPixel is 24 or 32 colorsUsed should be zero
  dibHeader.colorsUsed = 0; //PAH 256;  // Grayscale palette
  dibHeader.colorsImportant = 0;



#if defined(DEBUG_BMP)
  Serial.println(F("====================== generateBMPFile(const char* folderName): "));
  Serial.print(F("sizeof(BMPFileHeader): "));  printHexLn(sizeof(BMPFileHeader));
  Serial.print(F("((width * height) * BYTES_PER_PIXEL): "));  Serial.println(((width * height) * BYTES_PER_PIXEL));
  // Create and write BMP header
  Serial.println(F("---------------------- fileHeader: "));
  Serial.print(F("fileHeader.fileType: "));  printHexLn(fileHeader.fileType);
  Serial.print(F("fileHeader.fileSize: "));  printHexLn(fileHeader.fileSize);
  Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
  Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
  Serial.print(F("fileHeader.offset: "));  printHexLn(fileHeader.offset);

  Serial.println(F("---------------------- dibHeader: "));
  Serial.print(F("dibHeader.headerSize: "));  printHexLn(dibHeader.headerSize);
  Serial.print(F("dibHeader.width: "));  printHexLn(dibHeader.width);
  Serial.print(F("dibHeader.height: "));  printHexLn(dibHeader.height);
  Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
  Serial.print(F("dibHeader.bitsPerPixel: "));  printHexLn(dibHeader.bitsPerPixel);
  Serial.print(F("dibHeader.compression: "));  printHexLn(dibHeader.compression);
  Serial.print(F("dibHeader.imageSize: "));  printHexLn(dibHeader.imageSize);
  Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
  Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
  Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
  Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

  Serial.println(F("image data: "));
#endif

  if (bmpFile)
  {
    bmpFile.write((uint8_t*)&fileHeader, sizeof(fileHeader));                        // Write BMP header to the SD card
    bmpFile.write((uint8_t*)&dibHeader, sizeof(dibHeader));                        // Write BMP header to the SD card
    size_t rowSize = (BYTES_PER_PIXEL * width);

    uint8_t row[rowSize] = {0};                                                 // declare write image data.

#if defined(DEBUG_BMP)
    Serial.print(F("rowSize: "));
    Serial.println(rowSize);
    Serial.print(F("width: "));
    Serial.println(width);
    Serial.print(F("height: "));
    Serial.println(height);
    uint32_t byteCounter = 0;
    uint8_t yCounter = 0;
    uint32_t counter = 0;
#endif

    for (int y = 0; y < height; y++)                                            // populate the pixel data
    {
      for (int x = 0; x < width; x++)
      {
        if ((size_t)(x * BYTES_PER_PIXEL) > rowSize)
        {
          Serial.print(F("error rowSize: ((x * BYTES_PER_PIXEL) > rowSize): "));
          Serial.println(x * BYTES_PER_PIXEL);

          break;
        }

        Pixel pix = getPixel(x, y);                                             // get the pixel color

#if defined(DEBUG_BMPa)
        Serial.print(F("B: "));
        printHexLn(pix.b);
        Serial.print(F(", G: "));
        printHexLn(pix.g);
        Serial.print(F(", R: "));
        printHexLn(pix.r);
        Serial.print(F(" | "));
        if ((counter + 1) % 8 == 0) Serial.println();                                // Print a line break every 64 bytes
        counter++;
#endif

        // having a hard time determining where to go here. looking at the hex dumps how do we order these 4 byte sequences?

        row[(BYTES_PER_PIXEL * x)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3. 4th byte is 00
        row[(BYTES_PER_PIXEL * x) + 1] = pix.g;
        row[(BYTES_PER_PIXEL * x) + 2] = pix.r;
        row[(BYTES_PER_PIXEL * x) + 3] = 0x00;     // force the 4th byte

        //        // to handle endinness? No i dont think this is right.
        //        row[(BYTES_PER_PIXEL * x)] = 0x00;     // force the 4th byte
        //        row[(BYTES_PER_PIXEL * x + 1)] = pix.b;                                                       // scan through BYTES_PER_PIXEL bytes per pixel. only set the first 3, 4th is 00
        //        row[(BYTES_PER_PIXEL * x) + 2] = pix.g;
        //        row[(BYTES_PER_PIXEL * x) + 3] = pix.r;

      }

#if defined(DEBUG_BMP)
      // now dump the row data

      counter = 0;

      Serial.print(F("\n-------- begin row  "));
      Serial.print(yCounter);
      Serial.println(F(" -------- "));

      for (uint32_t i = 0; i < rowSize; i++)
      {
        if (counter % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes
        if (counter % 16 == 0) Serial.print(F("  "));                                 // Print a double space every 16 bytes

        if (row[i] < 0x10) Serial.print(F("0"));
        Serial.print(row[i], HEX);
        Serial.print(F(" "));

        if ((counter + 1) % 64 == 0) Serial.println();                                // Print a line break every 64 bytes

        counter++;
        byteCounter++;
      }

      Serial.print(F("\n--- "));
      Serial.print(yCounter);
      Serial.println(F(" -------- end row ----- "));
      yCounter++;
#endif

      bmpFile.write(row, sizeof row);                                           // write the pixel
    }

#if defined(DEBUG_BMP)
    Serial.println(F("============ end BMP file data =============="));
    Serial.print(F("\n-------- total image bytes processed: "));
    Serial.print(byteCounter);
    Serial.println(F(" ------------ end report ----------- "));
#endif

    bmpFile.close();                                                            // Close the file
  }
  else
  {

#if defined(DEBUG_BMP)
    Serial.println("Error opening file for writing");
#endif

    return;
  }
}

FLASHMEM void generateFilename(const char* folderName, char fileName[], size_t fileSize, time_t currentTime)
{
  struct tm* timeinfo = gmtime(&currentTime);  // Get broken down time directly

  int offset = 0;

 
  while (*folderName)                         // Copy folderName to fileName
  {
    fileName[offset++] = *(folderName++);
  }
  fileName[offset++] = '/';

  // Year
  int year = 1900 + timeinfo->tm_year;
  fileName[offset++] = '0' + year / 1000 % 10;
  fileName[offset++] = '0' + year / 100 % 10;
  fileName[offset++] = '0' + year / 10 % 10;
  fileName[offset++] = '0' + year % 10;

  // Month
  int month = 1 + timeinfo->tm_mon;
  fileName[offset++] = '0' + month / 10 % 10;
  fileName[offset++] = '0' + month % 10;

  // Day
  int day = timeinfo->tm_mday;
  fileName[offset++] = '0' + day / 10 % 10;
  fileName[offset++] = '0' + day % 10;

  // Hour
  int hour = timeinfo->tm_hour;
  fileName[offset++] = '0' + hour / 10 % 10;
  fileName[offset++] = '0' + hour % 10;

  // Minute
  int minute = timeinfo->tm_min;
  fileName[offset++] = '0' + minute / 10 % 10;
  fileName[offset++] = '0' + minute % 10;

  // Second
  int second = timeinfo->tm_sec;
  fileName[offset++] = '0' + second / 10 % 10;
  fileName[offset++] = '0' + second % 10;

  // Extension
  fileName[offset++] = '.';
  fileName[offset++] = 'b';
  fileName[offset++] = 'm';
  fileName[offset++] = 'p';

  // Null-terminate the string
  fileName[offset] = '\0';

  Serial.print("Generated filename: ");
  Serial.print(fileName);
  Serial.print(" - offset: ");
  Serial.println(offset);
}

void readAndDumpBMP()
{
  File bmpFile = SD.open(lastFilename, FILE_READ);

  if (bmpFile)
  {
    // Read BMP header
    BMPFileHeader fileHeader;
    bmpFile.read((uint8_t*)&fileHeader, sizeof(fileHeader));

    BMPDIBHeader dibHeader;
    bmpFile.read((uint8_t*)&dibHeader, sizeof(dibHeader));

    Serial.println("BMP Header:");
    Serial.print("File Type: "); printHexLn(fileHeader.fileType);
    Serial.print("File Size: "); printHexLn(fileHeader.fileSize);
    Serial.print(F("fileHeader.reserved1: "));  printHexLn(fileHeader.reserved1);
    Serial.print(F("fileHeader.reserved2: "));  printHexLn(fileHeader.reserved2);
    Serial.print("Offset: "); printHexLn(fileHeader.offset);

    Serial.println("DIB Header:");
    Serial.print("Header Size: "); printHexLn(dibHeader.headerSize);
    Serial.print("Width: "); printHexLn(dibHeader.width);
    Serial.print("Height: "); printHexLn(dibHeader.height);
    Serial.print(F("dibHeader.planes: "));  printHexLn(dibHeader.planes);
    Serial.print("Bits per Pixel: "); printHexLn(dibHeader.bitsPerPixel);
    Serial.print("Compression: "); printHexLn(dibHeader.compression);
    Serial.print("Image Size: "); printHexLn(dibHeader.imageSize);
    Serial.print(F("dibHeader.xPixelsPerMeter: "));  printHexLn(dibHeader.xPixelsPerMeter);
    Serial.print(F("dibHeader.yPixelsPerMeter: "));  printHexLn(dibHeader.yPixelsPerMeter);
    Serial.print(F("dibHeader.colorsUsed: "));  printHexLn(dibHeader.colorsUsed);
    Serial.print(F("dibHeader.colorsImportant: "));  printHexLn(dibHeader.colorsImportant);

    // HEX Dump of Image Data
    Serial.println("Image Data HEX Dump:");
    uint8_t buffer[16];
    size_t bytesRead = 0;

    while (bmpFile.available()) 
    {
      size_t count = bmpFile.read(buffer, sizeof(buffer));

      for (size_t i = 0; i < count; i++)
      {
        if (count % BYTES_PER_PIXEL == 0) Serial.print(F(" "));                                 // Print a space every 4 bytes
        printHex(buffer[i]);
        Serial.print(" ");
      }

      bytesRead += count;
      Serial.println();
    }

    bmpFile.close();
  }
  else
  {
    Serial.println("Error opening file for reading");
  }
}

Pete
 
Here's a test program to verify that the header works for VGA images. It generates a .BMP file with color bars and gray scale bars with the bitmap in RGB888 format (which is really GBR888 in the file). It includes MTP so that you can easily get the image into your PC. I find that double-clicking on the file name in the Teensy/SD Card folder automatically opens the image without the need to copy the file to the PC.

Code:
#include <SD.h>
#include <MTP_Teensy.h>

#define CS_SD BUILTIN_SDCARD  // Works on T_3.6 and T_4.1
# pragma pack (push)
# pragma pack (2)
// save previous packing, then set packing for 16-bits
typedef struct  tBMPHDRVGA{
  uint16_t  bfType = 0x4d42;   //'bm';
  uint32_t  bfSize = 921654;// 614400 pixels + 54 header
  uint16_t  bfReserved1 = 0;
  uint16_t  bfReserved2 = 0;
  uint32_t  bfOffBits =  54; // 14 bytes to here
  uint32_t  biSize = 40;
  int32_t   biWidth = 640;
  int32_t   biHeight = -480;  // windows wants negative for top-down image
  int16_t   biPlanes = 1;
  uint16_t  biBitCount = 24 ;
  uint32_t  biCompression = 0;  // bitfields used needs color masks
  uint32_t  biSizeImage = 921600;  // 640 * 480 * 3
  int32_t   biXPelsPerMeter = 0;
  int32_t   biYPelsPerMeter = 0;
  uint32_t  biClrUsed  = 0;
  uint32_t  biClrImportant = 0;// 54 bytes to end
} BMPHDR_VGA;

// restore previous packing
# pragma pack (pop)

#define IMGWIDTH 640
#define IMGHEIGHT 480
uint8_t  GSLine[3 * IMGWIDTH];
uint8_t  RGBLine[3 * IMGWIDTH];

tBMPHDRVGA HeaderVGA;

void setup() {
  Serial.begin(9600);
  while (!Serial && millis() < 2000) {
    // wait for serial port to connect.
  }
  Serial.println("\n\nBMP File Test");

  // Add SD Card
  if (SD.begin(CS_SD)) {
    MTP.addFilesystem(SD, "SD Card");
    Serial.println("Added SD card using built in SDIO");
    MakeBMPFile();
  } else {
    Serial.println("No SD Card");
  }
  // mandatory to begin the MTP session.
  MTP.begin();
}

void loop() {
  MTP.loop();  //This is mandatory to be placed in the loop code.
}

// Function to generate BMP file on SD card
void MakeBMPFile(void){
File bmpFile;
uint16_t i;
  Serial.println("Generating BMP File");
  SetGSLine(&GSLine[0]); // make 32 grayscale bars, each 20 pixels wide
  SetRGBLine(&RGBLine[0]);// make 8 color bars, each 40 pixels wide

  bmpFile = SD.open("Test.bmp", FILE_WRITE_BEGIN);
  // Write the BMP File header
  bmpFile.write(&HeaderVGA, sizeof(HeaderVGA));

  // write 240 lines of color bars
  for(i=0; i<IMGHEIGHT/2; i++){
    bmpFile.write(&RGBLine[0], sizeof(RGBLine));
  }
  Serial.println("Color bars written.");

  // write 240 lines of gray scale
  for(i=0; i<IMGHEIGHT/2; i++){
    bmpFile.write(&GSLine[0], sizeof(GSLine));
  }
  Serial.println("Gray Scale bars written.");

  bmpFile.close();
  Serial.println("BMP file closed.");
}

#define GBWIDTH 20
// make a line of 32 grayscale bars, each 20 pixels wide
void SetGSLine(uint8_t *gsptr){
uint8_t grayval;
uint16_t pnum; 
uint8_t *lptr = gsptr;
  grayval = 255;  // white bar
  for(pnum = 0; pnum <IMGWIDTH; pnum++){
      *gsptr++ = grayval; // Write red value
      *gsptr++ = grayval; // Write greem value
      *gsptr++ = grayval; // Write blue value
      // darken the bar every GBWIDTH pixels
      if((pnum % GBWIDTH) == (GBWIDTH-1)) grayval = grayval - 8;
  }
  Serial.println("Gray Scale Line initialized");

}

#define CBWIDTH 80
// make line of 8 color bars, each 80 pixels wide
// Colors are Red, Green, Blue, Black, Magenta, Yellow, Cyan, White
// NOTE: in each pixel, bytes are in B G R order
void SetRGBLine(uint8_t *rgbptr){
uint8_t rval, gval, bval;
uint16_t pnum;
uint8_t *lptr = rgbptr;

  rval = 255; gval = 0; bval = 0;  // red bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval;
  }

  rval = 0; gval = 255; bval = 0;  // green bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }
 
  rval = 0; gval = 0; bval = 255;  // blue bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }

  rval = 0; gval = 0; bval = 0;  //Black bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }
 
  rval = 255; gval = 0; bval = 255;  // Cyan bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }
 
  rval = 255; gval = 255; bval = 0;  // Yellow bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }
 
  rval = 0; gval = 255; bval = 255;  // Magenta bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval;
  }
 
  rval = 255; gval = 255; bval = 255;  // White bar
  for(pnum = 0; pnum <CBWIDTH; pnum++){
    *rgbptr++ = bval; *rgbptr++ = gval; *rgbptr++ = rval; 
  }
  Serial.println("Color Bar Line initialized");

}


More than half the source is just initializing the gray scale and color bar lines.
 
yes! we got there. So I learnt something new today. #pragma.....

Thank you everyone who answered. i will post up my fixed example for reference later.
 
Back
Top