TFT_eSPI/examples/ePaper/Floyd_Steinberg/Floyd_Steinberg_BMP.ino

201 lines
7.4 KiB
C++

/*
Support function for Floyd-Steinberg dithering of an 8bit grey-scale BMP image
on a Monochrome display:
https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
Bitmap format:
https://en.wikipedia.org/wiki/BMP_file_format
Example for https://github.com/Bodmer/TFT_eSPI
The MIT License (MIT)
Copyright (c) 2015 by Bodmer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYBR_DATUM HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Note: drawFSBmp() is a simplified function and does not handle all possible
BMP file header variants. It works OK with 8 bit per pixel grey-scale images
generated by MS Paint and IrfanView.
*/
// https://github.com/Bodmer/TFT_eSPI
//====================================================================================
// Draw an 8 bit grey-scale bitmap (*.BMP) on a Monochrome display using dithering
//====================================================================================
// Uses RAM for buffers (3 * width + 4) ( 532 bytes for 176 pixels)
// Image must be stored in ESP8266 or ESP32 SPIFFS
// Quantisation error distribution for pixel X
// (This is for bottum up drawing of the BMP)
// |-------|-------|-------|
// | +3/16 | +5/16 | +1/16 |
// |-------|-------|-------|
// | | X | +7/16 |
// |-------|-------|-------|
//
void drawFSBmp(const char *filename, int16_t x, int16_t y) {
if ((x >= frame.width()) || (y >= frame.height())) return;
fs::File bmpFS;
// Open requested file
bmpFS = SPIFFS.open( filename, "r");
if (!bmpFS)
{
Serial.print("File not found");
return;
}
uint32_t seekOffset, dib_size;
uint16_t w, h, row, col, num_colors;
uint8_t r, g, b;
if (read16(bmpFS) == 0x4D42) // Check it is a valid bitmap header
{
read32(bmpFS);
read32(bmpFS);
seekOffset = read32(bmpFS); // Pointer to image start
dib_size = read32(bmpFS); // DIB header size, typically 40 bytes
w = read32(bmpFS); // Get width and height of image
h = read32(bmpFS);
// Check it is 1 plane and 8 bits per pixel and no compression
if ((read16(bmpFS) == 1) && (read16(bmpFS) == 8) && (read32(bmpFS) == 0))
{
read32(bmpFS); // Throw away image size
read32(bmpFS); // Throw away x pixels per meter
read32(bmpFS); // Throw away y pixels per meter
num_colors = read32(bmpFS); // Number of colours in colour table (usually 256)
uint8_t pixel_color[num_colors]; // Lookup table for grey-scale
bmpFS.seek(14 + dib_size); // Seek to start of colour table
// Capture the colour lookup table
for (uint16_t i = 0; i < num_colors; i++)
{
uint32_t abgr = read32(bmpFS); // Assume 4 byte, RGB colours in LS 3 bytes
pixel_color[i] = (uint8_t) abgr; // For grey-scale R, G, B are same value
}
bmpFS.seek(seekOffset); // Seek to start of image
uint16_t padding = (4 - (w & 3)) & 3; // Calculate the BMP line padding
// Create an zero an 8 bit pixel line buffer
uint8_t* lineBuffer = ( uint8_t*) calloc(w , sizeof(uint8_t));
// Create a 16 bit signed line buffer for the quantisation error
// Diffusion spreads to x-1 and x+1 so w + 2 avoids a bounds check
int16_t* qerrBuffer = ( int16_t*) calloc((w + 2)<<1, sizeof(uint8_t));
y += h - 1; // Start from bottom (assumes bottum up!)
// Draw row by row from bottom up
for (row = 0; row < h; row++) {
// Read a row of pixels
bmpFS.read(lineBuffer, w);
// Prep variables
uint16_t dx = 0;
uint8_t* bptr = lineBuffer;
int16_t* qptr = qerrBuffer + 1; // + 1 because diffusion spreads to x-1
// Lookup color, add quantisation error, clip and clear error buffer
while(dx < w)
{
int16_t depixel = pixel_color[(uint8_t)*bptr] + *qptr;
if (depixel >255) depixel = 255; // Clip pixel to 0-255
else if (depixel < 0) depixel = 0;
*bptr++ = (uint8_t) depixel; // Save new value, inc pointer
*qptr++ = 0; // Zero error, inc pointer
dx++; // Next pixel
}
dx = 0; // Reset varaibles to start of line
bptr = lineBuffer;
qptr = qerrBuffer + 1;
int32_t qerr = 0;
int32_t qerr16 = 0;
// Push the pixel row to screen
while(dx < w)
{
// Add 7/16 of error (error = 0 on first entry)
int16_t pixel = *bptr + (qerr>>1) - qerr16;
// Do not clip here so quantisation error accumulates correctly?
// Draw pixel (black or white) and determine new error
if (pixel < 128) { frame.drawPixel(x + dx, y, INK); qerr = pixel; }
else qerr = pixel - 255;
// Diffuse into error buffer for next pixel line
qerr16 = qerr>>4; // 1/16 of error
*(qptr - 1) += (qerr>>2) - qerr16; // Add 3/16 of error
*(qptr ) += (qerr>>2) + qerr16; // Add 5/16 of error
*(qptr + 1) += qerr16; // Add 1/16 of error
bptr++; // Move along pixel and error buffers
qptr++;
dx++; // Move coordinate along
}
y--;
// Read any line padding (saves a slow seek)
if (padding) bmpFS.read(lineBuffer, padding);
}
free(lineBuffer);
free(qerrBuffer);
}
else Serial.println("BMP format not recognized.");
}
bmpFS.close();
}
//====================================================================================
// Read a 16 bit value from the filing system
//====================================================================================
uint16_t read16(fs::File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
//====================================================================================
// Read a 32 bit value from the filing system
//====================================================================================
uint32_t read32(fs::File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
// TODO: Add support for colour images by converting RGB to grey-scale
// grey = (R+G+B)/3