bitbank2 / JPEGDEC

An optimized JPEG decoder for Arduino
Apache License 2.0
366 stars 47 forks source link

Dithering isn't dithering in my code for the M5Stack EPaper. #25

Closed Sarah-C closed 2 years ago

Sarah-C commented 2 years ago

Hi!

I love this utility you wrote. I've crossed it with a photo-frame app for the M5 Paper I've got. https://shop.m5stack.com/products/m5paper-esp32-development-kit-960x540-4-7-eink-display-235-ppi It's 960 x 540 and 16 shades. (4bit)

I'm having a bit of a weird issue - in JPEGDEC\examples\epd_demo\epd_demo.ino, you've done dithering for the 565 pixel format that the jpeg.c code produces.

Looking through the header file, I saw there was a jpeg.setPixelType function, that I've tried passing (FOUR_BIT_DITHERED) to. I see in jpeg.c, that it applies Floyd-Steinberg dithering to the output if the pixel type is of the correct type. I'm not calling it directly, but using jpeg.setPixelType(FOUR_BIT_DITHERED) , and then jpeg.decode(0, 0, 0); but had no success...

If you could spare a little while to have a look at the code for me, could you show me how to get the jpeg.c outputting that lovely FS dithering it's got in it?

Thank you lots!

Line 3101:

// Dither the 8-bit gray pixels into 1, 2, or 4-bit gray
static void JPEGDither(JPEGIMAGE *pJPEG, int iWidth, int iHeight)
/*
  https://github.com/wizche/flip-pics
  https://github.com/bitbank2/JPEGDEC
*/
#include <M5EPD.h>
#include <memory>
#include <stdexcept>
#include <SD.h>

#include <iostream>
#include <vector>
#include <sstream>
#include <string>
#include <JPEGDEC.h>

#define SLEEP_HOURS 1
bool dither = true;

JPEGDEC jpeg;

M5EPD_Canvas canvas(&M5.EPD);

const char *DATA_FILE = "/data.txt";
uint32_t lastCount;

//###############
// Callbacks for the jpeg decoding.

File myfile;

void * myOpen(const char *filename, int32_t *size) {
  myfile = SD.open(filename);
  *size = myfile.size();
  return &myfile;
}
void myClose(void *handle) {
  if (myfile) myfile.close();
}
int32_t myRead(JPEGFILE *handle, uint8_t *buffer, int32_t length) {
  if (!myfile) return 0;
  return myfile.read(buffer, length);
}
int32_t mySeek(JPEGFILE *handle, int32_t position) {
  if (!myfile) return 0;
  return myfile.seek(position);
}

//###############

bool has_suffix(const std::string &str, const std::string &suffix) {
  return str.size() >= suffix.size() &&
         str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}

bool is_jpg(const std::string &filename) {
  return (has_suffix(filename, ".jpg") || has_suffix(filename, ".jpeg"));
}

bool is_valid_image(const std::string &filename) {
  return is_jpg(filename);
}

std::vector<std::string> split(const std::string &s, char delimiter) {
  std::vector<std::string> tokens;
  std::string token;
  std::istringstream tokenStream(s);
  while (std::getline(tokenStream, token, delimiter)) {
    tokens.push_back(token);
  }
  return tokens;
}

void storeCountSD(uint32_t c) {
  auto f = SD.open(DATA_FILE, "wb");
  uint8_t buf[4];
  buf[0] = c;
  buf[1] = c >> 8;
  buf[2] = c >> 16;
  buf[3] = c >> 24;
  auto bytes = f.write(&buf[0], 4);
  f.close();
}

uint32_t getCountSD() {
  uint32_t val;
  if (SD.exists(DATA_FILE)) {
    auto f = SD.open(DATA_FILE, "rb");
    val = f.read();
    f.close();
  }
  else {
    val = 0;
  }
  return val;
}

void drawTempHumidityBattery() {
  char batteryBuffer[20];
  uint32_t vol = M5.getBatteryVoltage();
  if (vol < 3300) {
    vol = 3300;
  }
  else if (vol > 4350) {
    vol = 4350;
  }
  float battery = (float)(vol - 3300) / (float)(4350 - 3300);
  if (battery <= 0.01) {
    battery = 0.01;
  }
  if (battery > 1) {
    battery = 1;
  }
  sprintf(batteryBuffer, "%d%%", (int)(battery * 100));
  char statusBuffer[256] = "CHARGING";
  M5.SHT30.UpdateData();
  float tem = M5.SHT30.GetTemperature();
  float hum = M5.SHT30.GetRelHumidity();
  sprintf(statusBuffer, "%2.2fC | %0.2f%% | %s", tem, hum, batteryBuffer);
  canvas.drawRightString(statusBuffer, 960, 0, 1);
}

int JPEGDraw(JPEGDRAW *pDraw)
{
  int x = pDraw->x;
  int y = pDraw->y;
  int w = pDraw->iWidth;
  int h = pDraw->iHeight;

  /*  Serial.println("---");
    Serial.println(x);
    Serial.println(y);
    Serial.println(w);
    Serial.println(h);*/

uint32_t rgb = 0;

  for (int i = 0; i < 4; i++) {
    Serial.print(" ------ ");
    Serial.print(i);
    Serial.print(" - ");
    rgb = pDraw->pPixels[i];
    Serial.print(rgb & 0xff);
    Serial.print(", ");
    Serial.print((rgb >> 8) & 0xff);
    Serial.print(", ");
    Serial.print((rgb >> 16) & 0xff);
    Serial.print(", ");
    Serial.println((rgb >> 24) & 0xff);
  }

  //M5.EPD.WritePartGram4bpp(x, y, w, h, (uint8_t) pDraw->pPixels);

  for (int16_t i = 0; i < w; i++){
    for (int16_t j = 0; j < h; j++){

    //canvas.drawPixel(x + i, y + j, 255 - ((pDraw->pPixels[i + j * w]) & 0xff));

       // 0 - 255 brightness, amended to 16 shades in the function
       // This currently produces a suitable greyscale that is sadly undithered. Concerningly, I'm inversing the value to be displayed correctly!
      canvas.drawPixel(x + i, y + j, (255 - ((pDraw->pPixels[i + j * w]) & 0xff)) >> 1);

      */
    } // for j
  } // for i
  return 1;
} /* JPEGDraw() */

void load_image() {
  File root = SD.open("/");
  if (!root) {
    Serial.printf("Failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.printf("Not a directory");
    return;
  }

  String lastFile;
  lastCount = getCountSD();

  uint32_t currentCount = 0;
  while (true) {
    File file = root.openNextFile();
    if (!file) {
      Serial.printf("Reached end of directory, restart!\n");
      lastCount = 0;
      currentCount = 0;
      root.rewindDirectory();
      continue;
    }
    if (is_valid_image(file.name())) {
      currentCount++;
    }
    else {
      continue;
    }

    if (currentCount > lastCount) {
      if (is_jpg(file.name())) {
        Serial.printf("Filename %s\n", file.name());
        //canvas.drawJpgFile(SD, file.name(), 0, 0, 960, 540, 0, 0, JPEG_DIV_NONE);
        myfile = file;
        jpeg.open((const char *)file.name(), myOpen, myClose, myRead, mySeek, JPEGDraw);
        jpeg.decode(0, 0, 0);
        jpeg.close();
      }
      else {
        Serial.printf("Something went wrong!\n");
        break;
      }
      lastCount = currentCount;
      //canvas.drawRightString(basename.c_str(), 960, 540, 1);
      canvas.drawRightString(file.name(), 960, 540, 1);
      break;
    }
  }
  storeCountSD(lastCount);
  drawTempHumidityBattery();
  canvas.pushCanvas(0, 0, UPDATE_MODE_GC16);
}

void setup() {
  M5.begin();
  M5.EPD.SetRotation(0);
  M5.EPD.Clear(1);
  M5.RTC.begin();

//  jpeg.setPixelType(FOUR_BIT_DITHERED);  //<< this should do Floyd Steinberg dithering, but isn't.

  canvas.createCanvas(960, 540);
  canvas.setTextSize(2);
  load_image();
}

void loop() {
  int tick = 200;
  while ((--tick) > 0) {
    delay(500);
    M5.update();

    if (M5.BtnL.wasPressed()) {
      Serial.println("Previous image.");
      tick = 0;
      uint32_t lastCount = getCountSD();
      lastCount -= 2;
      if ((lastCount) < 0) lastCount = 0;
      storeCountSD(lastCount);
    }

    if (M5.BtnP.wasPressed()) {
      Serial.println("Turning off.");
      canvas.drawRightString("Off", 960, 540, 1);
      canvas.pushCanvas(0, 0, UPDATE_MODE_GC16);
      M5.shutdown();
    }

    if (M5.BtnR.wasPressed()) {
      Serial.println("Next image.");
      tick = 0;
    }
  }
  load_image();
}
bitbank2 commented 2 years ago

I'm sorry I didn't provide a dither example with the latest code; I'll see if I can add one soon. To get dithered output you need to call decodeDither() instead of decode(). See the Wiki for more details.