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
 | |
|     
 |