201 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C++
		
	
	
	
/*
 | 
						|
  Support function for Floyd-Steinberg dithering of an 8bit grey-scale BMP image
 | 
						|
  on a Monochrome display:
 | 
						|
  https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
 | 
						|
 | 
						|
  Bitmap format:
 | 
						|
  https://en.wikipedia.org/wiki/BMP_file_format
 | 
						|
  
 | 
						|
  Example for https://github.com/Bodmer/TFT_eSPI
 | 
						|
  
 | 
						|
  The MIT License (MIT)
 | 
						|
  Copyright (c) 2015 by Bodmer
 | 
						|
  Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
  of this software and associated documentation files (the "Software"), to deal
 | 
						|
  in the Software without restriction, including without limitation the rights
 | 
						|
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						|
  copies of the Software, and to permit persons to whom the Software is
 | 
						|
  furnished to do so, subject to the following conditions:
 | 
						|
  The above copyright notice and this permission notice shall be included in all
 | 
						|
  copies or substantial portions of the Software.
 | 
						|
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						|
  AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
						|
  SOFTWARE.
 | 
						|
 | 
						|
  Note: drawFSBmp() is a simplified function and does not handle all possible
 | 
						|
  BMP file header variants. It works OK with 8 bit per pixel grey-scale images
 | 
						|
  generated by MS Paint and IrfanView.
 | 
						|
*/
 | 
						|
 | 
						|
// https://github.com/Bodmer/TFT_eSPI
 | 
						|
 | 
						|
//====================================================================================
 | 
						|
// Draw an 8 bit grey-scale bitmap (*.BMP) on a Monochrome display using dithering
 | 
						|
//====================================================================================
 | 
						|
// Uses RAM for buffers (3 * width + 4) ( 532 bytes for 176 pixels)
 | 
						|
 | 
						|
//   Image must be stored in ESP8266 or ESP32 SPIFFS
 | 
						|
 | 
						|
//    Quantisation error distribution for pixel X
 | 
						|
//     (This is for bottum up drawing of the BMP)
 | 
						|
//          |-------|-------|-------|
 | 
						|
//          | +3/16 | +5/16 | +1/16 |
 | 
						|
//          |-------|-------|-------|
 | 
						|
//          |       |   X   | +7/16 |
 | 
						|
//          |-------|-------|-------|
 | 
						|
//
 | 
						|
 | 
						|
void drawFSBmp(const char *filename, int16_t x, int16_t y) {
 | 
						|
 | 
						|
  if ((x >= frame.width()) || (y >= frame.height())) return;
 | 
						|
 | 
						|
  fs::File   bmpFS;
 | 
						|
 | 
						|
  // Open requested file
 | 
						|
  bmpFS = SPIFFS.open( filename, "r");
 | 
						|
 | 
						|
  if (!bmpFS)
 | 
						|
  {
 | 
						|
    Serial.print("File not found");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t seekOffset, dib_size;
 | 
						|
  uint16_t w, h, row, col, num_colors;
 | 
						|
  uint8_t  r, g, b;
 | 
						|
 | 
						|
  if (read16(bmpFS) == 0x4D42)         // Check it is a valid bitmap header
 | 
						|
  {
 | 
						|
    read32(bmpFS);
 | 
						|
    read32(bmpFS);
 | 
						|
    seekOffset = read32(bmpFS);        // Pointer to image start
 | 
						|
    dib_size   = read32(bmpFS);        // DIB header size, typically 40 bytes
 | 
						|
 | 
						|
    w = read32(bmpFS);                 // Get width and height of image
 | 
						|
    h = read32(bmpFS);
 | 
						|
 | 
						|
    //  Check it is 1 plane  and   8 bits per pixel  and       no compression
 | 
						|
    if ((read16(bmpFS) == 1) && (read16(bmpFS) == 8) && (read32(bmpFS) == 0))
 | 
						|
    {
 | 
						|
      read32(bmpFS); // Throw away image size
 | 
						|
      read32(bmpFS); // Throw away x pixels per meter
 | 
						|
      read32(bmpFS); // Throw away y pixels per meter
 | 
						|
 | 
						|
      num_colors = read32(bmpFS);           // Number of colours in colour table (usually 256)
 | 
						|
 | 
						|
      uint8_t pixel_color[num_colors];      // Lookup table for grey-scale
 | 
						|
 | 
						|
      bmpFS.seek(14 + dib_size);            // Seek to start of colour table
 | 
						|
 | 
						|
      // Capture the colour lookup table
 | 
						|
      for (uint16_t i = 0; i < num_colors; i++)
 | 
						|
      {
 | 
						|
        uint32_t abgr = read32(bmpFS);      // Assume 4 byte, RGB colours in LS 3 bytes
 | 
						|
        pixel_color[i] = (uint8_t) abgr;    // For grey-scale R, G, B are same value
 | 
						|
      }
 | 
						|
 | 
						|
      bmpFS.seek(seekOffset);               // Seek to start of image
 | 
						|
 | 
						|
      uint16_t padding = (4 - (w & 3)) & 3; // Calculate the BMP line padding
 | 
						|
 | 
						|
      // Create an zero an 8 bit pixel line buffer
 | 
						|
      uint8_t* lineBuffer = ( uint8_t*) calloc(w    , sizeof(uint8_t));
 | 
						|
 | 
						|
      // Create a 16 bit signed line buffer for the quantisation error
 | 
						|
      // Diffusion spreads to x-1 and x+1 so w + 2 avoids a bounds check
 | 
						|
      int16_t* qerrBuffer = ( int16_t*) calloc((w + 2)<<1, sizeof(uint8_t));
 | 
						|
 | 
						|
      y += h - 1; // Start from bottom (assumes bottum up!)
 | 
						|
 | 
						|
      // Draw row by row from bottom up
 | 
						|
      for (row = 0; row < h; row++) {
 | 
						|
 | 
						|
        // Read a row of pixels
 | 
						|
        bmpFS.read(lineBuffer, w);
 | 
						|
 | 
						|
        // Prep variables
 | 
						|
        uint16_t dx = 0;
 | 
						|
        uint8_t* bptr = lineBuffer;
 | 
						|
        int16_t* qptr = qerrBuffer + 1; // + 1 because diffusion spreads to x-1
 | 
						|
 | 
						|
        // Lookup color, add quantisation error, clip and clear error buffer
 | 
						|
        while(dx < w)
 | 
						|
        {
 | 
						|
          int16_t depixel =  pixel_color[(uint8_t)*bptr] + *qptr;
 | 
						|
          if (depixel >255) depixel = 255;   // Clip pixel to 0-255
 | 
						|
          else if (depixel < 0) depixel = 0;
 | 
						|
          *bptr++ = (uint8_t) depixel;       // Save new value, inc pointer
 | 
						|
          *qptr++ = 0;                       // Zero error, inc pointer
 | 
						|
          dx++;                              // Next pixel
 | 
						|
        }
 | 
						|
 | 
						|
        dx = 0;                // Reset varaibles to start of line
 | 
						|
        bptr = lineBuffer;
 | 
						|
        qptr = qerrBuffer + 1;
 | 
						|
        int32_t qerr = 0;
 | 
						|
        int32_t qerr16 = 0;
 | 
						|
 | 
						|
        // Push the pixel row to screen
 | 
						|
        while(dx < w)
 | 
						|
        {
 | 
						|
           // Add 7/16 of error (error = 0 on first entry)
 | 
						|
          int16_t pixel = *bptr + (qerr>>1) - qerr16;
 | 
						|
 | 
						|
          // Do not clip here so quantisation error accumulates correctly?
 | 
						|
          // Draw pixel (black or white) and determine new error
 | 
						|
          if (pixel < 128) { frame.drawPixel(x + dx, y, INK); qerr = pixel; }
 | 
						|
          else qerr = pixel - 255;
 | 
						|
 | 
						|
          // Diffuse into error buffer for next pixel line
 | 
						|
          qerr16 = qerr>>4;                  //     1/16 of error
 | 
						|
          *(qptr - 1) += (qerr>>2) - qerr16; // Add 3/16 of error
 | 
						|
          *(qptr    ) += (qerr>>2) + qerr16; // Add 5/16 of error
 | 
						|
          *(qptr + 1) +=  qerr16;            // Add 1/16 of error
 | 
						|
 | 
						|
          *bptr++; // Move along pixel and error buffers
 | 
						|
          *qptr++;
 | 
						|
          dx++;    // Move coordinate along
 | 
						|
        }
 | 
						|
        y--;
 | 
						|
 | 
						|
        // Read any line padding (saves a slow seek)
 | 
						|
        if (padding) bmpFS.read(lineBuffer, padding);
 | 
						|
      }
 | 
						|
    free(lineBuffer);
 | 
						|
    free(qerrBuffer);
 | 
						|
    }
 | 
						|
    else Serial.println("BMP format not recognized.");
 | 
						|
  }
 | 
						|
  bmpFS.close();
 | 
						|
}
 | 
						|
 | 
						|
//====================================================================================
 | 
						|
// Read a 16 bit value from the filing system
 | 
						|
//====================================================================================
 | 
						|
uint16_t read16(fs::File &f) {
 | 
						|
  uint16_t result;
 | 
						|
  ((uint8_t *)&result)[0] = f.read(); // LSB
 | 
						|
  ((uint8_t *)&result)[1] = f.read(); // MSB
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
//====================================================================================
 | 
						|
// Read a 32 bit value from the filing system
 | 
						|
//====================================================================================
 | 
						|
uint32_t read32(fs::File &f) {
 | 
						|
  uint32_t result;
 | 
						|
  ((uint8_t *)&result)[0] = f.read(); // LSB
 | 
						|
  ((uint8_t *)&result)[1] = f.read();
 | 
						|
  ((uint8_t *)&result)[2] = f.read();
 | 
						|
  ((uint8_t *)&result)[3] = f.read(); // MSB
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
//  TODO: Add support for colour images by converting RGB to grey-scale
 | 
						|
//  grey = (R+G+B)/3
 | 
						|
    
 |