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