Can't display dithered output on TFT display
clayton-cg opened this issue · comments
Hi there! Thanks for the great library! I'm trying to display a dithered output on a TFT display. I can use the normal decode() function just fine to display a camera feed at about 12fps, but when I try to use the decodeDither() function I get blank rows of pixels on the display. My ultimate goal is to use the ONE_BIT_DITHERED output on a Sharp Memory Display, but I am currently working with a TFT as an intermediate step. Based on my code, do you have any ideas on what I might be doing wrong? Does JPEGDraw need to change when using decodeDither()?
//Pinouts
//************************************************//
//Camera - Xiao ESP32S3 Sense OV2640
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 10
#define SIOD_GPIO_NUM 40
#define SIOC_GPIO_NUM 39
#define Y9_GPIO_NUM 48
#define Y8_GPIO_NUM 11
#define Y7_GPIO_NUM 12
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 16
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 17
#define Y2_GPIO_NUM 15
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM 47
#define PCLK_GPIO_NUM 13
#define LED_GPIO_NUM 21
#define USE_TFT
#define USE_CAM
#define USE_JPG
// #define USE_TFT_ESPI
#define USE_ADAFRUIT_GFX
#define USE_DITHER
// #define MYDEBUG
//************************************************//
//Separate includes
//************************************************//
//Camera includes
#ifdef USE_CAM
#include "esp_camera.h"
#include "camera_index.h"
#endif
//LCD includes
#ifdef USE_TFT
#include <SPI.h>
#ifdef USE_JPG
// #include <TJpg_Decoder.h>
#include <JPEGDEC.h>
JPEGDEC jpg;
#endif
#include <TFT_eSPI.h> // Hardware-specific library
// #include <Adafruit_SharpMem.h>
#endif
//General includes
#include "Arduino.h"
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h" // to hold led pin state constant
//************************************************//
//Separate feature setups
//************************************************//
//TFT setup
#ifdef USE_TFT
#ifdef USE_TFT_ESPI
TFT_eSPI tft = TFT_eSPI();
#endif
#ifdef USE_ADAFRUIT_GFX
#include "Adafruit_GFX.h"
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#define TFT_DC D3
#define TFT_CS D1
#define TFT_RST -1
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
// any pins can be used
// #define SHARP_SCK D8
// #define SHARP_MOSI D10
// #define SHARP_SS D1
// Set the size of the display here, e.g. 144x168!
// Adafruit_SharpMem display(SHARP_SCK, SHARP_MOSI, SHARP_SS, 400, 240);
#endif
#endif
//Camera setup
#ifdef USE_CAM
camera_fb_t * fb = NULL;
int i = 0;
int n = 100; //n is number of frames to average over for fps calculation
float fps;
static esp_err_t cam_err;
// #ifdef USE_JPG
// #ifdef USE_TFT
// bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap){
// if ( y >= tft.height() ) return 0;
// tft.pushImage(x, y, w, h, bitmap);
// return 1;
// }
// #endif
// bool no_tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap){
// if ( y >= tft.height() ) return 0;
// return 1;
// }
// #endif
bool setup_camera() { //true: set up for jpg capture, false set up for rgb565 capture
//Configure the camera
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
#ifdef USE_JPG
config.pixel_format = PIXFORMAT_JPEG;
#else
config.pixel_format = PIXFORMAT_RGB565;
#endif
//init with high specs to pre-allocate larger buffers
if (psramFound()) {
//Serial.println("psram found");
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
//Serial.println("psram not found");
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// camera init
cam_err = esp_camera_init(&config);
if (cam_err != ESP_OK) {
//Serial.printf("Camera init failed with error 0x%x", cam_err);
return false;
}
sensor_t * s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_QVGA);
s->set_vflip(s, 1);
s->set_saturation(s, 2);
Serial.println("Camera initialized");
// #ifdef USE_JPG
// #ifdef USE_TFT
// TJpgDec.setJpgScale(1);
// TJpgDec.setSwapBytes(true);
// TJpgDec.setCallback(tft_output);
// #endif
// #endif
return true;
}
#endif
#ifdef USE_DITHER
uint8_t ditherSpace[320 * 16];
bool dither = true;
#endif
#ifdef USE_TFT
bool setup_lcd() {
// start & clear the display
// display.begin();
// display.clearDisplay();
#ifdef USE_TFT_ESPI
tft.begin();
#endif
#ifdef USE_ADAFRUIT_GFX
tft.init(240, 320);
#endif
tft.setRotation(3); // 0 & 2 Portrait. 1 & 3 landscape
// tft.fillScreen(TFT_GREEN);
Serial.println("TFT works");
Serial.println("LCD initialized");
return true;
}
#endif
void setup() {
Serial.begin(9600);
Serial.println("-----------------------------------");
Serial.println("starting");
// Start the lcd
#ifdef USE_TFT
if (not setup_lcd()){
return;
}
#endif
// Configure and start the camera
#ifdef USE_CAM
if (not setup_camera()){
return;
}
#endif
Serial.println("Done configuring peripherals.");
Serial.println("\r\nInitialisation done.");
}
#ifdef USE_JPG
float captureDrawJPGDEC(){
long start = millis();
for (int i = 0; i < n; i++){
fb = esp_camera_fb_get();
tft.startWrite();
// display.startWrite();
jpg.openRAM((uint8_t*)fb->buf, fb->len, JPEGDraw);
#ifndef USE_DITHER
jpg.setPixelType(RGB565_BIG_ENDIAN);
jpg.decode(0, 0, 0);
#endif
#ifdef USE_DITHER
jpg.setPixelType(FOUR_BIT_DITHERED);
jpg.decodeDither(ditherSpace, 0);
#endif
tft.endWrite(); // ADAFRUIT_GFX needs this or it will stop after first frame
esp_camera_fb_return(fb);
}
long end = millis();
return (float)n * 1000.0 /( (float)end - (float)start);
}
#endif
void loop(){
#ifdef USE_JPG
#ifdef USE_TFT
float jpgDraw_fps = captureDrawJPGDEC();
Serial.printf("\r\n%.2f FPS", jpgDraw_fps);
#endif
#endif
}
void testdrawtext(char *text, uint16_t color) {
tft.setCursor(0, 0);
tft.setTextColor(color);
tft.setTextWrap(true);
tft.print(text);
}
int JPEGDraw(JPEGDRAW *pDraw)
{
#ifdef USE_TFT_ESPI
tft.dmaWait(); // Wait for prior writePixels() to finish
tft.setAddrWindow(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
tft.pushPixels(pDraw->pPixels, pDraw->iWidth * pDraw->iHeight); // Use DMA, big-endian
#endif
#ifdef USE_ADAFRUIT_GFX
tft.dmaWait(); // Wait for prior writePixels() to finish
tft.setAddrWindow(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
tft.writePixels(pDraw->pPixels, pDraw->iWidth * pDraw->iHeight, true, true); // Use DMA, big-endian
#endif
// #ifdef USE_DITHER
// tft.dmaWait(); // Wait for prior writePixels() to finish
// tft.setAddrWindow(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
// int x = pDraw->x;
// int y = pDraw->y;
// int w = pDraw->iWidth;
// int h = pDraw->iHeight;
// for(int i = 0; i < w * h; i++)
// {
// pDraw->pPixels[i] = (pDraw->pPixels[i] & 0x7e0) >> 5; // extract just the six green channel bits.
// }
// if (dither)
// {
// for(int16_t j = 0; j < h; j++)
// {
// for(int16_t i = 0; i < w; i++)
// {
// int8_t oldPixel = constrain(pDraw->pPixels[i + j * w], 0, 0x3F);
// int8_t newPixel = oldPixel & 0x38; // or 0x30 to dither to 2-bit directly. much improved tonal range, but more horizontal banding between blocks.
// pDraw->pPixels[i + j * w] = newPixel;
// int quantError = oldPixel - newPixel;
// if(i + 1 < w) pDraw->pPixels[i + 1 + j * w] += quantError * 7 / 16;
// if((i - 1 >= 0) && (j + 1 < h)) pDraw->pPixels[i - 1 + (j + 1) * w] += quantError * 3 / 16;
// if(j + 1 < h) pDraw->pPixels[i + (j + 1) * w] += quantError * 5 / 16;
// if((i + 1 < w) && (j + 1 < h)) pDraw->pPixels[i + 1 + (j + 1) * w] += quantError * 1 / 16;
// } // for i
// } // for j
// } // if dither
// for(int16_t i = 0; i < w; i++)
// {
// for(int16_t j = 0; j < h; j++)
// {
// switch (constrain(pDraw->pPixels[i + j * w] >> 4, 0, 3))
// {
// case 0:
// tft.writePixel(x+i, y+j, 0);
// break;
// case 1:
// tft.writePixel(x+i, y+j, 1000);
// break;
// case 2:
// tft.writePixel(x+i, y+j, 50000);
// break;
// case 3:
// tft.writePixel(x+i, y+j, 65535);
// break;
// } // switch
// } // for j
// } // for i
// #endif
// display.drawRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight);
// putPixels(pDraw->pPixels)
return 1;
} /*= JPEGDraw() */
// void putPixels(uint8_t c, int32_t len) {
// static uint8_t color;
// uint8_t b = 0;
// while(len--) {
// b = 128;
// for (int i=0; i<8; i++) {
// if (c & b) {
// color = WHITE;
// } else {
// color = BLACK;
// }
// b >>= 1;
// if (color == BLACK) {
// // we clear the buffer each frame so only black pixels need to be drawn
// display.fillRect(X_OFFSET+curr_x*SCALE, Y_OFFSET+curr_y*SCALE, SCALE, SCALE, color);
// }
// curr_x++;
// if(curr_x >= 128) {
// curr_x = 0;
// curr_y++;
// if(curr_y >= 64) {
// curr_y = 0;
// display.refresh();
// display.clearDisplayBuffer();
// // 30 fps target rate
// //if(digitalRead(0)) while((millis() - lastRefresh) < 33) ;
// //lastRefresh = millis();
// }
// }
// }
// }
// }```
1-bit and 4-bit dithered means that each pixel given to JPEGDraw is 1 or 4 bits (packed 8 or 2 pixels per byte). Your code is treating the dithered output as if it's RGB565.