acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
Floyd_Steinberg_BMP.ino (7541B)
1 /* 2 Support function for Floyd-Steinberg dithering of an 8bit grey-scale BMP image 3 on a Monochrome display: 4 https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering 5 6 Bitmap format: 7 https://en.wikipedia.org/wiki/BMP_file_format 8 9 Example for https://github.com/Bodmer/TFT_eSPI 10 11 The MIT License (MIT) 12 Copyright (c) 2015 by Bodmer 13 Permission is hereby granted, free of charge, to any person obtaining a copy 14 of this software and associated documentation files (the "Software"), to deal 15 in the Software without restriction, including without limitation the rights 16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 copies of the Software, and to permit persons to whom the Software is 18 furnished to do so, subject to the following conditions: 19 The above copyright notice and this permission notice shall be included in all 20 copies or substantial portions of the Software. 21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 SOFTWARE. 28 29 Note: drawFSBmp() is a simplified function and does not handle all possible 30 BMP file header variants. It works OK with 8 bit per pixel grey-scale images 31 generated by MS Paint and IrfanView. 32 */ 33 34 // https://github.com/Bodmer/TFT_eSPI 35 36 //==================================================================================== 37 // Draw an 8 bit grey-scale bitmap (*.BMP) on a Monochrome display using dithering 38 //==================================================================================== 39 // Uses RAM for buffers (3 * width + 4) ( 532 bytes for 176 pixels) 40 41 // Image must be stored in ESP8266 or ESP32 SPIFFS 42 43 // Quantisation error distribution for pixel X 44 // (This is for bottum up drawing of the BMP) 45 // |-------|-------|-------| 46 // | +3/16 | +5/16 | +1/16 | 47 // |-------|-------|-------| 48 // | | X | +7/16 | 49 // |-------|-------|-------| 50 // 51 52 void drawFSBmp(const char *filename, int16_t x, int16_t y) { 53 54 if ((x >= frame.width()) || (y >= frame.height())) return; 55 56 fs::File bmpFS; 57 58 // Open requested file 59 bmpFS = SPIFFS.open( filename, "r"); 60 61 if (!bmpFS) 62 { 63 Serial.print("File not found"); 64 return; 65 } 66 67 uint32_t seekOffset, dib_size; 68 uint16_t w, h, row, col, num_colors; 69 uint8_t r, g, b; 70 71 if (read16(bmpFS) == 0x4D42) // Check it is a valid bitmap header 72 { 73 read32(bmpFS); 74 read32(bmpFS); 75 seekOffset = read32(bmpFS); // Pointer to image start 76 dib_size = read32(bmpFS); // DIB header size, typically 40 bytes 77 78 w = read32(bmpFS); // Get width and height of image 79 h = read32(bmpFS); 80 81 // Check it is 1 plane and 8 bits per pixel and no compression 82 if ((read16(bmpFS) == 1) && (read16(bmpFS) == 8) && (read32(bmpFS) == 0)) 83 { 84 read32(bmpFS); // Throw away image size 85 read32(bmpFS); // Throw away x pixels per meter 86 read32(bmpFS); // Throw away y pixels per meter 87 88 num_colors = read32(bmpFS); // Number of colours in colour table (usually 256) 89 90 uint8_t pixel_color[num_colors]; // Lookup table for grey-scale 91 92 bmpFS.seek(14 + dib_size); // Seek to start of colour table 93 94 // Capture the colour lookup table 95 for (uint16_t i = 0; i < num_colors; i++) 96 { 97 uint32_t abgr = read32(bmpFS); // Assume 4 byte, RGB colours in LS 3 bytes 98 pixel_color[i] = (uint8_t) abgr; // For grey-scale R, G, B are same value 99 } 100 101 bmpFS.seek(seekOffset); // Seek to start of image 102 103 uint16_t padding = (4 - (w & 3)) & 3; // Calculate the BMP line padding 104 105 // Create an zero an 8 bit pixel line buffer 106 uint8_t* lineBuffer = ( uint8_t*) calloc(w , sizeof(uint8_t)); 107 108 // Create a 16 bit signed line buffer for the quantisation error 109 // Diffusion spreads to x-1 and x+1 so w + 2 avoids a bounds check 110 int16_t* qerrBuffer = ( int16_t*) calloc((w + 2)<<1, sizeof(uint8_t)); 111 112 y += h - 1; // Start from bottom (assumes bottum up!) 113 114 // Draw row by row from bottom up 115 for (row = 0; row < h; row++) { 116 117 // Read a row of pixels 118 bmpFS.read(lineBuffer, w); 119 120 // Prep variables 121 uint16_t dx = 0; 122 uint8_t* bptr = lineBuffer; 123 int16_t* qptr = qerrBuffer + 1; // + 1 because diffusion spreads to x-1 124 125 // Lookup color, add quantisation error, clip and clear error buffer 126 while(dx < w) 127 { 128 int16_t depixel = pixel_color[(uint8_t)*bptr] + *qptr; 129 if (depixel >255) depixel = 255; // Clip pixel to 0-255 130 else if (depixel < 0) depixel = 0; 131 *bptr++ = (uint8_t) depixel; // Save new value, inc pointer 132 *qptr++ = 0; // Zero error, inc pointer 133 dx++; // Next pixel 134 } 135 136 dx = 0; // Reset varaibles to start of line 137 bptr = lineBuffer; 138 qptr = qerrBuffer + 1; 139 int32_t qerr = 0; 140 int32_t qerr16 = 0; 141 142 // Push the pixel row to screen 143 while(dx < w) 144 { 145 // Add 7/16 of error (error = 0 on first entry) 146 int16_t pixel = *bptr + (qerr>>1) - qerr16; 147 148 // Do not clip here so quantisation error accumulates correctly? 149 // Draw pixel (black or white) and determine new error 150 if (pixel < 128) { frame.drawPixel(x + dx, y, INK); qerr = pixel; } 151 else qerr = pixel - 255; 152 153 // Diffuse into error buffer for next pixel line 154 qerr16 = qerr>>4; // 1/16 of error 155 *(qptr - 1) += (qerr>>2) - qerr16; // Add 3/16 of error 156 *(qptr ) += (qerr>>2) + qerr16; // Add 5/16 of error 157 *(qptr + 1) += qerr16; // Add 1/16 of error 158 159 bptr++; // Move along pixel and error buffers 160 qptr++; 161 dx++; // Move coordinate along 162 } 163 y--; 164 165 // Read any line padding (saves a slow seek) 166 if (padding) bmpFS.read(lineBuffer, padding); 167 } 168 free(lineBuffer); 169 free(qerrBuffer); 170 } 171 else Serial.println("BMP format not recognized."); 172 } 173 bmpFS.close(); 174 } 175 176 //==================================================================================== 177 // Read a 16 bit value from the filing system 178 //==================================================================================== 179 uint16_t read16(fs::File &f) { 180 uint16_t result; 181 ((uint8_t *)&result)[0] = f.read(); // LSB 182 ((uint8_t *)&result)[1] = f.read(); // MSB 183 return result; 184 } 185 186 //==================================================================================== 187 // Read a 32 bit value from the filing system 188 //==================================================================================== 189 uint32_t read32(fs::File &f) { 190 uint32_t result; 191 ((uint8_t *)&result)[0] = f.read(); // LSB 192 ((uint8_t *)&result)[1] = f.read(); 193 ((uint8_t *)&result)[2] = f.read(); 194 ((uint8_t *)&result)[3] = f.read(); // MSB 195 return result; 196 } 197 198 // TODO: Add support for colour images by converting RGB to grey-scale 199 // grey = (R+G+B)/3 200