From ada678d76e71207c0b10be1c65c2bb92ebfb1cfe Mon Sep 17 00:00:00 2001 From: Bodmer Date: Mon, 2 Apr 2018 02:53:42 +0100 Subject: [PATCH] Added Waveshare ePaper support Also added ability to push 1bpp bitmaps to a Sprite to support rendering images on an EPaper screen. Floyd-Steinberg dithering and basic graphics example added as first ePaper demo. --- Extensions/Sprite.cpp | 75 ++++++- TFT_eSPI.cpp | 1 - User_Setup_Select.h | 18 +- User_Setups/Setup17_ePaper.h | 94 +++++++++ examples/ePaper/Floyd_Steinberg/EPD_Support.h | 105 ++++++++++ .../Floyd_Steinberg/Floyd_Steinberg.ino | 187 +++++++++++++++++ .../Floyd_Steinberg/Floyd_Steinberg_BMP.ino | 198 ++++++++++++++++++ examples/ePaper/Floyd_Steinberg/SPIFFS.ino | 92 ++++++++ .../ePaper/Floyd_Steinberg/data/TestCard.bmp | Bin 0 -> 47542 bytes .../ePaper/Floyd_Steinberg/data/Tiger.bmp | Bin 0 -> 42262 bytes 10 files changed, 760 insertions(+), 10 deletions(-) create mode 100644 User_Setups/Setup17_ePaper.h create mode 100644 examples/ePaper/Floyd_Steinberg/EPD_Support.h create mode 100644 examples/ePaper/Floyd_Steinberg/Floyd_Steinberg.ino create mode 100644 examples/ePaper/Floyd_Steinberg/Floyd_Steinberg_BMP.ino create mode 100644 examples/ePaper/Floyd_Steinberg/SPIFFS.ino create mode 100644 examples/ePaper/Floyd_Steinberg/data/TestCard.bmp create mode 100644 examples/ePaper/Floyd_Steinberg/data/Tiger.bmp diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp index 90fadae..0ebca60 100644 --- a/Extensions/Sprite.cpp +++ b/Extensions/Sprite.cpp @@ -304,7 +304,42 @@ void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, uint1 } } } - // TODO Currently does nothing for 1 bpp + + else // 1bpp + { + // Move coordinate rotation to support fn + if (_rotation == 1) + { + int32_t tx = x; + x = _dwidth - y - 1; + y = tx; + } + else if (_rotation == 2) + { + x = _dwidth - x - 1; + y = _dheight - y - 1; + } + else if (_rotation == 3) + { + int32_t tx = x; + x = y; + y = _dheight - tx - 1; + } + + uint8_t* pdata = (uint8_t*) data; + uint32_t ww = (w+7) & 0xFFF8; + for (int32_t yp = 0; yp // Setup file configured for my ST7735 128x128 display //#include // Setup file configured for my ILI9163 128x128 display //#include // Setup file configured for my ST7735 -//#include // Setup file configured for my stock RPi TFT with touch -//#include // Setup file configured for my stock RPi TFT with touch +//#include // Setup file configured for ESP8266 and RPi TFT with touch +//#include // Setup file configured for ESP32 and RPi TFT with touch //#include // Setup file for the ESP32 based M5Stack //#include // Setup file for the ESP32 with parallel bus TFT //#include // Setup file for the ESP32 with parallel bus TFT //#include // Setup file configured for HX8357D (untested) //#include // Setup file for the ESP32 with parallel bus TFT +//#include // Setup file for any Waveshare ePaper display -//#include - -// ePaper #include // Setup file template for copying/editting +//#include #endif // USER_SETUP_LOADED @@ -70,7 +69,7 @@ #define TFT_DRIVER 0x6D02 #elif defined (RPI_ILI9486_DRIVER) #include - #define TFT_DRIVER 0x9481 + #define TFT_DRIVER 0x9486 #elif defined (ILI9481_DRIVER) #include #define TFT_DRIVER 0x9481 @@ -83,6 +82,11 @@ #elif defined (EPD_DRIVER) #include "TFT_Drivers/EPD_Defines.h" #define TFT_DRIVER 0xE9D +#elif defined (XYZZY_DRIVER) // <<<<<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVER HERE + #include "TFT_Drivers/XYZZY_Defines.h" + #define TFT_DRIVER 0x0000 +#else + #define TFT_DRIVER 0x0000 #endif // These are the pins for all ESP8266 boards diff --git a/User_Setups/Setup17_ePaper.h b/User_Setups/Setup17_ePaper.h new file mode 100644 index 0000000..3704cc5 --- /dev/null +++ b/User_Setups/Setup17_ePaper.h @@ -0,0 +1,94 @@ +// USER DEFINED SETTINGS +// Set driver type, fonts to be loaded etc +// +// See the User_Setup_Select.h file if you wish to be able to define multiple +// setups and then easily select which setup file is used by the compiler. +// +// If this file is edited correctly then all the library example sketches should +// run without the need to make any more changes for a particular hardware setup! + +// ################################################################################## +// +// Section 0. Call up the right driver file and any options for it +// +// ################################################################################## + +#define EPD_DRIVER // ePaper driver + +// ################################################################################## +// +// Section 1. Define the pins that are used to interface with the display here +// +// ################################################################################## + + +// READ THIS READ THIS READ THIS READ THIS READ THIS READ THIS +// Install the ePaper library for your own display size and type +// from here: +// https://github.com/Bodmer/EPD_Libraries + +// Note: Pin allocations for the ePaper signals are defined in +// the ePaper library's epdif.h file. There follows the default +// pins already included in epdif.h file for the ESP8266: + +/////////////////////////////////////////////////////////////////// +// For ESP8266 connect as follows: // +// Display 3.3V to NodeMCU 3V3 // +// Display GND to NodeMCU GND // +// // +// Display GPIO NodeMCU pin // +// BUSY 5 D1 // +// RESET 4 D2 // +// DC 0 D3 // +// CS 2 D4 // +// CLK 14 D5 // +// D6 (MISO not connected to display) // +// DIN 13 D7 // +// // +/////////////////////////////////////////////////////////////////// + + +// ################################################################################## +// +// Section 2. Not used +// +// ################################################################################## + + +// ################################################################################## +// +// Section 3. Define the fonts that are to be used here +// +// ################################################################################## + +// Comment out the #defines below with // to stop that font being loaded +// The ESP8366 and ESP32 have plenty of memory so commenting out fonts is not +// normally necessary. If all fonts are loaded the extra FLASH space required is +// about 17Kbytes. To save FLASH space only enable the fonts you need! + +#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH +#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters +#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters +#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm +#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. +//#define LOAD_FONT8N // Font 8. Alternative to Font 8 below, slightly narrower, so 3 digits fit a 160 pixel TFT +#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. +#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts + +// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded +// this will save ~20kbytes of FLASH +#define SMOOTH_FONT + +// ################################################################################## +// +// Section 4. Not used +// +// ################################################################################## + + +// ################################################################################## +// +// Section 5. Not used +// +// ################################################################################## + diff --git a/examples/ePaper/Floyd_Steinberg/EPD_Support.h b/examples/ePaper/Floyd_Steinberg/EPD_Support.h new file mode 100644 index 0000000..ce3ce1d --- /dev/null +++ b/examples/ePaper/Floyd_Steinberg/EPD_Support.h @@ -0,0 +1,105 @@ +/* Support definitions and functions for ePaper examples + * These tailor the library and screen settings + * Must be a header file to ensure #defines are established first + * + * Created by Bodmer 30/3/18 for TFT_eSPI library: + * https://github.com/Bodmer/TFT_eSPI + */ + +/* + EPD_WIDTH and EPD_HEIGHT are automatically defined here based on the library selected + + For 2 colour ePaper displays create one frame pointer in sketch: + uint8_t* framePtr; + + For 3 colour ePaper displays create two frame pointers in sketch: + uint8_t* blackFramePtr; + uint8_t* redFramePtr; + + Call this function to update whole display: + updateDisplay(); +*/ +// Install the ePaper library for your own display size and type +// from here: +// https://github.com/Bodmer/EPD_Libraries + + +//------------------------------------------------------------------------------------ +// Define which colour values are paper and ink +//------------------------------------------------------------------------------------ +#if defined (EPD2IN7B_H) + #define COLORED 1 // EPD2IN7B is opposite to all others! + #define UNCOLORED 0 +#else + #define COLORED 0 + #define UNCOLORED 1 +#endif + + +//------------------------------------------------------------------------------------ +// Define the width and height of the different displays +//------------------------------------------------------------------------------------ +#if defined (EPD1IN54_H) || defined (EPD1IN54B_H) + #define EPD_WIDTH 200 // Frame buffer is 5000 bytes + #define EPD_HEIGHT 200 + +#elif defined (EPD1IN54C_H) + #define EPD_WIDTH 152 // 2 frame buffers of 2888 bytes each + #define EPD_HEIGHT 152 + +#elif defined (EPD2IN7_H) || defined (EPD2IN7B_H) + #define EPD_WIDTH 176 // Frame buffer is 5808 bytes + #define EPD_HEIGHT 264 + +#elif defined (EPD2IN9_H) || defined (EPD2IN9B_H) + #define EPD_WIDTH 128 // Frame buffer is 4736 bytes + #define EPD_HEIGHT 296 + +#elif defined (EPD2IN13_H) + #define EPD_WIDTH 122 // Frame buffer is 4000 bytes + #define EPD_HEIGHT 250 + +#elif defined (EPD2IN13B_H) + #define EPD_WIDTH 104 // 2 frame buffers of 2756 bytes each + #define EPD_HEIGHT 212 + +#elif defined (EPD4IN2_H) + #define EPD_WIDTH 400 // Frame buffer is 15000 bytes + #define EPD_HEIGHT 300 + +// ESP8266 has just enough RAM for a 2 color 7.5" display full screen buffer +// ESP32 has just enough RAM for 2 or 3 color 7.5" display +// (Without using partial screen updates) +#elif defined (EPD7IN5_H) || defined (EPD7IN5B_H) + #define EPD_WIDTH 640 // 2 colour frame buffer is 30720 bytes + #define EPD_HEIGHT 384 // 2 colour frame buffer is 61440 bytes + +#else + # error "Selected ePaper library is not supported" + +#endif + + +//------------------------------------------------------------------------------------ +// Update display - different displays have different function names in the default +// Waveshare libraries :-( +//------------------------------------------------------------------------------------ +#if defined (EPD1IN54B_H) || defined(EPD1IN54C_H) || defined(EPD2IN13B_H) || defined(EPD2IN7B_H) || defined(EPD2IN9B_H) || defined(EPD4IN2_H) + void updateDisplay(uint8_t* blackFrame = blackFramePtr, uint8_t* redFrame = redFramePtr) + { + ePaper.DisplayFrame(blackFrame, redFrame); // Update 3 colour display +#else + void updateDisplay(uint8_t* blackFrame = framePtr) + { + #if defined (EPD2IN7_H) || defined(EPD4IN2_H) + ePaper.DisplayFrame(blackFrame); // Update 2 color display + + #elif defined (EPD1IN54_H) || defined(EPD2IN13_H) || defined(EPD2IN9_H) + ePaper.SetFrameMemory(blackFrame); // Update 2 colour display + + #else + # error "Selected ePaper library is not supported" + #endif +#endif +} + diff --git a/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg.ino b/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg.ino new file mode 100644 index 0000000..1245a8d --- /dev/null +++ b/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg.ino @@ -0,0 +1,187 @@ +// Display grey-scale images on a Monchrome ePaper display using +// Floyd-Steinberg dithering +// https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering + +// Example created by Bodmer 31/3/18 for TFT_eSPI library: +// https://github.com/Bodmer/TFT_eSPI +// Select the ePaper setup in library's "User_Setup_Select.h" file + +// This sketch supports Waveshare 2 colour ePaper displays +// https://www.waveshare.com/product/modules/oleds-lcds/e-paper.htm + +// Test images are in the Data folder with sketch (press Ctrl+k) +// Upload using the Tools menu "ESP8266 Sketch Data Upload" option + +/////////////////////////////////////////////////////////////////// +// For ESP8266 connect as follows: // +// Display 3.3V to NodeMCU 3V3 // +// Display GND to NodeMCU GND // +// // +// Display GPIO NodeMCU pin // +// BUSY 5 D1 // +// RESET 4 D2 // +// DC 0 D3 // +// CS 2 D4 // +// CLK 14 D5 // +// D6 (MISO not connected to display) // +// DIN 13 D7 // +// // +// Note: Pin allocations for the ePaper signals are defined in // +// ePaper library's epdif.h file, above are the default pins // +/////////////////////////////////////////////////////////////////// + +// READ THIS READ THIS READ THIS READ THIS READ THIS READ THIS +// Install the ePaper library for your own display size and type +// from here: +// https://github.com/Bodmer/EPD_Libraries + +// The following is for the Waveshare 2.7" colour ePaper display +// include where ?.?? is screen size in inches + +#include // Screen specific library +Epd ePaper; // Create an instance ePaper + +#include // Graphics library and Sprite class + +TFT_eSPI glc = TFT_eSPI(); // Invoke the graphics library class +TFT_eSprite frame = TFT_eSprite(&glc); // Invoke the Sprite class for the image frame buffer + +#define INK COLORED // Black ink +#define PAPER UNCOLORED // 'paper' background colour + +uint16_t epd_width = EPD_WIDTH; // Set the initial values, these are swapped +uint16_t epd_height = EPD_HEIGHT; // in different landscape/portrait rotations + // so call frame.width() or frame.height() to get new values + +#define EPD_BUFFER 1 // Label for the black frame buffer 1 + +uint8_t* framePtr = NULL; // Pointer for the black frame buffer + +#include "EPD_Support.h" // Include sketch EPD support functions last! + +int8_t limit = 5; // Limit the number of loops before halting +//------------------------------------------------------------------------------------ +// Setup +//------------------------------------------------------------------------------------ +void setup() { + + Serial.begin(250000); // Used for messages + + // Initialise the ePaper library + if (ePaper.Init() != 0) { + Serial.print("ePaper init failed"); + while (1) yield(); // Wait here until re-boot + } + + Serial.println("\r\n ePaper initialisation OK"); + + // Initialise the SPIFFS filing system + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); // Stay here twiddling thumbs + } + + Serial.println(" SPIFFS initialisation OK"); + + frame.setColorDepth(1); // Must set the bits per pixel to 1 for ePaper displays + // Set bit depth BEFORE creating Sprite, default is 16! + + // Create a frame buffer in RAM of defined size and save the pointer to it + // RAM needed is about (EPD_WIDTH * EPD_HEIGHT)/8 , ~5000 bytes for 200 x 200 pixels + // Note: always create the Sprite before setting the Sprite rotation + framePtr = (uint8_t*) frame.createSprite(EPD_WIDTH, EPD_HEIGHT); + + Serial.println("\r\nInitialisation done."); + + listFiles(); // List all the files in the SPIFFS +} + +//------------------------------------------------------------------------------------ +// Loop +//------------------------------------------------------------------------------------ +void loop() { + + frame.setRotation(random(4)); // Set the rotation to 0, 1, 2 or 3 ( 1 & 3 = landscape) + + frame.fillSprite(PAPER); + + // Draw 8 bit grey-scale bitmap using Floyd-Steinberg dithering at x,y + // /File name x y + //drawFSBmp("/TestCard.bmp", 0, 0); // 176 x 264 pixels + + drawFSBmp("/Tiger.bmp", (frame.width()-176)/2, (frame.height()-234)/2); // 176 x 234 pixels + + updateDisplay(); // Send image to display and refresh + + delay(5000); + + frame.fillSprite(PAPER); // Fill frame with white + + // Draw circle in frame buffer (x, y, r, color) in center of screen + frame.drawCircle(frame.width()/2, frame.height()/2, frame.width()/6, INK); + + // Draw diagonal lines + frame.drawLine(0 , 0, frame.width()-1, frame.height()-1, INK); + frame.drawLine(0 , frame.height()-1, frame.width()-1, 0, INK); + + updateDisplay(); // Send image to display and refresh + + delay(3000); + + // Run a rotation test + rotateTest(); + + // Put screen to sleep to save power (if wanted) + ePaper.Sleep(); + + if (--limit <= 0) while(1) yield(); // Wait here + + delay(20000); // Wait here for 20s + + // Wake up ePaper display so we can talk to it + Serial.println("Waking up!"); + ePaper.Init(); + +} // end of loop() + + +//------------------------------------------------------------------------------------ +// setRotation() actually rotates the drawing coordinates, not the whole display frame +// buffer so we can use this to draw text at right angles or upside down +//------------------------------------------------------------------------------------ +void rotateTest(void) +{ + //frame.fillSprite(PAPER); // Fill buffer with white to clear old graphics + + // Draw some text in frame buffer + frame.setTextFont(4); // Select font 4 + frame.setTextColor(INK); // Set colour to ink + frame.setTextDatum(TC_DATUM); // Middle centre text datum + + frame.setRotation(0); // Set the display rotation to 0, 1, 2 or 3 ( 1 & 3 = landscape) + epd_width = frame.width(); // Get the values for the current rotation + epd_height = frame.height(); // epd_height is not used in this sketch + + frame.drawString("Rotation 0", epd_width / 2, 10); + + frame.setRotation(1); // Set the display rotation to 1 + epd_width = frame.width(); // Get the values for the current rotation + epd_height = frame.height(); // epd_height is not used in this sketch + + frame.drawString("Rotation 1", epd_width / 2, 10); + + frame.setRotation(2); // Set the display rotation to 2 + epd_width = frame.width(); // Get the values for the current rotation + epd_height = frame.height(); // epd_height is not used in this sketch + + frame.drawString("Rotation 2", epd_width / 2, 10); + + frame.setRotation(3); // Set the display rotation to 3 + epd_width = frame.width(); // Get the values for the current rotation + epd_height = frame.height(); // epd_height is not used in this sketch + + frame.drawString("Rotation 3", epd_width / 2, 10); + + Serial.println("Updating display"); + updateDisplay(); // Update display +} diff --git a/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg_BMP.ino b/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg_BMP.ino new file mode 100644 index 0000000..e78c741 --- /dev/null +++ b/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg_BMP.ino @@ -0,0 +1,198 @@ +/* + 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); + } + } + 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 + diff --git a/examples/ePaper/Floyd_Steinberg/SPIFFS.ino b/examples/ePaper/Floyd_Steinberg/SPIFFS.ino new file mode 100644 index 0000000..ff35f61 --- /dev/null +++ b/examples/ePaper/Floyd_Steinberg/SPIFFS.ino @@ -0,0 +1,92 @@ + + // Call up the SPIFFS FLASH filing system + #define FS_NO_GLOBALS + #include + + #ifdef ESP32 + #include "SPIFFS.h" + #endif + + /*==================================================================================== + This sketch supports the ESP6266 and ESP32 SPIFFS filing system + + Created by Bodmer 15th Jan 2017 + ==================================================================================*/ + +//==================================================================================== +// Print a SPIFFS directory list (root directory) +//==================================================================================== + +void listFiles(void) { + Serial.println(); + Serial.println("SPIFFS files found:"); + +#ifdef ESP32 + listDir(SPIFFS, "/", true); +#else + fs::Dir dir = SPIFFS.openDir("/"); // Root directory + String line = "====================================="; + + Serial.println(line); + Serial.println(" File name Size"); + Serial.println(line); + + while (dir.next()) { + String fileName = dir.fileName(); + Serial.print(fileName); + int spaces = 25 - fileName.length(); // Tabulate nicely + if (spaces < 0) spaces = 1; + while (spaces--) Serial.print(" "); + fs::File f = dir.openFile("r"); + Serial.print(f.size()); Serial.println(" bytes"); + yield(); + } + + Serial.println(line); +#endif + Serial.println(); + delay(1000); +} +//==================================================================================== + +#ifdef ESP32 +void listDir(fs::FS &fs, const char * dirname, uint8_t levels) { + Serial.printf("Listing directory: %s\n", dirname); + + fs::File root = fs.open(dirname); + if (!root) { + Serial.println("Failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return; + } + + fs::File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("DIR : "); + String fileName = file.name(); + Serial.print(fileName); + if (levels) { + listDir(fs, file.name(), levels - 1); + } + } else { + String fileName = file.name(); + Serial.print(" " + fileName); + int spaces = 32 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + String fileSize = (String) file.size(); + spaces = 8 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + file = root.openNextFile(); + } +} +#endif diff --git a/examples/ePaper/Floyd_Steinberg/data/TestCard.bmp b/examples/ePaper/Floyd_Steinberg/data/TestCard.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3a18f0cd57fc8ec887f26f63b177f6beeb7cc9db GIT binary patch literal 47542 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 Szyz286JP>NfC>Dz1pWmKz)6n) literal 0 HcmV?d00001 diff --git a/examples/ePaper/Floyd_Steinberg/data/Tiger.bmp b/examples/ePaper/Floyd_Steinberg/data/Tiger.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ef26eb18d6cf4b49bef1ccba605cd79b092eba06 GIT binary patch literal 42262 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 Nqcsss%~LyInCvLq}C)Z*o;=JLUqq0K)CD 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