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