bitbank2/JPEGDEC

Can't display dithered output on TFT display

Closed this issue · 1 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()?

IMG_9593

//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.