Tasmota/lib/libesp32/JPEGDEC/examples/dithering/dithering.ino
2025-09-07 20:16:53 +02:00

119 lines
4.8 KiB
C++

//
// Example to demonstrate the built-in dithering features of JPEGDEC
// Dithering is available for grayscale output (1/2/4-bits per pixel)
// This example makes use of my FastEPD library for parallel e-ink displays
// The target device is the M5Stack PaperS3, but any 1-bit or 4-bit grayscale display
// can be substituted
//
#include <JPEGDEC.h>
#include <FastEPD.h>
#include "../../test_images/tulips.h" // 640x480 color images
FASTEPD epaper;
JPEGDEC jpg;
//
// JPEGDraw callback function
// called periodically with blocks of multiple MCUs of pixels
// We're requesting 1 or 4-bit pixels, so with decodeDither()
// that's the pixel format that JPEGDEC supplies
//
// 4-bpp = (bits) aaaabbbb. a = top nibble, left pixel, b = bottom nibble, right pixel
// 0 = black, 0xf (15) = white
//
// 1-bpp = (bits) abcdefgh. a = MSB (0x80), leftmost pixel, h = LSB (0x01), rightmost pixel
// 0 = black, 1 = white
//
// This example function does not take into account rotation of the display buffer
// If you want to view the image rotated from the native orientation of the display
// you'll need to write the pixels in the rotated direction.
//
int JPEGDraw(JPEGDRAW *pDraw)
{
int y, iPitch, shift;
uint8_t *s, *d, *pBuffer = epaper.currentBuffer();
if (jpg.getPixelType() == ONE_BIT_DITHERED) {
shift = 3;
} else {
shift = 1;
}
iPitch = epaper.width() >> shift; // destination pitch of the display framebuffer
for (y=0; y<pDraw->iHeight; y++) { // this is 8 or 16 depending on the color subsampling
// This is the destination pointer into the correct spot (with centering)
// in the FastEPD framebuffer
d = &pBuffer[((pDraw->y + y)*iPitch) + (pDraw->x >> shift)];
// This is the pointer to the source pixels provided by this JPEGDraw callback
// each line advances pDraw->iWidth pixels into the memory
s = (uint8_t *)pDraw->pPixels;
s += (y * (pDraw->iWidth >> shift));
// The pixel format of the display is the same as JPEGDEC, so just copy it
memcpy(d, s, pDraw->iWidth >> shift);
} // for y
return 1; // continue decoding
} /* JPEGDraw() */
void setup()
{
int rc, w, h, xoff, yoff;
uint8_t *pDither;
Serial.begin(115200);
delay(3000); // wait for CDC-Serial to start
// Initialize the ESP32-S3 I/O and allocate (PSRAM) for the framebuffer
rc = epaper.initPanel(BB_PANEL_M5PAPERS3); // 960x540 parallel eink
if (rc != BBEP_SUCCESS) { // something went wrong
Serial.println("Error initializing eink display!");
while (1) {}; // wait forever
}
epaper.fillScreen(BBEP_WHITE); // start in 1-bit mode
epaper.setFont(FONT_12x16);
epaper.setTextColor(BBEP_BLACK);
epaper.setCursor(312, 6);
epaper.print("1-bit Floyd Steinberg Dither");
if (jpg.openFLASH((uint8_t *)tulips, sizeof(tulips), JPEGDraw)) {
// 1-bit dithered can fit in the existing JPEGIMAGE structure
jpg.setPixelType(ONE_BIT_DITHERED); // request 1-bit dithered output
w = jpg.getWidth();
h = jpg.getHeight();
// We need to allocate extra memory to do dithering because it needs the full
// width of the image x 16 rows to do Floyd Steinberg's algorithm properly
pDither = (uint8_t *)malloc(w * 16);
// center the image on the display
xoff = (epaper.width() - w)/2;
yoff = (epaper.height() - h)/2;
jpg.decodeDither(xoff, yoff, pDither, 0); // decode it and pass centering offsets
jpg.close();
free(pDither);
epaper.fullUpdate(true); // display the 1-bpp dithered image
}
delay(3000); // allow time to observe it
// Now switch to 4-bpp grayscale mode and use 4-bpp dithering
epaper.setMode(BB_MODE_4BPP);
epaper.fillScreen(0xf); // 0-15 in grayscale mode, 0=black, 15 = white
epaper.setCursor(312, 6);
epaper.print("4-bit Floyd Steinberg Dither");
// Decode the same image again
if (jpg.openFLASH((uint8_t *)tulips, sizeof(tulips), JPEGDraw)) {
// 1-bit dithered can fit in the existing JPEGIMAGE structure
jpg.setPixelType(FOUR_BIT_DITHERED); // request 4-bit dithered output
w = jpg.getWidth();
h = jpg.getHeight();
// We need to allocate extra memory to do dithering because it needs the full
// width of the image x 16 rows to do Floyd Steinberg's algorithm properly
// Neighboring pixels are used to spread the color errors and if we limit it
// to the JPEG MCU blocks, visible lines will form on block boundaries
pDither = (uint8_t *)malloc(w * 16);
// center the image on the display
xoff = (epaper.width() - w)/2;
yoff = (epaper.height() - h)/2;
jpg.decodeDither(xoff, yoff, pDither, 0); // decode it and pass centering offsets
jpg.close();
free(pDither);
epaper.fullUpdate(true); // display the 1-bpp dithered image
}
// done, program will end
} /* setup() */
void loop()
{
// nothing needed here
} /* loop() */