ソースを参照

preliminary fixes & enhancements

ZinggJM 8 年 前
親
コミット
5977e32317

+ 178 - 117
examples/GxEPD2_SD_Example/GxEPD2_SD_Example.ino

@@ -3,6 +3,8 @@
 //
 // Display Library based on Demo Example from Good Display: http://www.good-display.com/download_list/downloadcategoryid=34&isMode=false.html
 //
+// BMP handling code extracts taken from: https://github.com/prenticedavid/MCUFRIEND_kbv/tree/master/examples/showBMP_kbv_Uno
+//
 // Author: Jean-Marc Zingg
 //
 // Version: see library.properties
@@ -45,16 +47,7 @@
 // include SdFat for FAT32 support with long filenames; available through Library Manager
 #include <SdFat.h>
 
-#if defined(SdFat_h)
 SdFat SD;
-#define FileClass SdFile
-#define position curPosition
-#define seek seekSet
-#else
-#include <SD.h>
-#define FileClass File
-#undef SdFat_h
-#endif
 
 #if defined (ESP8266)
 #define SD_CS SS  // e.g. for RobotDyn Wemos D1 mini SD board
@@ -156,6 +149,8 @@ GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=D8*/ EPD_CS, /
 //GxEPD2_3C<GxEPD2_750c, MAX_HEIGHT(GxEPD2_750c)> display(GxEPD2_750c(/*CS=10*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
 #endif
 
+// function declaration with default parameter
+void drawBitmapFromSD(const char *filename, int16_t x, int16_t y, bool with_color = true);
 
 void setup()
 {
@@ -173,68 +168,97 @@ void setup()
   }
   Serial.println("SD OK!");
 
-  drawBitmapFromSD("parrot.bmp", 0, 0);
+  drawBitmaps_200x200();
+  drawBitmaps_other();
+}
+
+void loop(void)
+{
+}
+
+void drawBitmaps_200x200()
+{
+  int16_t x = (display.width() - 200) / 2;
+  int16_t y = (display.height() - 200) / 2;
+  drawBitmapFromSD("logo200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("betty_1.bmp", 0, 0);
+  drawBitmapFromSD("first200x200.bmp", x, y);
   delay(2000);
-#if defined(SdFat_h)
-  drawBitmapFromSD("logo200x200.bmp", 0, 0);
+  drawBitmapFromSD("second200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("first200x200.bmp", 0, 0);
+  drawBitmapFromSD("third200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("second200x200.bmp", 0, 0);
+  drawBitmapFromSD("fourth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("third200x200.bmp", 0, 0);
+  drawBitmapFromSD("fifth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("fourth200x200.bmp", 0, 0);
+  drawBitmapFromSD("sixth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("fifth200x200.bmp", 0, 0);
+  drawBitmapFromSD("senventh200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("sixth200x200.bmp", 0, 0);
+  drawBitmapFromSD("eighth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSD("senventh200x200.bmp", 0, 0);
+}
+
+void drawBitmaps_other()
+{
+  int16_t w2 = display.width() / 2;
+  int16_t h2 = display.height() / 2;
+  drawBitmapFromSD("parrot.bmp", w2 - 64, h2 - 80);
   delay(2000);
-  drawBitmapFromSD("eighth200x200.bmp", 0, 0);
+  drawBitmapFromSD("betty_1.bmp", w2 - 100, h2 - 160);
   delay(2000);
-#else
-  drawBitmapFromSD("logo.bmp", 0, 0);
+  drawBitmapFromSD("betty_4.bmp", w2 - 102, h2 - 126);
+  delay(2000);
+  drawBitmapFromSD("marilyn_240x240x8.bmp", w2 - 120, h2 - 120);
+  delay(2000);
+  drawBitmapFromSD("miniwoof.bmp", w2 - 60, h2 - 80);
+  delay(2000);
+  drawBitmapFromSD("t200x200.bmp", w2 - 100, h2 - 100);
+  delay(2000);
+  drawBitmapFromSD("test.bmp", w2 - 120, h2 - 160);
+  delay(2000);
+  drawBitmapFromSD("tiger.bmp", w2 - 160, h2 - 120);
+  delay(2000);
+  drawBitmapFromSD("tiger_178x160x4.bmp", w2 - 89, h2 - 80);
+  delay(2000);
+  drawBitmapFromSD("tiger_240x317x4.bmp", w2 - 120, h2 - 160);
+  delay(2000);
+  drawBitmapFromSD("tiger_320x200x24.bmp", w2 - 160, h2 - 100);
+  delay(2000);
+  drawBitmapFromSD("tiger16T.bmp", w2 - 160, h2 - 120);
+  delay(2000);
+  drawBitmapFromSD("woof.bmp", w2 - 120, h2 - 160);
   delay(2000);
-#endif
 }
 
-void loop(void)
-{
-}
+static const uint16_t input_buffer_pixels = 20; // may affect performance
 
-#define SD_BUFFER_PIXELS 20
+static const uint16_t max_row_width = 640; // for up to 7.5" display
+static const uint16_t max_palette_pixels = 256; // for depth <= 8
 
-void drawBitmapFromSD(const char *filename, uint8_t x, uint8_t y)
+uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
+uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
+uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
+uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
+uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w
+
+void drawBitmapFromSD(const char *filename, int16_t x, int16_t y, bool with_color)
 {
-  FileClass file;
-  uint8_t buffer[3 * SD_BUFFER_PIXELS]; // pixel buffer, size for r,g,b
+  SdFile file;
   bool valid = false; // valid format to be handled
   bool flip = true; // bitmap is stored bottom-to-top
-  uint32_t pos = 0;
   uint32_t startTime = millis();
   if ((x >= display.width()) || (y >= display.height())) return;
   Serial.println();
   Serial.print("Loading image '");
   Serial.print(filename);
   Serial.println('\'');
-#if defined(SdFat_h)
   if (!file.open(filename, FILE_READ))
   {
     Serial.print("File not found");
     return;
   }
-#else
-  file = SD.open(filename);
-  if (!file)
-  {
-    Serial.print("File not found");
-    return;
-  }
-#endif
   // Parse BMP header
   if (read16(file) == 0x4D42) // BMP signature
   {
@@ -247,7 +271,7 @@ void drawBitmapFromSD(const char *filename, uint8_t x, uint8_t y)
     uint16_t planes = read16(file);
     uint16_t depth = read16(file); // bits per pixel
     uint32_t format = read32(file);
-    if ((planes == 1) && (format == 0)) // uncompressed is handled
+    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
     {
       Serial.print("File size: "); Serial.println(fileSize);
       Serial.print("Image Offset: "); Serial.println(imageOffset);
@@ -257,14 +281,6 @@ void drawBitmapFromSD(const char *filename, uint8_t x, uint8_t y)
       Serial.print(width);
       Serial.print('x');
       Serial.println(height);
-      //      Serial.print("first position "); Serial.println(file.position());
-      //      for (uint32_t i = file.position(); i < imageOffset; i++)
-      //      {
-      //        uint8_t data = file.read();// remaining header bytes
-      //        Serial.print("0x"); Serial.print(data, HEX); Serial.print(", ");
-      //      }
-      //      Serial.println();
-      //      Serial.print("start position "); Serial.println(file.position());
       // BMP rows are padded (if needed) to 4-byte boundary
       uint32_t rowSize = (width * depth / 8 + 3) & ~3;
       if (height < 0)
@@ -276,93 +292,138 @@ void drawBitmapFromSD(const char *filename, uint8_t x, uint8_t y)
       uint16_t h = height;
       if ((x + w - 1) >= display.width())  w = display.width()  - x;
       if ((y + h - 1) >= display.height()) h = display.height() - y;
-      if ((depth == 1) && (rowSize < sizeof(buffer))) // handle with direct drawing
+      if (w < max_row_width) // handle with direct drawing
       {
         valid = true;
-        display.writeScreenBuffer();
-        if (flip) file.seek(imageOffset + (height - h) * rowSize);
-        else file.seek(imageOffset);
-        for (uint16_t row = 0; row < h; row++) // for each line
+        uint8_t bitmask = 0xFF;
+        uint8_t bitshift = 8 - depth;
+        uint16_t red, green, blue;
+        bool whitish, colored;
+        if (depth == 1) with_color = false;
+        if (depth <= 8)
         {
-          file.read(buffer, rowSize);
-          uint16_t y = flip ? h - row - 1 : row;
-          display.writeImage(buffer, 0, y, w, 1);
+          if (depth < 8) bitmask >>= depth;
+          file.seekSet(54); //palette is always @ 54
+          for (uint16_t pn = 0; pn < (1 << depth); pn++)
+          {
+            blue  = file.read();
+            green = file.read();
+            red   = file.read();
+            file.read();
+            whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+            colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+            if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
+            mono_palette_buffer[pn / 8] |= whitish << pn % 8;
+            if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
+            color_palette_buffer[pn / 8] |= colored << pn % 8;
+          }
         }
-        display.refresh();
-      }
-      else
-      {
-        display.firstPage();
-        do
+        display.clearScreen();
+        uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
+        for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
         {
-          size_t buffidx = sizeof(buffer); // force buffer load
-          for (uint16_t row = 0; row < h; row++) // for each line
+          uint32_t in_remain = rowSize;
+          uint32_t in_idx = 0;
+          uint32_t in_bytes = 0;
+          uint8_t in_byte = 0; // for depth <= 8
+          uint8_t in_bits = 0; // for depth <= 8
+          uint8_t out_byte = 0;
+          uint8_t out_color_byte = 0;
+          uint32_t out_idx = 0;
+          file.seekSet(rowPosition);
+          for (uint16_t col = 0; col < w; col++) // for each pixel
           {
-            if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
-              pos = imageOffset + (height - 1 - row) * rowSize;
-            else     // Bitmap is stored top-to-bottom
-              pos = imageOffset + row * rowSize;
-            if (file.position() != pos)
-            { // Need seek?
-              file.seek(pos);
-              buffidx = sizeof(buffer); // force buffer reload
+            // Time to read more pixel data?
+            if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
+            {
+              in_bytes = file.read(input_buffer, in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain);
+              in_remain -= in_bytes;
+              in_idx = 0;
             }
-            uint8_t bits;
-            for (uint16_t col = 0; col < w; col++) // for each pixel
+            switch (depth)
             {
-              // Time to read more pixel data?
-              if (buffidx >= sizeof(buffer))
-              {
-                file.read(buffer, sizeof(buffer));
-                buffidx = 0; // Set index to beginning
-              }
-              switch (depth)
-              {
-                case 1: // one bit per pixel b/w format
+              case 24:
+                blue = input_buffer[in_idx++];
+                green = input_buffer[in_idx++];
+                red = input_buffer[in_idx++];
+                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                break;
+              case 16:
+                {
+                  uint8_t lsb = input_buffer[in_idx++];
+                  uint8_t msb = input_buffer[in_idx++];
+                  if (format == 0) // 555
+                  {
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
+                    red   = (msb & 0x7C) << 1;
+                  }
+                  else // 565
                   {
-                    valid = true;
-                    if (0 == col % 8)
-                    {
-                      bits = buffer[buffidx++];
-                    }
-                    uint16_t bw_color = bits & 0x80 ? GxEPD_WHITE : GxEPD_BLACK;
-                    display.drawPixel(col, row, bw_color);
-                    bits <<= 1;
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
+                    red   = (msb & 0xF8);
                   }
-                  break;
-                case 24: // standard BMP format
+                  whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                  colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                }
+                break;
+              case 1:
+              case 4:
+              case 8:
+                {
+                  if (0 == in_bits)
                   {
-                    valid = true;
-                    uint16_t b = buffer[buffidx++];
-                    uint16_t g = buffer[buffidx++];
-                    uint16_t r = buffer[buffidx++];
-                    uint16_t bw_color = ((r + g + b) / 3 > 0xFF  / 2) ? GxEPD_WHITE : GxEPD_BLACK;
-                    display.drawPixel(col, row, bw_color);
+                    in_byte = input_buffer[in_idx++];
+                    in_bits = 8;
                   }
-                  break;
-              }
-            } // end pixel
-          } // end line
-        } // end do
-        while (display.nextPage());
+                  uint16_t pn = (in_byte >> bitshift) & bitmask;
+                  whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  in_byte <<= depth;
+                  in_bits -= depth;
+                }
+                break;
+            }
+            if (whitish)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            else if (colored && with_color)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+            }
+            else
+            {
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            if (7 == col % 8)
+            {
+              output_row_color_buffer[out_idx] = out_color_byte;
+              output_row_mono_buffer[out_idx++] = out_byte;
+              out_byte = 0;
+              out_color_byte = 0;
+            }
+          } // end pixel
+          uint16_t yrow = y + (flip ? h - row - 1 : row);
+          display.writeImage(output_row_mono_buffer, output_row_color_buffer, x, yrow, w, 1);
+        } // end line
+        Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
+        display.refresh();
       }
     }
   }
-  //Serial.print("end position  "); Serial.println(file.position());
+  //Serial.print("end curPosition  "); Serial.println(file.curPosition());
   file.close();
-  if (valid)
-  {
-    Serial.print("loaded in ");
-    Serial.print(millis() - startTime);
-    Serial.println(" ms");
-  }
-  else
+  if (!valid)
   {
     Serial.println("bitmap format not handled.");
   }
 }
 
-uint16_t read16(FileClass& f)
+uint16_t read16(SdFile& f)
 {
   // BMP data is stored little-endian, same as Arduino.
   uint16_t result;
@@ -371,7 +432,7 @@ uint16_t read16(FileClass& f)
   return result;
 }
 
-uint32_t read32(FileClass& f)
+uint32_t read32(SdFile& f)
 {
   // BMP data is stored little-endian, same as Arduino.
   uint32_t result;

+ 176 - 85
examples/GxEPD2_Spiffs_Example/GxEPD2_Spiffs_Example.ino

@@ -3,6 +3,8 @@
 //
 // Display Library based on Demo Example from Good Display: http://www.good-display.com/download_list/downloadcategoryid=34&isMode=false.html
 //
+// BMP handling code extracts taken from: https://github.com/prenticedavid/MCUFRIEND_kbv/tree/master/examples/showBMP_kbv_Uno
+//
 // Author: Jean-Marc Zingg
 //
 // Version: see library.properties
@@ -43,7 +45,7 @@
 
 #if defined (ESP8266)
 // select one and adapt to your mapping, can use full buffer size (full HEIGHT)
-//GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
+GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_213, GxEPD2_213::HEIGHT> display(GxEPD2_213(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_270, GxEPD2_270::HEIGHT> display(GxEPD2_270(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
@@ -140,6 +142,9 @@
 #endif
 
 
+// function declaration with default parameter
+void drawBitmapFromSpiffs(const char *filename, int16_t x, int16_t y, bool with_color = true);
+
 void setup()
 {
   Serial.begin(115200);
@@ -152,39 +157,80 @@ void setup()
   Serial.println("SPIFFS started");
   listFiles();
 
-  drawBitmapFromSpiffs("chanceflurries.bmp", 0, 0);
-  delay(2000);
-  drawBitmapFromSpiffs("logo200x200.bmp", 0, 0);
+  drawBitmaps_200x200();
+  drawBitmaps_other();
+}
+
+void loop(void)
+{
+}
+
+void drawBitmaps_200x200()
+{
+  int16_t x = (display.width() - 200) / 2;
+  int16_t y = (display.height() - 200) / 2;
+  drawBitmapFromSpiffs("logo200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("first200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("first200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("second200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("second200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("third200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("third200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("fourth200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("fourth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("fifth200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("fifth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("sixth200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("sixth200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("seventh200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("senventh200x200.bmp", x, y);
   delay(2000);
-  drawBitmapFromSpiffs("eighth200x200.bmp", 0, 0);
+  drawBitmapFromSpiffs("eighth200x200.bmp", x, y);
   delay(2000);
 }
 
-void loop(void)
+void drawBitmaps_other()
 {
+  int16_t w2 = display.width() / 2;
+  int16_t h2 = display.height() / 2;
+  drawBitmapFromSpiffs("chanceflurries.bmp", w2 - 50, h2 - 50, false);
+  delay(2000);
+  drawBitmapFromSpiffs("betty_1.bmp", w2 - 100, h2 - 160);
+  delay(2000);
+  drawBitmapFromSpiffs("betty_4.bmp", w2 - 102, h2 - 126);
+  delay(2000);
+  drawBitmapFromSpiffs("marilyn_240x240x8.bmp", w2 - 120, h2 - 120);
+  delay(2000);
+  drawBitmapFromSpiffs("miniwoof.bmp", w2 - 60, h2 - 80);
+  delay(2000);
+  drawBitmapFromSpiffs("tiger.bmp", w2 - 160, h2 - 120);
+  delay(2000);
+  drawBitmapFromSpiffs("tiger_178x160x4.bmp", w2 - 89, h2 - 80);
+  delay(2000);
+  drawBitmapFromSpiffs("tiger_240x317x4.bmp", w2 - 120, h2 - 160);
+  delay(2000);
+  drawBitmapFromSpiffs("tiger_320x200x24.bmp", w2 - 160, h2 - 100);
+  delay(2000);
+  drawBitmapFromSpiffs("woof.bmp", w2 - 120, h2 - 160);
+  delay(2000);
 }
 
-void drawBitmapFromSpiffs(const char *filename, uint8_t x, uint8_t y)
+static const uint16_t input_buffer_pixels = 640; // may affect performance
+
+static const uint16_t max_row_width = 640; // for up to 7.5" display
+static const uint16_t max_palette_pixels = 256; // for depth <= 8
+
+uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
+uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
+uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
+uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
+uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w
+
+void drawBitmapFromSpiffs(const char *filename, int16_t x, int16_t y, bool with_color)
 {
-  FileClass file;
-  uint8_t buffer[512]; // buffer for at least one row
+  fs::File file;
   bool valid = false; // valid format to be handled
   bool flip = true; // bitmap is stored bottom-to-top
-  uint32_t pos = 0;
   uint32_t startTime = millis();
   if ((x >= display.width()) || (y >= display.height())) return;
   Serial.println();
@@ -209,7 +255,7 @@ void drawBitmapFromSpiffs(const char *filename, uint8_t x, uint8_t y)
     uint16_t planes = read16(file);
     uint16_t depth = read16(file); // bits per pixel
     uint32_t format = read32(file);
-    if ((planes == 1) && (format == 0)) // uncompressed is handled
+    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
     {
       Serial.print("File size: "); Serial.println(fileSize);
       Serial.print("Image Offset: "); Serial.println(imageOffset);
@@ -230,93 +276,138 @@ void drawBitmapFromSpiffs(const char *filename, uint8_t x, uint8_t y)
       uint16_t h = height;
       if ((x + w - 1) >= display.width())  w = display.width()  - x;
       if ((y + h - 1) >= display.height()) h = display.height() - y;
-      if ((depth == 1) && (rowSize < sizeof(buffer))) // handle with direct drawing
+      if (w < max_row_width) // handle with direct drawing
       {
         valid = true;
-        display.writeScreenBuffer();
-        if (flip) file.seek(imageOffset + (height - h) * rowSize);
-        else file.seek(imageOffset);
-        for (uint16_t row = 0; row < h; row++) // for each line
+        uint8_t bitmask = 0xFF;
+        uint8_t bitshift = 8 - depth;
+        uint16_t red, green, blue;
+        bool whitish, colored;
+        if (depth == 1) with_color = false;
+        if (depth <= 8)
         {
-          file.read(buffer, rowSize);
-          uint16_t y = flip ? h - row - 1 : row;
-          display.writeImage(buffer, 0, y, w, 1);
+          if (depth < 8) bitmask >>= depth;
+          file.seek(54); //palette is always @ 54
+          for (uint16_t pn = 0; pn < (1 << depth); pn++)
+          {
+            blue  = file.read();
+            green = file.read();
+            red   = file.read();
+            file.read();
+            whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+            colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+            if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
+            mono_palette_buffer[pn / 8] |= whitish << pn % 8;
+            if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
+            color_palette_buffer[pn / 8] |= colored << pn % 8;
+          }
         }
-        display.refresh();
-      }
-      else
-      {
-        display.firstPage();
-        do
+        display.writeScreenBuffer();
+        uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
+        for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
         {
-          size_t buffidx = sizeof(buffer); // force buffer load
-          for (uint16_t row = 0; row < h; row++) // for each line
+          uint32_t in_remain = rowSize;
+          uint32_t in_idx = 0;
+          uint32_t in_bytes = 0;
+          uint8_t in_byte = 0; // for depth <= 8
+          uint8_t in_bits = 0; // for depth <= 8
+          uint8_t out_byte = 0;
+          uint8_t out_color_byte = 0;
+          uint32_t out_idx = 0;
+          file.seek(rowPosition);
+          for (uint16_t col = 0; col < w; col++) // for each pixel
           {
-            if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
-              pos = imageOffset + (height - 1 - row) * rowSize;
-            else     // Bitmap is stored top-to-bottom
-              pos = imageOffset + row * rowSize;
-            if (file.position() != pos)
-            { // Need seek?
-              file.seek(pos);
-              buffidx = sizeof(buffer); // force buffer reload
+            // Time to read more pixel data?
+            if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
+            {
+              in_bytes = file.read(input_buffer, in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain);
+              in_remain -= in_bytes;
+              in_idx = 0;
             }
-            uint8_t bits;
-            for (uint16_t col = 0; col < w; col++) // for each pixel
+            switch (depth)
             {
-              // Time to read more pixel data?
-              if (buffidx >= sizeof(buffer))
-              {
-                file.read(buffer, sizeof(buffer));
-                buffidx = 0; // Set index to beginning
-              }
-              switch (depth)
-              {
-                case 1: // one bit per pixel b/w format
+              case 24:
+                blue = input_buffer[in_idx++];
+                green = input_buffer[in_idx++];
+                red = input_buffer[in_idx++];
+                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                break;
+              case 16:
+                {
+                  uint8_t lsb = input_buffer[in_idx++];
+                  uint8_t msb = input_buffer[in_idx++];
+                  if (format == 0) // 555
                   {
-                    valid = true;
-                    if (0 == col % 8)
-                    {
-                      bits = buffer[buffidx++];
-                    }
-                    uint16_t bw_color = bits & 0x80 ? GxEPD_WHITE : GxEPD_BLACK;
-                    display.drawPixel(col, row, bw_color);
-                    bits <<= 1;
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
+                    red   = (msb & 0x7C) << 1;
                   }
-                  break;
-                case 24: // standard BMP format
+                  else // 565
                   {
-                    valid = true;
-                    uint16_t b = buffer[buffidx++];
-                    uint16_t g = buffer[buffidx++];
-                    uint16_t r = buffer[buffidx++];
-                    uint16_t bw_color = ((r + g + b) / 3 > 0xFF  / 2) ? GxEPD_WHITE : GxEPD_BLACK;
-                    display.drawPixel(col, row, bw_color);
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
+                    red   = (msb & 0xF8);
                   }
-                  break;
-              }
-            } // end pixel
-          } // end line
-        } // end do
-        while (display.nextPage());
+                  whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                  colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                }
+                break;
+              case 1:
+              case 4:
+              case 8:
+                {
+                  if (0 == in_bits)
+                  {
+                    in_byte = input_buffer[in_idx++];
+                    in_bits = 8;
+                  }
+                  uint16_t pn = (in_byte >> bitshift) & bitmask;
+                  whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  in_byte <<= depth;
+                  in_bits -= depth;
+                }
+                break;
+            }
+            if (whitish)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            else if (colored && with_color)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+            }
+            else
+            {
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            if (7 == col % 8)
+            {
+              output_row_color_buffer[out_idx] = out_color_byte;
+              output_row_mono_buffer[out_idx++] = out_byte;
+              out_byte = 0;
+              out_color_byte = 0;
+            }
+          } // end pixel
+          uint16_t yrow = y + (flip ? h - row - 1 : row);
+          display.writeImage(output_row_mono_buffer, output_row_color_buffer, x, yrow, w, 1);
+        } // end line
+        Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
+        display.refresh();
       }
     }
   }
-  //Serial.print("end position  "); Serial.println(file.position());
+  //Serial.print("end curPosition  "); Serial.println(file.curPosition());
   file.close();
-  if (valid)
-  {
-    Serial.print("loaded in ");
-    Serial.print(millis() - startTime);
-    Serial.println(" ms");
-  }
-  else
+  if (!valid)
   {
     Serial.println("bitmap format not handled.");
   }
 }
 
-uint16_t read16(FileClass& f)
+uint16_t read16(fs::File& f)
 {
   // BMP data is stored little-endian, same as Arduino.
   uint16_t result;
@@ -325,7 +416,7 @@ uint16_t read16(FileClass& f)
   return result;
 }
 
-uint32_t read32(FileClass& f)
+uint32_t read32(fs::File& f)
 {
   // BMP data is stored little-endian, same as Arduino.
   uint32_t result;

+ 68 - 5
examples/GxEPD2_Spiffs_Loader/GxEPD2_Spiffs_Loader.ino

@@ -27,6 +27,7 @@ const char* fp_github_com     = "ca 06 f5 6b 25 8b 7a 0d 4f 2b 05 47 09 39 47 86
 const char* fp_rawcontent     = "cc aa 48 48 66 46 0e 91 53 2c 9c 7c 23 2a b1 74 4d 29 9d 33";
 const char* host_rawcontent   = "raw.githubusercontent.com";
 const char* path_rawcontent   = "/ZinggJM/GxEPD2/master/extras/bitmaps/";
+const char* path_prenticedavid   = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/";
 
 void setup()
 {
@@ -76,7 +77,9 @@ void setup()
   SPIFFS.begin();
   Serial.println("SPIFFS started");
   listFiles();
-  downloadBitmaps();
+  //deleteFiles();
+  downloadBitmaps_200x200();
+  downloadBitmaps_other();
   listFiles();
 }
 
@@ -84,9 +87,8 @@ void loop()
 {
 }
 
-void downloadBitmaps()
+void downloadBitmaps_200x200()
 {
-  downloadFile_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", "chanceflurries.bmp");
   downloadFile_HTTPS(host_rawcontent, path_rawcontent, "logo200x200.bmp", fp_rawcontent, "logo200x200.bmp");
   downloadFile_HTTPS(host_rawcontent, path_rawcontent, "first200x200.bmp", fp_rawcontent, "first200x200.bmp");
   downloadFile_HTTPS(host_rawcontent, path_rawcontent, "second200x200.bmp", fp_rawcontent, "second200x200.bmp");
@@ -98,6 +100,47 @@ void downloadBitmaps()
   downloadFile_HTTPS(host_rawcontent, path_rawcontent, "eighth200x200.bmp", fp_rawcontent, "eighth200x200.bmp");
 }
 
+void downloadBitmaps_other()
+{
+  downloadFile_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", "chanceflurries.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "betty_1.bmp", fp_rawcontent, "betty_1.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, "betty_4.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "marilyn_240x240x8.bmp", fp_rawcontent, "marilyn_240x240x8.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "miniwoof.bmp", fp_rawcontent, "miniwoof.bmp");
+  //downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "test.bmp", fp_rawcontent, "test.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "tiger.bmp", fp_rawcontent, "tiger.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "tiger_178x160x4.bmp", fp_rawcontent, "tiger_178x160x4.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "tiger_240x317x4.bmp", fp_rawcontent, "tiger_240x317x4.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "tiger_320x200x24.bmp", fp_rawcontent, "tiger_320x200x24.bmp");
+  //downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "tiger16T.bmp", fp_rawcontent, "tiger16T.bmp");
+  downloadFile_HTTPS(host_rawcontent, path_prenticedavid, "woof.bmp", fp_rawcontent, "woof.bmp");
+}
+
+void deleteFiles()
+{
+  SPIFFS.remove("logo200x200.bmp");
+  SPIFFS.remove("first200x200.bmp");
+  SPIFFS.remove("second200x200.bmp");
+  SPIFFS.remove("third200x200.bmp");
+  SPIFFS.remove("fourth200x200.bmp");
+  SPIFFS.remove("fifth200x200.bmp");
+  SPIFFS.remove("sixth200x200.bmp");
+  SPIFFS.remove("seventh200x200.bmp");
+  SPIFFS.remove("eighth200x200.bmp");
+  SPIFFS.remove("chanceflurries.bmp");
+  SPIFFS.remove("betty_1.bmp");
+  SPIFFS.remove("betty_4.bmp");
+  SPIFFS.remove("marilyn_240x240x8.bmp");
+  SPIFFS.remove("miniwoof.bmp");
+  SPIFFS.remove("test.bmp");
+  SPIFFS.remove("tiger.bmp");
+  SPIFFS.remove("tiger_178x160x4.bmp");
+  SPIFFS.remove("tiger_240x317x4.bmp");
+  SPIFFS.remove("tiger_320x200x24.bmp");
+  SPIFFS.remove("tiger16T.bmp");
+  SPIFFS.remove("woof.bmp");
+}
+
 void downloadFile_HTTP(const char* host, const char* path, const char* filename, const char* target)
 {
   WiFiClient client;
@@ -164,8 +207,8 @@ void downloadFile_HTTPS(const char* host, const char* path, const char* filename
 {
   // Use WiFiClientSecure class to create TLS connection
   WiFiClientSecure client;
-  Serial.print("connecting to ");
-  Serial.println(host);
+  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
+  Serial.print("connecting to "); Serial.println(host);
   if (!client.connect(host, httpsPort))
   {
     Serial.println("connection failed");
@@ -221,7 +264,26 @@ void downloadFile_HTTPS(const char* host, const char* path, const char* filename
   }
   while (client.connected())
   {
+    // this doesn't work as expected, but it helps for long downloads
+    int32_t start = millis();
+    for (int16_t t = 0, dly = 50; t < 20; t++, dly += 50)
+    {
+      if (!client.connected()) break;
+      if (client.available()) break; // read would not recover after having returned 0
+      delay(dly);
+    }
+    if (!client.connected()) break;
+    int32_t elapsed = millis() - start;
+    if (elapsed > 250)
+    {
+      Serial.print("waited for available "); Serial.print(millis() - start); Serial.print(" ms @ "); Serial.println(total);
+    }
     size_t available = client.available();
+    if (0 == available)
+    {
+      Serial.print("download error: timeout on available() after "); Serial.print(total); Serial.println(" bytes");
+      break; // don't hang forever
+    }
     size_t fetch = available <= sizeof(buffer) ? available : sizeof(buffer);
     if (fetch > 0)
     {
@@ -230,6 +292,7 @@ void downloadFile_HTTPS(const char* host, const char* path, const char* filename
       total += got;
     }
     delay(1); // yield();
+    if (total > 30000) delay(250); // helps for long downloads
   }
   file.close();
   Serial.print("done, "); Serial.print(total); Serial.println(" bytes transferred");

+ 408 - 124
examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino

@@ -3,6 +3,8 @@
 //
 // Display Library based on Demo Example from Good Display: http://www.good-display.com/download_list/downloadcategoryid=34&isMode=false.html
 //
+// BMP handling code extracts taken from: https://github.com/prenticedavid/MCUFRIEND_kbv/tree/master/examples/showBMP_kbv_Uno
+//
 // Author: Jean-Marc Zingg
 //
 // Version: see library.properties
@@ -40,7 +42,7 @@
 
 #if defined (ESP8266)
 // select one and adapt to your mapping, can use full buffer size (full HEIGHT)
-//GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
+GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_213, GxEPD2_213::HEIGHT> display(GxEPD2_213(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
 //GxEPD2_BW<GxEPD2_270, GxEPD2_270::HEIGHT> display(GxEPD2_270(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
@@ -150,6 +152,10 @@ const char* fp_github_com     = "ca 06 f5 6b 25 8b 7a 0d 4f 2b 05 47 09 39 47 86
 const char* fp_rawcontent     = "cc aa 48 48 66 46 0e 91 53 2c 9c 7c 23 2a b1 74 4d 29 9d 33";
 const char* host_rawcontent   = "raw.githubusercontent.com";
 const char* path_rawcontent   = "/ZinggJM/GxEPD2/master/extras/bitmaps/";
+const char* path_prenticedavid   = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/";
+
+void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color = true);
+void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color = true);
 
 void setup()
 {
@@ -198,52 +204,89 @@ void setup()
   // Print the IP address
   Serial.println(WiFi.localIP());
 
-  showBitmapsFromWiFi();
+  drawBitmaps_200x200();
+  drawBitmaps_other();
 }
 
 void loop(void)
 {
 }
 
-void showBitmapsFromWiFi()
+void drawBitmaps_200x200()
+{
+  int16_t x = (display.width() - 200) / 2;
+  int16_t y = (display.height() - 200) / 2;
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "logo200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "first200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "second200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "third200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fourth200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fifth200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "sixth200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "seventh200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "eighth200x200.bmp", fp_rawcontent, x, y);
+  delay(2000);
+}
+
+void drawBitmaps_other()
 {
-  showBitmapFrom_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", 0, 0);
+  int16_t w2 = display.width() / 2;
+  int16_t h2 = display.height() / 2;
+  showBitmapFrom_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", w2 - 50, h2 - 50, false);
   delay(2000);
-  //return;
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "logo200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_1.bmp", fp_rawcontent, w2 - 100, h2 - 160);
   delay(2000);
-  //return;
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "first200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "second200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "marilyn_240x240x8.bmp", fp_rawcontent, w2 - 120, h2 - 120);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "third200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "miniwoof.bmp", fp_rawcontent, w2 - 60, h2 - 80);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fourth200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "test.bmp", fp_rawcontent, w2 - 100, h2 - 100);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fifth200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger.bmp", fp_rawcontent, w2 - 160, h2 - 120);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "sixth200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_178x160x4.bmp", fp_rawcontent, w2 - 89, h2 - 80);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "seventh200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_240x317x4.bmp", fp_rawcontent, w2 - 120, h2 - 160);
   delay(2000);
-  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "eighth200x200.bmp", fp_rawcontent, 0, 0);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_320x200x24.bmp", fp_rawcontent, w2 - 160, h2 - 100);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger16T.bmp", fp_rawcontent, w2 - 160, h2 - 100);
+  delay(2000);
+  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "woof.bmp", fp_rawcontent, w2 - 100, h2 - 100);
   delay(2000);
 }
 
+static const uint16_t input_buffer_pixels = 640; // may affect performance
+
 static const uint16_t max_row_width = 640; // for up to 7.5" display
+static const uint16_t max_palette_pixels = 256; // for depth <= 8
 
-uint8_t row_buffer[3 * max_row_width]; // buffer for at least one row (of up to 24bit BMP)
+uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
+uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
+uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
+uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
+uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w
 
-void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, uint8_t x, uint8_t y)
+void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color)
 {
   WiFiClient client;
+  bool connection_ok = false;
   bool valid = false; // valid format to be handled
   bool flip = true; // bitmap is stored bottom-to-top
   uint32_t startTime = millis();
   if ((x >= display.width()) || (y >= display.height())) return;
-  Serial.print("connecting to ");
-  Serial.println(host);
+  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
+  Serial.print("connecting to "); Serial.println(host);
   if (!client.connect(host, httpPort))
   {
     Serial.println("connection failed");
@@ -256,17 +299,16 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam
                "User-Agent: GxEPD2_Spiffs_Loader\r\n" +
                "Connection: close\r\n\r\n");
   Serial.println("request sent");
-  bool ok = false;
   while (client.connected())
   {
     String line = client.readStringUntil('\n');
-    if (!ok)
+    if (!connection_ok)
     {
-      ok = line.startsWith("HTTP/1.1 200 OK");
-      if (ok) Serial.println(line);
-      //if (!ok) Serial.println(line);
+      connection_ok = line.startsWith("HTTP/1.1 200 OK");
+      if (connection_ok) Serial.println(line);
+      //if (!connection_ok) Serial.println(line);
     }
-    if (!ok) Serial.println(line);
+    if (!connection_ok) Serial.println(line);
     //Serial.println(line);
     if (line == "\r")
     {
@@ -274,7 +316,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam
       break;
     }
   }
-  if (!ok) return;
+  if (!connection_ok) return;
   // Parse BMP header
   if (read16(client) == 0x4D42) // BMP signature
   {
@@ -288,7 +330,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam
     uint16_t depth = read16(client); // bits per pixel
     uint32_t format = read32(client);
     uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far
-    if ((planes == 1) && (format == 0)) // uncompressed is handled
+    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
     {
       Serial.print("File size: "); Serial.println(fileSize);
       Serial.print("Image Offset: "); Serial.println(imageOffset);
@@ -309,78 +351,177 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam
       uint16_t h = height;
       if ((x + w - 1) >= display.width())  w = display.width()  - x;
       if ((y + h - 1) >= display.height()) h = display.height() - y;
-      uint32_t skip = (flip ? (imageOffset + (height - h) * rowSize) : imageOffset) - bytes_read;
-      //Serial.print("skip "); Serial.println(skip);
-      while ((skip > 0) && client.connected())
-      {
-        client.read(); bytes_read++; skip--;
-      }
-      if (((depth == 1) || (depth == 24)) && (rowSize < sizeof(row_buffer))) // handle with direct drawing
+      if (w < max_row_width) // handle with direct drawing
       {
         valid = true;
-        display.writeScreenBuffer();
-        for (uint16_t row = 0; row < h; row++) // for each line
+        uint8_t bitmask = 0xFF;
+        uint8_t bitshift = 8 - depth;
+        uint16_t red, green, blue;
+        bool whitish, colored;
+        if (depth == 1) with_color = false;
+        if (depth <= 8)
         {
-          delay(1); // yield() to avoid WDT
-          uint32_t got = client.read(row_buffer, rowSize);
-          while ((got < rowSize) && client.connected())
+          if (depth < 8) bitmask >>= depth;
+          bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54
+          for (uint16_t pn = 0; pn < (1 << depth); pn++)
           {
-            delay(1); // yield() to avoid WDT
-            got += client.read(row_buffer + got, rowSize - got);
+            blue  = client.read();
+            green = client.read();
+            red   = client.read();
+            client.read();
+            bytes_read += 4;
+            whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+            colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+            if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
+            mono_palette_buffer[pn / 8] |= whitish << pn % 8;
+            if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
+            color_palette_buffer[pn / 8] |= colored << pn % 8;
+            //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX);
+            //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored);
           }
-          bytes_read += got;
-          if (depth == 24)
+        }
+        display.writeScreenBuffer();
+        uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
+        //Serial.print("skip "); Serial.println(rowPosition - bytes_read);
+        bytes_read += skip(client, rowPosition - bytes_read);
+        for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
+        {
+          if (!connection_ok || !client.connected()) break;
+          delay(1); // yield() to avoid WDT
+          uint32_t in_remain = rowSize;
+          uint32_t in_idx = 0;
+          uint32_t in_bytes = 0;
+          uint8_t in_byte = 0; // for depth <= 8
+          uint8_t in_bits = 0; // for depth <= 8
+          uint8_t out_byte = 0;
+          uint8_t out_color_byte = 0;
+          uint32_t out_idx = 0;
+          for (uint16_t col = 0; col < w; col++) // for each pixel
           {
-            uint8_t out_byte = 0;
-            uint32_t out_idx = 0;
-            uint32_t in_idx = 0;
-            for (uint16_t col = 0; col < w; col++)
+            yield();
+            if (!connection_ok || !client.connected()) break;
+            // Time to read more pixel data?
+            if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
             {
-              uint16_t b = row_buffer[in_idx++];
-              uint16_t g = row_buffer[in_idx++];
-              uint16_t r = row_buffer[in_idx++];
-              if ((r + g + b) / 3 > 0xFF  / 2)
+              uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
+              //if (get > client.available()) delay(200); // does improve? yes, if >= 200
+              // there seems an issue with long downloads on ESP8266
+              tryToWaitForAvailable(client, get);
+              uint32_t got = client.read(input_buffer, get);
+              while ((got < get) && connection_ok)
               {
-                out_byte |= 0x80 >> col % 8;
-              }
-              if (7 == col % 8)
-              {
-                row_buffer[out_idx++] = out_byte;
-                out_byte = 0;
+                //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read);
+                //if ((get - got) > client.available()) delay(200); // does improve? yes, if >= 200
+                // there seems an issue with long downloads on ESP8266
+                tryToWaitForAvailable(client, get - got);
+                uint32_t gotmore = client.read(input_buffer + got, get - got);
+                got += gotmore;
+                connection_ok = gotmore > 0;
               }
+              in_bytes = got;
+              in_remain -= got;
+              bytes_read += got;
             }
-          }
-          uint16_t y = flip ? h - row - 1 : row;
-          display.writeImage(row_buffer, 0, y, w, 1);
-        }
+            if (!connection_ok)
+            {
+              Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!");
+              break;
+            }
+            switch (depth)
+            {
+              case 24:
+                blue = input_buffer[in_idx++];
+                green = input_buffer[in_idx++];
+                red = input_buffer[in_idx++];
+                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                break;
+              case 16:
+                {
+                  uint8_t lsb = input_buffer[in_idx++];
+                  uint8_t msb = input_buffer[in_idx++];
+                  if (format == 0) // 555
+                  {
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
+                    red   = (msb & 0x7C) << 1;
+                  }
+                  else // 565
+                  {
+                    blue  = (lsb & 0x1F) << 3;
+                    green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
+                    red   = (msb & 0xF8);
+                  }
+                  whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                  colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                }
+                break;
+              case 1:
+              case 4:
+              case 8:
+                {
+                  if (0 == in_bits)
+                  {
+                    in_byte = input_buffer[in_idx++];
+                    in_bits = 8;
+                  }
+                  uint16_t pn = (in_byte >> bitshift) & bitmask;
+                  whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  in_byte <<= depth;
+                  in_bits -= depth;
+                }
+                break;
+            }
+            if (whitish)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            else if (colored && with_color)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+            }
+            else
+            {
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            if (7 == col % 8)
+            {
+              output_row_color_buffer[out_idx] = out_color_byte;
+              output_row_mono_buffer[out_idx++] = out_byte;
+              out_byte = 0;
+              out_color_byte = 0;
+            }
+          } // end pixel
+          int16_t yrow = y + (flip ? h - row - 1 : row);
+          display.writeImage(output_row_mono_buffer, output_row_color_buffer, x, yrow, w, 1);
+        } // end line
+        Serial.print("downloaded in ");
+        Serial.print(millis() - startTime);
+        Serial.println(" ms");
         display.refresh();
       }
+      Serial.print("bytes read "); Serial.println(bytes_read);
     }
-    Serial.print("bytes read "); Serial.println(bytes_read);
-    //Serial.print("available  "); Serial.println(client.available());
   }
-  if (valid)
-  {
-    Serial.print("loaded in ");
-    Serial.print(millis() - startTime);
-    Serial.println(" ms");
-  }
-  else
+  if (!valid)
   {
     Serial.println("bitmap format not handled.");
   }
 }
 
-void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, uint8_t x, uint8_t y)
+void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color)
 {
   // Use WiFiClientSecure class to create TLS connection
   WiFiClientSecure client;
+  bool connection_ok = false;
   bool valid = false; // valid format to be handled
   bool flip = true; // bitmap is stored bottom-to-top
   uint32_t startTime = millis();
   if ((x >= display.width()) || (y >= display.height())) return;
-  Serial.print("connecting to ");
-  Serial.println(host);
+  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
+  Serial.print("connecting to "); Serial.println(host);
   if (!client.connect(host, httpsPort))
   {
     Serial.println("connection failed");
@@ -405,17 +546,16 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena
                "User-Agent: GxEPD2_Spiffs_Loader\r\n" +
                "Connection: close\r\n\r\n");
   Serial.println("request sent");
-  bool ok = false;
   while (client.connected())
   {
     String line = client.readStringUntil('\n');
-    if (!ok)
+    if (!connection_ok)
     {
-      ok = line.startsWith("HTTP/1.1 200 OK");
-      if (ok) Serial.println(line);
-      //if (!ok) Serial.println(line);
+      connection_ok = line.startsWith("HTTP/1.1 200 OK");
+      if (connection_ok) Serial.println(line);
+      //if (!connection_ok) Serial.println(line);
     }
-    if (!ok) Serial.println(line);
+    if (!connection_ok) Serial.println(line);
     //Serial.println(line);
     if (line == "\r")
     {
@@ -423,7 +563,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena
       break;
     }
   }
-  if (!ok) return;
+  if (!connection_ok) return;
   // Parse BMP header
   if (read16(client) == 0x4D42) // BMP signature
   {
@@ -437,7 +577,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena
     uint16_t depth = read16(client); // bits per pixel
     uint32_t format = read32(client);
     uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far
-    if ((planes == 1) && (format == 0)) // uncompressed is handled
+    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
     {
       Serial.print("File size: "); Serial.println(fileSize);
       Serial.print("Image Offset: "); Serial.println(imageOffset);
@@ -458,63 +598,159 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena
       uint16_t h = height;
       if ((x + w - 1) >= display.width())  w = display.width()  - x;
       if ((y + h - 1) >= display.height()) h = display.height() - y;
-      uint32_t skip = (flip ? (imageOffset + (height - h) * rowSize) : imageOffset) - bytes_read;
-      //Serial.print("skip "); Serial.println(skip);
-      while ((skip > 0) && client.connected())
-      {
-        client.read(); bytes_read++; skip--;
-      }
-      if (((depth == 1) || (depth == 24)) && (rowSize < sizeof(row_buffer))) // handle with direct drawing
+      if (w < max_row_width) // handle with direct drawing
       {
         valid = true;
-        display.writeScreenBuffer();
-        for (uint16_t row = 0; row < h; row++) // for each line
+        uint8_t bitmask = 0xFF;
+        uint8_t bitshift = 8 - depth;
+        uint16_t red, green, blue;
+        bool whitish, colored;
+        if (depth == 1) with_color = false;
+        if (depth <= 8)
         {
-          delay(1); // yield() to avoid WDT
-          uint32_t got = client.read(row_buffer, rowSize);
-          while ((got < rowSize) && client.connected())
+          if (depth < 8) bitmask >>= depth;
+          bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54
+          for (uint16_t pn = 0; pn < (1 << depth); pn++)
           {
-            delay(1); // yield() to avoid WDT
-            got += client.read(row_buffer + got, rowSize - got);
+            blue  = client.read();
+            green = client.read();
+            red   = client.read();
+            client.read();
+            bytes_read += 4;
+            whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+            colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+            if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
+            mono_palette_buffer[pn / 8] |= whitish << pn % 8;
+            if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
+            color_palette_buffer[pn / 8] |= colored << pn % 8;
+            //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX);
+            //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored);
           }
-          bytes_read += got;
-          if (depth == 24)
+        }
+        display.writeScreenBuffer();
+        uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
+        //Serial.print("skip "); Serial.println(rowPosition - bytes_read);
+        bytes_read += skip(client, rowPosition - bytes_read);
+        for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
+        {
+          if (!connection_ok || !client.connected()) break;
+          delay(1); // yield() to avoid WDT
+          uint32_t in_remain = rowSize;
+          uint32_t in_idx = 0;
+          uint32_t in_bytes = 0;
+          uint8_t in_byte = 0; // for depth <= 8
+          uint8_t in_bits = 0; // for depth <= 8
+          uint8_t out_byte = 0;
+          uint8_t out_color_byte = 0;
+          uint32_t out_idx = 0;
+          for (uint16_t col = 0; col < w; col++) // for each pixel
           {
-            uint8_t out_byte = 0;
-            uint32_t out_idx = 0;
-            uint32_t in_idx = 0;
-            for (uint16_t col = 0; col < w; col++)
+            yield();
+            if (!connection_ok || !client.connected()) break;
+            // Time to read more pixel data?
+            if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
             {
-              uint16_t b = row_buffer[in_idx++];
-              uint16_t g = row_buffer[in_idx++];
-              uint16_t r = row_buffer[in_idx++];
-              if ((r + g + b) / 3 > 0xFF  / 2)
-              {
-                out_byte |= 0x80 >> col % 8;
-              }
-              if (7 == col % 8)
+              uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
+              //if (get > client.available()) delay(200); // does improve? yes, if >= 200
+              // there seems an issue with long downloads on ESP8266
+              tryToWaitForAvailable(client, get);
+              uint32_t got = client.read(input_buffer, get);
+              while ((got < get) && connection_ok)
               {
-                row_buffer[out_idx++] = out_byte;
-                out_byte = 0;
+                //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read);
+                //if ((get - got) > client.available()) delay(200); // does improve? yes, if >= 200
+                // there seems an issue with long downloads on ESP8266
+                tryToWaitForAvailable(client, get - got);
+                uint32_t gotmore = client.read(input_buffer + got, get - got);
+                got += gotmore;
+                connection_ok = gotmore > 0;
               }
+              in_bytes = got;
+              in_remain -= got;
+              bytes_read += got;
             }
-          }
-          uint16_t y = flip ? h - row - 1 : row;
-          display.writeImage(row_buffer, 0, y, w, 1);
-        }
+            if (!connection_ok)
+            {
+              Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!");
+              break;
+            }
+            switch (depth)
+            {
+              case 24:
+                blue = input_buffer[in_idx++];
+                green = input_buffer[in_idx++];
+                red = input_buffer[in_idx++];
+                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                break;
+              case 16:
+                {
+                  uint8_t lsb = input_buffer[in_idx++];
+                  uint8_t msb = input_buffer[in_idx++];
+                  if (format == 0) // 555
+                  {
+                    blue  = (lsb & 0x1F);
+                    green = ((msb & 0x03) << 3) | ((lsb & 0xE0) >> 5);
+                    red   = (msb & 0x7C) >> 2;
+                  }
+                  else // 565
+                  {
+                    blue  = (lsb & 0x1F);
+                    green = ((msb & 0x07) << 3) | ((lsb & 0xE0) >> 5);
+                    red   = (msb & 0xF8) >> 3;
+                  }
+                  whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
+                  colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
+                }
+                break;
+              case 1:
+              case 4:
+              case 8:
+                {
+                  if (0 == in_bits)
+                  {
+                    in_byte = input_buffer[in_idx++];
+                    in_bits = 8;
+                  }
+                  uint16_t pn = (in_byte >> bitshift) & bitmask;
+                  whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
+                  in_byte <<= depth;
+                  in_bits -= depth;
+                }
+                break;
+            }
+            if (whitish)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            else if (colored && with_color)
+            {
+              out_byte |= 0x80 >> col % 8; // not black
+            }
+            else
+            {
+              out_color_byte |= 0x80 >> col % 8; // not colored
+            }
+            if (7 == col % 8)
+            {
+              output_row_color_buffer[out_idx] = out_color_byte;
+              output_row_mono_buffer[out_idx++] = out_byte;
+              out_byte = 0;
+              out_color_byte = 0;
+            }
+          } // end pixel
+          int16_t yrow = y + (flip ? h - row - 1 : row);
+          display.writeImage(output_row_mono_buffer, output_row_color_buffer, x, yrow, w, 1);
+        } // end line
+        Serial.print("downloaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
+        Serial.print("bytes read "); Serial.println(bytes_read);
         display.refresh();
       }
     }
-    Serial.print("bytes read "); Serial.println(bytes_read);
-    //Serial.print("available  "); Serial.println(client.available());
-  }
-  if (valid)
-  {
-    Serial.print("loaded in ");
-    Serial.print(millis() - startTime);
-    Serial.println(" ms");
   }
-  else
+  if (!valid)
   {
     Serial.println("bitmap format not handled.");
   }
@@ -540,3 +776,51 @@ uint32_t read32(WiFiClient& client)
   return result;
 }
 
+uint32_t skip(WiFiClient& client, int32_t bytes)
+{
+  int32_t remain = bytes;
+  int16_t retries = 3;
+  uint32_t chunk = 1024;
+  if (chunk > sizeof(input_buffer)) chunk = sizeof(input_buffer);
+  while (client.connected() && (remain > chunk))
+  {
+    // there seems an issue with long downloads on ESP8266
+    tryToWaitForAvailable(client, chunk);
+    uint32_t got = client.read(input_buffer, chunk);
+    //Serial.print("skipped "); Serial.println(got);
+    remain -= got;
+    if ((0 == got) && (0 == --retries))
+    {
+      Serial.print("Error on skip, got 0, skipped "); Serial.print(bytes - remain); Serial.print(" of "); Serial.println(bytes);
+      break; // don't hang forever (retries don't help)
+    }
+  }
+  if (client.connected() && (remain > 0) && (remain <= chunk))
+  {
+    remain -= client.read(input_buffer, remain);
+  }
+  return bytes - remain; // bytes read and skipped
+}
+
+void tryToWaitForAvailable(WiFiClient& client, int32_t amount)
+{
+  // this doesn't work as expected, but it helps for long downloads
+  int32_t start = millis();
+  for (int16_t t = 0, dly = 50; t < 20; t++, dly += 50)
+  {
+    if (client.available()) break; // read would not recover after having returned 0
+    delay(dly);
+  }
+  for (int16_t t = 0, dly = 50; t < 3; t++, dly += 25)
+  {
+    if (amount <= client.available()) break;
+    delay(dly);
+    //Serial.print("available "); Serial.println(client.available()); // stays constant
+  }
+  int32_t elapsed = millis() - start;
+  if (elapsed > 250)
+  {
+    Serial.print("waited for available "); Serial.print(millis() - start); Serial.println(" ms"); // usually 0 or 225ms
+  }
+}
+