From 22e8b6df1914d1f448a31c9474f3679b3afdbdf7 Mon Sep 17 00:00:00 2001 From: Thomas Ballmann Date: Thu, 1 Oct 2020 21:31:17 +0200 Subject: [PATCH] #19 - switch to canvas for print screen functionality - implement jpg encoder for print screen --- lib/app/app.cpp | 51 ++++--- lib/display/display.cpp | 195 ++++++++++++++++++++++++-- lib/display/display.h | 21 ++- lib/face/faceCalendar.cpp | 100 ++++++------- lib/face/faceWeather.cpp | 87 ++++++------ lib/image/imageJPEG.cpp | 10 +- lib/jpec/buf.cpp | 70 ++++++++++ lib/jpec/buf.h | 47 +++++++ lib/jpec/conf.cpp | 157 +++++++++++++++++++++ lib/jpec/conf.h | 65 +++++++++ lib/jpec/enc.cpp | 286 ++++++++++++++++++++++++++++++++++++++ lib/jpec/enc.h | 81 +++++++++++ lib/jpec/huff.cpp | 164 ++++++++++++++++++++++ lib/jpec/huff.h | 54 +++++++ lib/jpec/jpec.h | 89 ++++++++++++ 15 files changed, 1337 insertions(+), 140 deletions(-) create mode 100644 lib/jpec/buf.cpp create mode 100644 lib/jpec/buf.h create mode 100644 lib/jpec/conf.cpp create mode 100644 lib/jpec/conf.h create mode 100644 lib/jpec/enc.cpp create mode 100644 lib/jpec/enc.h create mode 100644 lib/jpec/huff.cpp create mode 100644 lib/jpec/huff.h create mode 100644 lib/jpec/jpec.h diff --git a/lib/app/app.cpp b/lib/app/app.cpp index 2558e7d..b5a1ebb 100644 --- a/lib/app/app.cpp +++ b/lib/app/app.cpp @@ -30,12 +30,6 @@ bool updateDisplayRequired = false; //flag to use from web update to reboot the ESP bool shouldReboot = false; -// bmp -void write16(AsyncResponseStream &f, uint16_t v); -void write32(AsyncResponseStream &f, uint32_t v); -uint8_t filldata2[] = {0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0}; -// bmp - void setupApp() { Serial.println("setup configure"); @@ -108,7 +102,7 @@ void setupApp() // CORS DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); - //DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "content-type")); + //DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "content-type")); server.begin(); @@ -132,7 +126,7 @@ void loopApp() PlaylistResetTimer(); updateDisplayRequired = false; - display.nextPage(); + displayFlush(); } } @@ -232,6 +226,7 @@ void setupSettingsPost() */ void setupCurrentImage() { + /* server.on("/current-image", HTTP_GET, [](AsyncWebServerRequest *request) { uint8_t *bitmap = display.getBuffer(); int16_t w = display.width(); @@ -285,6 +280,32 @@ void setupCurrentImage() request->send(response); }); + */ + + server.on("/current-image2", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t q = 10; + if (request->hasParam("q")) + { + q = request->getParam("q")->value().toInt(); + } + + displayPrintScreenJPG("/tmp2.jpeg", q); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/tmp2.jpeg", "image/jpeg"); + + request->send(response); + }); + + server.on("/current-image3", HTTP_GET, [](AsyncWebServerRequest *request) { + displayPrintScreenBMP("/tmp2.bmp"); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/tmp2.bmp", "image/bmp"); + + request->send(response); + }); + + server.on("/current-image4", HTTP_GET, [](AsyncWebServerRequest *request) { + uint8_t ratio = displayPixelBWRatio(); + request->send(200, "text/plain", "{}"); + }); } /** @@ -514,18 +535,4 @@ void setupOTA() } } }); -} - -void write16(AsyncResponseStream &f, uint16_t v) -{ - f.write(uint8_t(v)); - f.write(uint8_t(v >> 8)); -} - -void write32(AsyncResponseStream &f, uint32_t v) -{ - f.write(uint8_t(v)); - f.write(uint8_t(v >> 8)); - f.write(uint8_t(v >> 16)); - f.write(uint8_t(v >> 24)); } \ No newline at end of file diff --git a/lib/display/display.cpp b/lib/display/display.cpp index 1da8bb9..e6594e9 100644 --- a/lib/display/display.cpp +++ b/lib/display/display.cpp @@ -1,6 +1,8 @@ #include -#include "display.h" #include +#include "display.h" +#include "jpec.h" +#include "esp_task_wdt.h" // mapping suggestion for ESP32, e.g. LOLIN32, see .../variants/.../pins_arduino.h for your board // NOTE: there are variants with different pins for SPI ! CHECK SPI PINS OF YOUR BOARD @@ -11,42 +13,57 @@ // base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code // enable or disable GxEPD2_GFX base class -#define ENABLE_GxEPD2_GFX 0 GxEPD2_BW display(GxEPD2_750(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4)); +GFXcanvas1 *displayCanvas; +File tmpFileCache; +long startMills; + +// bmp +void write16(File &f, uint16_t v); +void write32(File &f, uint32_t v); +uint8_t filldata3[] = {0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0}; +// bmp + void setupDisplay() { Serial.println("setupDisplay"); delay(100); - display.init(115200); + //display.init(115200); TODO + + displayCanvas = new GFXcanvas1(display.width(), display.height()); Serial.println("setup done"); } -void displayOpen() +GFXcanvas1 *displayGetCanvas() { - display.setRotation(0); - display.setFullWindow(); - display.firstPage(); - display.fillScreen(GxEPD_WHITE); + return displayCanvas; } -void displayWritePixel(int16_t x, int16_t y, uint16_t color) +void displayOpen() { - display.drawPixel(x, y, color); + displayCanvas->setRotation(0); + displayCanvas->fillScreen(GxEPD_WHITE); } -void displayWriteFramebuffer(uint8_t bitmap[]) +void displayWritePixel(int16_t x, int16_t y, uint16_t color) { - display.drawInvertedBitmap(0, 0, bitmap, display.epd2.WIDTH, display.epd2.HEIGHT, GxEPD_BLACK); + displayCanvas->drawPixel(x, y, color); } void displayFlush() { - display.nextPage(); + display.setRotation(0); + display.setFullWindow(); + display.firstPage(); + display.fillScreen(GxEPD_WHITE); + + display.drawBitmap(0, 0, displayCanvas->getBuffer(), displayCanvas->width(), displayCanvas->height(), GxEPD_BLACK); } +/* void printSplash() { const char Hello[] = "Hello Paperdash!"; @@ -69,3 +86,155 @@ void printSplash() display.print(Hello); } while (display.nextPage()); } +*/ + +void exportJPG(GFXcanvas1 *_canvas, const char *fileName, uint8_t q) +{ + Serial.println(F("exportJPG")); + long start = millis(); + + SPIFFS.remove(fileName); + tmpFileCache = SPIFFS.open(fileName, FILE_WRITE); + if (!tmpFileCache) + { + Serial.println("Failed to open file for writing"); + return; + } + + /* Create a JPEG encoder provided image data */ + jpec_enc_t *e = jpec_enc_new2(_canvas->getBuffer(), _canvas->width(), _canvas->height(), q, [](int offset, uint8_t val) { + tmpFileCache.write(val); + + // every 1000ms + if (startMills + 1000 < millis()) + { + startMills = millis(); + esp_task_wdt_reset(); + } + }); + + /* Compress */ + jpec_enc_run(e); + Serial.print(millis() - start); + Serial.println("ms"); + + /* Release the encoder */ + jpec_enc_del(e); + tmpFileCache.close(); +} + +void exportBMP(GFXcanvas1 *_canvas, const char *fileName) +{ + Serial.println(F("exportBMP")); + startMills = millis(); + + SPIFFS.remove(fileName); + tmpFileCache = SPIFFS.open(fileName, FILE_WRITE); + if (!tmpFileCache) + { + Serial.println("Failed to open file for writing"); + return; + } + + uint8_t *bitmap = _canvas->getBuffer(); + int16_t w = _canvas->width(); + int16_t h = _canvas->height(); + + uint16_t depth = 1; + uint32_t rowSizeCode = (w + 8 - depth) * depth / 8; + + // BMP rows are padded (if needed) to 4-byte boundary + uint32_t rowSizeBMP = (w * depth / 8 + 3) & ~3; + uint32_t headerSize = 40; + uint32_t imageOffset = 62; + uint32_t fileSize = imageOffset + h * rowSizeBMP; + + write16(tmpFileCache, 0x4D42); // BMP signature + write32(tmpFileCache, fileSize); // fileSize + write32(tmpFileCache, 0); // creator bytes + write32(tmpFileCache, imageOffset); // image offset + write32(tmpFileCache, headerSize); // Header size + write32(tmpFileCache, w); // image width + write32(tmpFileCache, h); // image height + write16(tmpFileCache, 1); // # planes + write16(tmpFileCache, depth); // bits per pixel + write32(tmpFileCache, 0); // format uncompressed + + uint32_t j = 0; + for (uint32_t i = 34; i < imageOffset; i++) + { + tmpFileCache.write(filldata3[j++]); // remaining header bytes + } + + uint32_t rowidx = w * h / 8; + for (uint16_t row = 0; row < h; row++) // for each line + { + rowidx -= rowSizeCode; + + uint32_t colidx; + for (colidx = 0; colidx < rowSizeCode; colidx++) + { + uint8_t data = pgm_read_byte(&bitmap[rowidx + colidx]); + tmpFileCache.write(data); + } + + while (colidx++ < rowSizeBMP) + { + tmpFileCache.write(uint8_t(0)); // padding + } + + esp_task_wdt_reset(); + } + + Serial.print(millis() - startMills); + Serial.println("ms"); + + tmpFileCache.close(); +} + +void write16(File &f, uint16_t v) +{ + f.write(uint8_t(v)); + f.write(uint8_t(v >> 8)); +} + +void write32(File &f, uint32_t v) +{ + f.write(uint8_t(v)); + f.write(uint8_t(v >> 8)); + f.write(uint8_t(v >> 16)); + f.write(uint8_t(v >> 24)); +} + +uint8_t displayPixelBWRatio() +{ + uint8_t *buffer = displayCanvas->getBuffer(); + uint32_t pixelWhite = 0; + for (uint16_t y = 0; y < displayCanvas->height(); y++) + { + for (uint16_t x = 0; x < displayCanvas->width(); x++) + { + + uint8_t *ptr = (uint8_t *)&buffer[(x / 8) + y * ((displayCanvas->width() + 7) / 8)]; + // return ((*ptr) & (0x80 >> (x & 7))) != 0 ? 0xFF : 0; + pixelWhite += ((*ptr) & (0x80 >> (x & 7))) != 0 ? 1 : 0; + } + } + + uint32_t pixelCount = displayCanvas->width() * displayCanvas->height(); + uint8_t pixelRatio = (uint8_t)round(100.0 / pixelCount * pixelWhite); + Serial.println(100.0 / pixelCount * pixelWhite); + Serial.printf("Pixel count: %d, white pixel: %d, ratio: %d\n", pixelCount, pixelWhite, pixelRatio); + + return pixelRatio; +} + +void displayPrintScreenJPG(const char *fileName, uint8_t q) +{ + exportJPG(displayCanvas, fileName, q); +} + +void displayPrintScreenBMP(const char *fileName) +{ + exportBMP(displayCanvas, fileName); +} \ No newline at end of file diff --git a/lib/display/display.h b/lib/display/display.h index 5a62057..22dec49 100644 --- a/lib/display/display.h +++ b/lib/display/display.h @@ -1,24 +1,23 @@ #ifndef DISPLAY_H #define DISPLAY_H -//#include "EPD2_BW.h" +#define ENABLE_GxEPD2_GFX 0 #include -#include - -//#define ENABLE_GxEPD2_GFX 0 - -extern GxEPD2_BW display; -//extern GxEPD2_BW display(GxEPD2_750(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4)); -//GxEPD2_BW displayGet(); +#include void setupDisplay(); +GFXcanvas1 *displayGetCanvas(); void displayOpen(); - void displayWritePixel(int16_t x, int16_t y, uint16_t color); -void displayWriteFramebuffer(uint8_t bitmap[]); - void displayFlush(); +void exportJPG(GFXcanvas1 *_canvas, const char *fileName, uint8_t q); +void exportBMP(GFXcanvas1 *_canvas, const char *fileName); + +uint8_t displayPixelBWRatio(); +void displayPrintScreenJPG(const char *fileName, uint8_t q); +void displayPrintScreenBMP(const char *fileName); + #endif \ No newline at end of file diff --git a/lib/face/faceCalendar.cpp b/lib/face/faceCalendar.cpp index 27534f6..4530d7a 100644 --- a/lib/face/faceCalendar.cpp +++ b/lib/face/faceCalendar.cpp @@ -10,6 +10,7 @@ #include "faceWeatherIcons.h" #include // weekday - month year +#include // #include // current day #include // current day @@ -48,10 +49,10 @@ void playlistFaceCalendar() void showFaceCalendar() { - display.setRotation(0); - display.setFullWindow(); - display.firstPage(); - display.fillScreen(GxEPD_WHITE); + GFXcanvas1 *canvas = displayGetCanvas(); + + canvas->setRotation(0); + canvas->fillScreen(GxEPD_WHITE); display_picture(); display_calender(); @@ -70,83 +71,84 @@ bool updateCalendarData() void display_calender() { // init + GFXcanvas1 *canvas = displayGetCanvas(); int16_t sideWidth = 250; int16_t tbx, tby; uint16_t tbw, tbh, x; char label[64]; - display.setTextColor(GxEPD_WHITE); - display.setTextSize(1); - display.setRotation(0); + canvas->setTextColor(GxEPD_WHITE); + canvas->setTextSize(1); + canvas->setRotation(0); // left side - display.fillRect(0, 0, sideWidth, display.height(), GxEPD_BLACK); + canvas->fillRect(0, 0, sideWidth, canvas->height(), GxEPD_BLACK); // weekday strftime(label, 64, "%A", &now); - display.setFont(&FreeMono12pt7b); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->setFont(&FreeMono12pt7b); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((sideWidth - tbw) / 2) - tbx; - display.setCursor(x, 30); - display.println(label); + canvas->setCursor(x, 30); + canvas->println(label); // today - display.setFont(&FreeSansBold24pt7b); - display.setTextSize(2); - display.getTextBounds("29", 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->setFont(&FreeSansBold24pt7b); + canvas->setTextSize(2); + canvas->getTextBounds("29", 0, 0, &tbx, &tby, &tbw, &tbh); x = ((sideWidth - tbw) / 2) - tbx; - display.setCursor(x, 120); - display.println(now.tm_mday); + canvas->setCursor(x, 120); + canvas->println(now.tm_mday); // month yearh strftime(label, 64, "%B %Y", &now); - display.setTextSize(1); - display.setFont(&FreeMono12pt7b); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->setTextSize(1); + canvas->setFont(&FreeMono12pt7b); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((sideWidth - tbw) / 2) - tbx; - display.setCursor(x, 150); - display.println(label); + canvas->setCursor(x, 150); + canvas->println(label); // weekday headline - display.setFont(&FreeMonoBold9pt7b); - display.setCursor(20, 192); - display.println("Mo Tu We Th Fr Sa Su"); + canvas->setFont(&FreeMonoBold9pt7b); + canvas->setCursor(20, 192); + canvas->println("Mo Tu We Th Fr Sa Su"); - display.setCursor(20, 220); + canvas->setCursor(20, 220); // skip week days from previous month uint8_t skip = (now.day_offset == 0 ? 7 : now.day_offset); for (uint8_t d = 1; d < skip; d++) { - display.print(" "); + canvas->print(" "); } for (uint8_t d = 1; d <= now.days_in_month; d++) { - display.printf("%2d ", d); + canvas->printf("%2d ", d); if ((d + now.day_offset - 1) % 7 == 0) { // new week - display.println(""); - display.setCursor(20, display.getCursorY()); + canvas->println(""); + canvas->setCursor(20, canvas->getCursorY()); } } // current weather - display.drawLine(15, 320, sideWidth - 15, 320, GxEPD_WHITE); + canvas->drawLine(15, 320, sideWidth - 15, 320, GxEPD_WHITE); // icon const unsigned char *icon = getIconById(weatherData.current_icon, 64); if (icon) { - display.drawInvertedBitmap(72, 325, icon, 64, 64, GxEPD_WHITE); + canvas->drawBitmap(72, 325, icon, 64, 64, GxEPD_WHITE); } // temperature - display.setFont(&FreeSans24pt7b); - display.setTextSize(1); - display.setCursor(150, 367); - display.println(weatherData.current_temp); + canvas->setFont(&FreeSans24pt7b); + canvas->setTextSize(1); + canvas->setCursor(150, 367); + canvas->println(weatherData.current_temp); } void display_picture() @@ -177,34 +179,34 @@ void display_time() uint16_t w, h; //display time - display.setFont(&FreeMonoBold9pt7b); // LARGE_FONT - display.setTextSize(1); - display.setTextColor(GxEPD_BLACK); + canvas->setFont(&FreeMonoBold9pt7b); // LARGE_FONT + canvas->setTextSize(1); + canvas->setTextColor(GxEPD_BLACK); int16_t time_base_y = 60; int16_t time_base_x = 25; - display.getTextBounds("03", time_base_x, time_base_y, &x1, &y1, &w, &h); // 03 is arbitrary text to get the height and width - display.fillRect(time_base_x - 10, time_base_y - h - 10, w + 15, time_base_y + h + 10, GxEPD_WHITE); + canvas->getTextBounds("03", time_base_x, time_base_y, &x1, &y1, &w, &h); // 03 is arbitrary text to get the height and width + canvas->fillRect(time_base_x - 10, time_base_y - h - 10, w + 15, time_base_y + h + 10, GxEPD_WHITE); - display.setCursor(time_base_x, time_base_y); + canvas->setCursor(time_base_x, time_base_y); if (now.hour < 10) { - display.print("0"); - display.print(now.hour); + canvas->print("0"); + canvas->print(now.hour); } else { - display.println(now.hour); + canvas->println(now.hour); } - display.setCursor(time_base_x, time_base_y + h + 10); + canvas->setCursor(time_base_x, time_base_y + h + 10); if (now.min < 10) { - display.print("0"); - display.print(now.min); + canvas->print("0"); + canvas->print(now.min); } else { - display.println(now.min); + canvas->println(now.min); } */ } diff --git a/lib/face/faceWeather.cpp b/lib/face/faceWeather.cpp index 04f169a..eac3d8c 100644 --- a/lib/face/faceWeather.cpp +++ b/lib/face/faceWeather.cpp @@ -39,12 +39,12 @@ void loopFaceWeather() void playlistFaceWeather() { - display.setRotation(0); - display.setFullWindow(); - display.firstPage(); - display.fillScreen(GxEPD_BLACK); - display.setTextColor(GxEPD_WHITE); - display.setTextSize(1); + GFXcanvas1 *canvas = displayGetCanvas(); + + canvas->setRotation(0); + canvas->fillScreen(GxEPD_BLACK); + canvas->setTextColor(GxEPD_WHITE); + canvas->setTextSize(1); render_current(); render_forecast(); @@ -54,50 +54,53 @@ void playlistFaceWeather() void render_current() { + GFXcanvas1 *canvas = displayGetCanvas(); + // name - display.setFont(&FreeSansBold18pt7b); - display.setTextSize(1); - display.setCursor(20, 220); - display.println(weatherData.location); + canvas->setFont(&FreeSansBold18pt7b); + canvas->setTextSize(1); + canvas->setCursor(20, 220); + canvas->println(weatherData.location); // temperature - display.setFont(&FreeSansBold24pt7b); - display.setTextSize(2); - display.setCursor(50, 120); - display.println(weatherData.current_temp); + canvas->setFont(&FreeSansBold24pt7b); + canvas->setTextSize(2); + canvas->setCursor(50, 120); + canvas->println(weatherData.current_temp); // icon const unsigned char *icon = getIconById(weatherData.current_icon, 256); if (icon) { - display.drawInvertedBitmap(192, 0, icon, 256, 256, GxEPD_WHITE); + canvas->drawBitmap(192, 0, icon, 256, 256, GxEPD_WHITE); } // 250 height // high - display.setTextSize(1); - display.setCursor(500, 100); - display.println(weatherData.current_max); + canvas->setTextSize(1); + canvas->setCursor(500, 100); + canvas->println(weatherData.current_max); // low - display.setCursor(500, 180); - display.println(weatherData.current_min); + canvas->setCursor(500, 180); + canvas->println(weatherData.current_min); } void render_forecast() { + GFXcanvas1 *canvas = displayGetCanvas(); const unsigned char *icon; // line forecast - display.drawRect(0, 250, 640, 2, GxEPD_WHITE); + canvas->drawRect(0, 250, 640, 2, GxEPD_WHITE); - display.drawLine(160, 250, 160, 384, GxEPD_WHITE); - display.drawLine(320, 250, 320, 384, GxEPD_WHITE); - display.drawLine(480, 250, 480, 384, GxEPD_WHITE); + canvas->drawLine(160, 250, 160, 384, GxEPD_WHITE); + canvas->drawLine(320, 250, 320, 384, GxEPD_WHITE); + canvas->drawLine(480, 250, 480, 384, GxEPD_WHITE); // 160 per block - display.setTextSize(1); - display.setFont(&FreeSansBold18pt7b); + canvas->setTextSize(1); + canvas->setFont(&FreeSansBold18pt7b); char label[20]; int16_t tbx, tby; @@ -108,52 +111,52 @@ void render_forecast() icon = getIconById(weatherData.forecast_1_icon, 64); if (icon) { - display.drawInvertedBitmap(0 + 48, 260, icon, 64, 64, GxEPD_WHITE); + canvas->drawBitmap(0 + 48, 260, icon, 64, 64, GxEPD_WHITE); sprintf(label, "%2d ... %2d", weatherData.forecast_1_min, weatherData.forecast_1_max); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((160 - tbw) / 2) - tbx; - display.setCursor(x, tempRangeY); - display.print(label); + canvas->setCursor(x, tempRangeY); + canvas->print(label); } // day +2 icon = getIconById(weatherData.forecast_2_icon, 64); if (icon) { - display.drawInvertedBitmap(160 + 48, 260, icon, 64, 64, GxEPD_WHITE); + canvas->drawBitmap(160 + 48, 260, icon, 64, 64, GxEPD_WHITE); sprintf(label, "%2d ... %2d", weatherData.forecast_2_min, weatherData.forecast_2_max); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((160 - tbw) / 2) - tbx; - display.setCursor(160 + x, tempRangeY); - display.print(label); + canvas->setCursor(160 + x, tempRangeY); + canvas->print(label); } // day +3 icon = getIconById(weatherData.forecast_3_icon, 64); if (icon) { - display.drawInvertedBitmap(320 + 48, 260, icon, 64, 64, GxEPD_WHITE); + canvas->drawBitmap(320 + 48, 260, icon, 64, 64, GxEPD_WHITE); sprintf(label, "%2d ... %2d", weatherData.forecast_3_min, weatherData.forecast_3_max); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((160 - tbw) / 2) - tbx; - display.setCursor(320 + x, tempRangeY); - display.print(label); + canvas->setCursor(320 + x, tempRangeY); + canvas->print(label); } // day +4 icon = getIconById(weatherData.forecast_4_icon, 64); if (icon) { - display.drawInvertedBitmap(480 + 48, 260, icon, 64, 64, GxEPD_WHITE); + canvas->drawBitmap(480 + 48, 260, icon, 64, 64, GxEPD_WHITE); sprintf(label, "%2d ... %2d", weatherData.forecast_4_min, weatherData.forecast_4_max); - display.getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); + canvas->getTextBounds(label, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((160 - tbw) / 2) - tbx; - display.setCursor(480 + x, tempRangeY); - display.print(label); + canvas->setCursor(480 + x, tempRangeY); + canvas->print(label); } } diff --git a/lib/image/imageJPEG.cpp b/lib/image/imageJPEG.cpp index 9eca092..d654923 100644 --- a/lib/image/imageJPEG.cpp +++ b/lib/image/imageJPEG.cpp @@ -68,6 +68,8 @@ void jpegInfo() void renderJPEG(int xpos, int ypos) { + GFXcanvas1 *canvas = displayGetCanvas(); + // retrieve infomration about the image uint16_t *pImg; uint16_t mcu_w = JpegDec.MCUWidth; @@ -115,14 +117,14 @@ void renderJPEG(int xpos, int ypos) win_h = min_h; // draw image block if it will fit on the screen - if ((mcu_x + win_w) <= display.width() && (mcu_y + win_h) <= display.height()) + if ((mcu_x + win_w) <= canvas->width() && (mcu_y + win_h) <= canvas->height()) { renderMcuBlock(mcu_x, mcu_y, win_w, win_h, pImg); } // stop drawing blocks if the bottom of the screen has been reached // the abort function will close the file - else if ((mcu_y + win_h) >= display.height()) + else if ((mcu_y + win_h) >= canvas->height()) { JpegDec.abort(); } @@ -206,8 +208,10 @@ void renderMcuBlockPixel(uint32_t x, uint32_t y, uint32_t color) void renderMcuBlock(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) { + GFXcanvas1 *canvas = displayGetCanvas(); + // Stop further decoding as image is running off bottom of screen - if (y >= display.height()) + if (y >= canvas->height()) { Serial.println("y is out of display range!"); return; diff --git a/lib/jpec/buf.cpp b/lib/jpec/buf.cpp new file mode 100644 index 0000000..3541741 --- /dev/null +++ b/lib/jpec/buf.cpp @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#include "buf.h" +#include "conf.h" + +#define JPEC_BUFFER_INIT_SIZ 65536 + +jpec_buffer_t *jpec_buffer_new(void) { + // return jpec_buffer_new2(-1); +} + +jpec_buffer_t *jpec_buffer_new2(int siz, jpec_enc_callback_t callback) { + if (siz < 0) siz = 0; + jpec_buffer_t *b = (jpec_buffer_t*)malloc(sizeof(*b)); + // b->stream = NULL; // siz > 0 ? (uint8_t*) malloc(siz) : NULL; + b->siz = siz; + b->len = 0; + + b->callback = callback; + + return b; +} + +void jpec_buffer_del(jpec_buffer_t *b) { + assert(b); + // if (b->stream) free(b->stream); + free(b); +} + +void jpec_buffer_write_byte(jpec_buffer_t *b, int val) { + assert(b); + if (b->siz == b->len) { + int nsiz = (b->siz > 0) ? 2 * b->siz : JPEC_BUFFER_INIT_SIZ; + // void* tmp = realloc(b->stream, nsiz); + // b->stream = (uint8_t *) tmp; + b->siz = nsiz; + } + // b->stream[b->len++] = (uint8_t) val; + b->len++; + + // callback + b->callback(b->len, (uint8_t) val); +} + +void jpec_buffer_write_2bytes(jpec_buffer_t *b, int val) { + assert(b); + jpec_buffer_write_byte(b, (val >> 8) & 0xFF); + jpec_buffer_write_byte(b, val & 0xFF); +} diff --git a/lib/jpec/buf.h b/lib/jpec/buf.h new file mode 100644 index 0000000..b8bd37b --- /dev/null +++ b/lib/jpec/buf.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#ifndef JPEC_BUFFER_H +#define JPEC_BUFFER_H + +#include + +// Callback signatures +typedef void (*jpec_enc_callback_t)(int offset, uint8_t val); + +/** Extensible byte buffer */ +typedef struct jpec_buffer_t_ { + jpec_enc_callback_t callback; + uint8_t *stream; /* byte buffer */ + int len; /* current length */ + int siz; /* maximum size */ +} jpec_buffer_t; + +jpec_buffer_t *jpec_buffer_new(void); +jpec_buffer_t *jpec_buffer_new2(int siz, jpec_enc_callback_t callback); +// jpec_buffer_t *jpec_buffer_new2(int siz); +void jpec_buffer_del(jpec_buffer_t *b); +void jpec_buffer_write_byte(jpec_buffer_t *b, int val); +void jpec_buffer_write_2bytes(jpec_buffer_t *b, int val); + +#endif diff --git a/lib/jpec/conf.cpp b/lib/jpec/conf.cpp new file mode 100644 index 0000000..695579a --- /dev/null +++ b/lib/jpec/conf.cpp @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#include "conf.h" + +const uint8_t jpec_qzr[64] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 +}; + +const float jpec_dct[7] = { + 0.49039, 0.46194, 0.41573, 0.35355, + 0.27779, 0.19134, 0.09755 +}; + +const int jpec_zz[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +const uint8_t jpec_dc_nodes[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; +const int jpec_dc_nb_vals = 12; /* sum of dc_nodes */ +const uint8_t jpec_dc_vals[12] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + +const uint8_t jpec_ac_nodes[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; +const int jpec_ac_nb_vals = 162; /* sum of ac_nodes */ +const uint8_t jpec_ac_vals[162] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, /* 0x00: EOB */ + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, /* 0xf0: ZRL */ + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa +}; + +const uint8_t jpec_dc_len[12] = { 2,3,3,3,3,3,4,5,6,7,8,9 }; +const int jpec_dc_code[12] = { + 0x000,0x002,0x003,0x004,0x005,0x006, + 0x00e,0x01e,0x03e,0x07e,0x0fe,0x1fe +}; + +const int8_t jpec_ac_len[256] = { + 4, 2, 2, 3, 4, 5, 7, 8, + 10,16,16, 0, 0, 0, 0, 0, + 0, 4, 5, 7, 9,11,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 5, 8,10,12,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 6, 9,12,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 6,10,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 7,11,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 7,12,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 8,12,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 9,15,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 9,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0, 9,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0,10,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0,10,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0,11,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 0,16,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0, + 11,16,16,16,16,16,16,16, + 16,16,16, 0, 0, 0, 0, 0 +}; + +const int jpec_ac_code[256] = { + 0x000a,0x0000,0x0001,0x0004,0x000b,0x001a,0x0078,0x00f8, + 0x03f6,0xff82,0xff83,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x000c,0x001b,0x0079,0x01f6,0x07f6,0xff84,0xff85, + 0xff86,0xff87,0xff88,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x001c,0x00f9,0x03f7,0x0ff4,0xff89,0xff8a,0xff8b, + 0xff8c,0xff8d,0xff8e,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x003a,0x01f7,0x0ff5,0xff8f,0xff90,0xff91,0xff92, + 0xff93,0xff94,0xff95,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x003b,0x03f8,0xff96,0xff97,0xff98,0xff99,0xff9a, + 0xff9b,0xff9c,0xff9d,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x007a,0x07f7,0xff9e,0xff9f,0xffa0,0xffa1,0xffa2, + 0xffa3,0xffa4,0xffa5,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x007b,0x0ff6,0xffa6,0xffa7,0xffa8,0xffa9,0xffaa, + 0xffab,0xffac,0xffad,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x00fa,0x0ff7,0xffae,0xffaf,0xffb0,0xffb1,0xffb2, + 0xffb3,0xffb4,0xffb5,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x01f8,0x7fc0,0xffb6,0xffb7,0xffb8,0xffb9,0xffba, + 0xffbb,0xffbc,0xffbd,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x01f9,0xffbe,0xffbf,0xffc0,0xffc1,0xffc2,0xffc3, + 0xffc4,0xffc5,0xffc6,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x01fa,0xffc7,0xffc8,0xffc9,0xffca,0xffcb,0xffcc, + 0xffcd,0xffce,0xffcf,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x03f9,0xffd0,0xffd1,0xffd2,0xffd3,0xffd4,0xffd5, + 0xffd6,0xffd7,0xffd8,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x03fa,0xffd9,0xffda,0xffdb,0xffdc,0xffdd,0xffde, + 0xffdf,0xffe0,0xffe1,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x07f8,0xffe2,0xffe3,0xffe4,0xffe5,0xffe6,0xffe7, + 0xffe8,0xffe9,0xffea,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0xffeb,0xffec,0xffed,0xffee,0xffef,0xfff0,0xfff1, + 0xfff2,0xfff3,0xfff4,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x07f9,0xfff5,0xfff6,0xfff7,0xfff8,0xfff9,0xfffa,0xfffb, + 0xfffc,0xfffd,0xfffe,0x0000,0x0000,0x0000,0x0000,0x0000 +}; diff --git a/lib/jpec/conf.h b/lib/jpec/conf.h new file mode 100644 index 0000000..2d1cb32 --- /dev/null +++ b/lib/jpec/conf.h @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#ifndef JPEC_CONF_H +#define JPEC_CONF_H + +/* Common headers */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Standard JPEG quantizing table */ +extern const uint8_t jpec_qzr[64]; + +/** DCT coefficients */ +extern const float jpec_dct[7]; + +/** Zig-zag order */ +extern const int jpec_zz[64]; + +/** JPEG standard Huffman tables */ +/** Luminance (Y) - DC */ +extern const uint8_t jpec_dc_nodes[17]; +extern const int jpec_dc_nb_vals; +extern const uint8_t jpec_dc_vals[12]; +/** Luminance (Y) - AC */ +extern const uint8_t jpec_ac_nodes[17]; +extern const int jpec_ac_nb_vals; +extern const uint8_t jpec_ac_vals[162]; + +/** Huffman inverted tables */ +/** Luminance (Y) - DC */ +extern const uint8_t jpec_dc_len[12]; +extern const int jpec_dc_code[12]; +/** Luminance (Y) - AC */ +extern const int8_t jpec_ac_len[256]; +extern const int jpec_ac_code[256]; + +#endif diff --git a/lib/jpec/enc.cpp b/lib/jpec/enc.cpp new file mode 100644 index 0000000..5fb3498 --- /dev/null +++ b/lib/jpec/enc.cpp @@ -0,0 +1,286 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#include "enc.h" +#include "huff.h" +#include "conf.h" +#include + +#define JPEG_ENC_DEF_QUAL 93 /* default quality factor */ +#define JPEC_ENC_HEAD_SIZ 330 /* header typical size in bytes */ +#define JPEC_ENC_BLOCK_SIZ 30 /* 8x8 entropy coded block typical size in bytes */ + + +/* Private function prototypes */ +static void jpec_enc_init_dqt(jpec_enc_t *e); +static void jpec_enc_open(jpec_enc_t *e); +static void jpec_enc_close(jpec_enc_t *e); +static void jpec_enc_write_soi(jpec_enc_t *e); +static void jpec_enc_write_app0(jpec_enc_t *e); +static void jpec_enc_write_dqt(jpec_enc_t *e); +static void jpec_enc_write_sof0(jpec_enc_t *e); +static void jpec_enc_write_dht(jpec_enc_t *e); +static void jpec_enc_write_sos(jpec_enc_t *e); +static int jpec_enc_next_block(jpec_enc_t *e); +static void jpec_enc_block_dct(jpec_enc_t *e); +static void jpec_enc_block_quant(jpec_enc_t *e); +static void jpec_enc_block_zz(jpec_enc_t *e); + +jpec_enc_t *jpec_enc_new(const uint8_t *img, uint16_t w, uint16_t h) { + // return jpec_enc_new2(img, w, h, JPEG_ENC_DEF_QUAL); +} + +jpec_enc_t *jpec_enc_new2(const uint8_t *img, uint16_t w, uint16_t h, int q, jpec_enc_callback_t callback) { + //assert(img && w > 0 && !(w & 0x7) && h > 0 && !(h & 0x7)); + jpec_enc_t *e = (jpec_enc_t *)malloc(sizeof(*e)); + e->img = img; + e->w = w; + e->w8 = (((w-1)>>3)+1)<<3; + e->h = h; + e->qual = q; + e->bmax = (((w-1)>>3)+1) * (((h-1)>>3)+1); + e->bnum = -1; + e->bx = -1; + e->by = -1; + int bsiz = JPEC_ENC_HEAD_SIZ + e->bmax * JPEC_ENC_BLOCK_SIZ; + e->buf = jpec_buffer_new2(bsiz, callback); + e->hskel = (jpec_huff_skel_t*)malloc(sizeof(*e->hskel)); + return e; +} + + +void jpec_enc_del(jpec_enc_t *e) { + assert(e); + if (e->buf) jpec_buffer_del(e->buf); + free(e->hskel); + free(e); +} + +const uint8_t *jpec_enc_run(jpec_enc_t *e) { + assert(e); + jpec_enc_open(e); + while (jpec_enc_next_block(e)) { + jpec_enc_block_dct(e); + jpec_enc_block_quant(e); + jpec_enc_block_zz(e); + e->hskel->encode_block(e->hskel->opq, &e->block, e->buf); + } + jpec_enc_close(e); + return e->buf->stream; +} + +/* Update the internal quantization matrix according to the asked quality */ +static void jpec_enc_init_dqt(jpec_enc_t *e) { + assert(e); + float qualf = (float) e->qual; + float scale = (e->qual < 50) ? (50/qualf) : (2 - qualf/50); + for (int i = 0; i < 64; i++) { + int a = (int) ((float) jpec_qzr[i]*scale + 0.5); + a = (a < 1) ? 1 : ((a > 255) ? 255 : a); + e->dqt[i] = a; + } +} + +static void jpec_enc_open(jpec_enc_t *e) { + assert(e); + jpec_huff_skel_init(e->hskel); + jpec_enc_init_dqt(e); + jpec_enc_write_soi(e); + jpec_enc_write_app0(e); + jpec_enc_write_dqt(e); + jpec_enc_write_sof0(e); + jpec_enc_write_dht(e); + jpec_enc_write_sos(e); +} + +static void jpec_enc_close(jpec_enc_t *e) { + assert(e); + e->hskel->del(e->hskel->opq); + jpec_buffer_write_2bytes(e->buf, 0xFFD9); /* EOI marker */ +} + +static void jpec_enc_write_soi(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFD8); /* SOI marker */ +} + +static void jpec_enc_write_app0(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFE0); /* APP0 marker */ + jpec_buffer_write_2bytes(e->buf, 0x0010); /* segment length */ + jpec_buffer_write_byte(e->buf, 0x4A); /* 'J' */ + jpec_buffer_write_byte(e->buf, 0x46); /* 'F' */ + jpec_buffer_write_byte(e->buf, 0x49); /* 'I' */ + jpec_buffer_write_byte(e->buf, 0x46); /* 'F' */ + jpec_buffer_write_byte(e->buf, 0x00); /* '\0' */ + jpec_buffer_write_2bytes(e->buf, 0x0101); /* v1.1 */ + jpec_buffer_write_byte(e->buf, 0x00); /* no density unit */ + jpec_buffer_write_2bytes(e->buf, 0x0001); /* X density = 1 */ + jpec_buffer_write_2bytes(e->buf, 0x0001); /* Y density = 1 */ + jpec_buffer_write_byte(e->buf, 0x00); /* thumbnail width = 0 */ + jpec_buffer_write_byte(e->buf, 0x00); /* thumbnail height = 0 */ +} + +static void jpec_enc_write_dqt(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFDB); /* DQT marker */ + jpec_buffer_write_2bytes(e->buf, 0x0043); /* segment length */ + jpec_buffer_write_byte(e->buf, 0x00); /* table 0, 8-bit precision (0) */ + for (int i = 0; i < 64; i++) { + jpec_buffer_write_byte(e->buf, e->dqt[jpec_zz[i]]); + } +} + +static void jpec_enc_write_sof0(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFC0); /* SOF0 marker */ + jpec_buffer_write_2bytes(e->buf, 0x000B); /* segment length */ + jpec_buffer_write_byte(e->buf, 0x08); /* 8-bit precision */ + jpec_buffer_write_2bytes(e->buf, e->h); + jpec_buffer_write_2bytes(e->buf, e->w); + jpec_buffer_write_byte(e->buf, 0x01); /* 1 component only (grayscale) */ + jpec_buffer_write_byte(e->buf, 0x01); /* component ID = 1 */ + jpec_buffer_write_byte(e->buf, 0x11); /* no subsampling */ + jpec_buffer_write_byte(e->buf, 0x00); /* quantization table 0 */ +} + +static void jpec_enc_write_dht(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFC4); /* DHT marker */ + jpec_buffer_write_2bytes(e->buf, 19 + jpec_dc_nb_vals); /* segment length */ + jpec_buffer_write_byte(e->buf, 0x00); /* table 0 (DC), type 0 (0 = Y, 1 = UV) */ + for (int i = 0; i < 16; i++) { + jpec_buffer_write_byte(e->buf, jpec_dc_nodes[i+1]); + } + for (int i = 0; i < jpec_dc_nb_vals; i++) { + jpec_buffer_write_byte(e->buf, jpec_dc_vals[i]); + } + jpec_buffer_write_2bytes(e->buf, 0xFFC4); /* DHT marker */ + jpec_buffer_write_2bytes(e->buf, 19 + jpec_ac_nb_vals); + jpec_buffer_write_byte(e->buf, 0x10); /* table 1 (AC), type 0 (0 = Y, 1 = UV) */ + for (int i = 0; i < 16; i++) { + jpec_buffer_write_byte(e->buf, jpec_ac_nodes[i+1]); + } + for (int i = 0; i < jpec_ac_nb_vals; i++) { + jpec_buffer_write_byte(e->buf, jpec_ac_vals[i]); + } +} + +static void jpec_enc_write_sos(jpec_enc_t *e) { + assert(e); + jpec_buffer_write_2bytes(e->buf, 0xFFDA); /* SOS marker */ + jpec_buffer_write_2bytes(e->buf, 8); /* segment length */ + jpec_buffer_write_byte(e->buf, 0x01); /* nb. components */ + jpec_buffer_write_byte(e->buf, 0x01); /* Y component ID */ + jpec_buffer_write_byte(e->buf, 0x00); /* Y HT = 0 */ + /* segment end */ + jpec_buffer_write_byte(e->buf, 0x00); + jpec_buffer_write_byte(e->buf, 0x3F); + jpec_buffer_write_byte(e->buf, 0x00); +} + +static int jpec_enc_next_block(jpec_enc_t *e) { + assert(e); + int rv = (++e->bnum >= e->bmax) ? 0 : 1; + if (rv) { + e->bx = (e->bnum << 3) % e->w8; + e->by = ( (e->bnum << 3) / e->w8 ) << 3; + } + return rv; +} + +static uint8_t getPixel(jpec_enc_t *e, int16_t x, int16_t y) { + // uint8_t *ptr = &buffer[(x / 8) + y * ((WIDTH + 7) / 8)]; + + if ((x < 0) || (y < 0) || (x >= e->w) || (y >= e->h)) + return 0; + + uint8_t *ptr = (uint8_t *)&e->img[(x / 8) + y * ((e->w + 7) / 8)]; + return ((*ptr) & (0x80 >> (x & 7))) != 0 ? 0xFF : 0; +} + +static void jpec_enc_block_dct(jpec_enc_t *e) { + assert(e && e->bnum >= 0); + +#define JPEC_BLOCK(col,row) getPixel(e, e->bx + col, e->by + row) + + const float* coeff = jpec_dct; + float tmp[64]; + for (int row = 0; row < 8; row++) { + /* NOTE: the shift by 256 allows resampling from [0 255] to [–128 127] */ + float s0 = (float) (JPEC_BLOCK(0, row) + JPEC_BLOCK(7, row) - 256); + float s1 = (float) (JPEC_BLOCK(1, row) + JPEC_BLOCK(6, row) - 256); + float s2 = (float) (JPEC_BLOCK(2, row) + JPEC_BLOCK(5, row) - 256); + float s3 = (float) (JPEC_BLOCK(3, row) + JPEC_BLOCK(4, row) - 256); + + float d0 = (float) (JPEC_BLOCK(0, row) - JPEC_BLOCK(7, row)); + float d1 = (float) (JPEC_BLOCK(1, row) - JPEC_BLOCK(6, row)); + float d2 = (float) (JPEC_BLOCK(2, row) - JPEC_BLOCK(5, row)); + float d3 = (float) (JPEC_BLOCK(3, row) - JPEC_BLOCK(4, row)); + + tmp[8 * row] = coeff[3]*(s0+s1+s2+s3); + tmp[8 * row + 1] = coeff[0]*d0+coeff[2]*d1+coeff[4]*d2+coeff[6]*d3; + tmp[8 * row + 2] = coeff[1]*(s0-s3)+coeff[5]*(s1-s2); + tmp[8 * row + 3] = coeff[2]*d0-coeff[6]*d1-coeff[0]*d2-coeff[4]*d3; + tmp[8 * row + 4] = coeff[3]*(s0-s1-s2+s3); + tmp[8 * row + 5] = coeff[4]*d0-coeff[0]*d1+coeff[6]*d2+coeff[2]*d3; + tmp[8 * row + 6] = coeff[5]*(s0-s3)+coeff[1]*(s2-s1); + tmp[8 * row + 7] = coeff[6]*d0-coeff[4]*d1+coeff[2]*d2-coeff[0]*d3; + } + for (int col = 0; col < 8; col++) { + float s0 = tmp[ col] + tmp[56 + col]; + float s1 = tmp[ 8 + col] + tmp[48 + col]; + float s2 = tmp[16 + col] + tmp[40 + col]; + float s3 = tmp[24 + col] + tmp[32 + col]; + + float d0 = tmp[ col] - tmp[56 + col]; + float d1 = tmp[ 8 + col] - tmp[48 + col]; + float d2 = tmp[16 + col] - tmp[40 + col]; + float d3 = tmp[24 + col] - tmp[32 + col]; + + e->block.dct[ col] = coeff[3]*(s0+s1+s2+s3); + e->block.dct[ 8 + col] = coeff[0]*d0+coeff[2]*d1+coeff[4]*d2+coeff[6]*d3; + e->block.dct[16 + col] = coeff[1]*(s0-s3)+coeff[5]*(s1-s2); + e->block.dct[24 + col] = coeff[2]*d0-coeff[6]*d1-coeff[0]*d2-coeff[4]*d3; + e->block.dct[32 + col] = coeff[3]*(s0-s1-s2+s3); + e->block.dct[40 + col] = coeff[4]*d0-coeff[0]*d1+coeff[6]*d2+coeff[2]*d3; + e->block.dct[48 + col] = coeff[5]*(s0-s3)+coeff[1]*(s2-s1); + e->block.dct[56 + col] = coeff[6]*d0-coeff[4]*d1+coeff[2]*d2-coeff[0]*d3; + } +#undef JPEC_BLOCK +} + +static void jpec_enc_block_quant(jpec_enc_t *e) { + assert(e && e->bnum >= 0); + for (int i = 0; i < 64; i++) { + e->block.quant[i] = (int) (e->block.dct[i]/e->dqt[i]); + } +} + +static void jpec_enc_block_zz(jpec_enc_t *e) { + assert(e && e->bnum >= 0); + e->block.len = 0; + for (int i = 0; i < 64; i++) { + if ((e->block.zz[i] = e->block.quant[jpec_zz[i]])) e->block.len = i + 1; + } +} diff --git a/lib/jpec/enc.h b/lib/jpec/enc.h new file mode 100644 index 0000000..9624c0f --- /dev/null +++ b/lib/jpec/enc.h @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#ifndef JPEC_STREAM_H +#define JPEC_STREAM_H + +#include + +#include "buf.h" + +// header----- +typedef struct jpec_enc_t_ jpec_enc_t; + +jpec_enc_t *jpec_enc_new(const uint8_t *img, uint16_t w, uint16_t h); +jpec_enc_t *jpec_enc_new2(const uint8_t *img, uint16_t w, uint16_t h, int q, jpec_enc_callback_t callback); + +void jpec_enc_del(jpec_enc_t *e); +const uint8_t *jpec_enc_run(jpec_enc_t *e, int *len); +// header----- + + + + +/** Structure used to hold and process an image 8x8 block */ +typedef struct jpec_block_t_ { + float dct[64]; /* DCT coefficients */ + int quant[64]; /* Quantization coefficients */ + int zz[64]; /* Zig-Zag coefficients */ + int len; /* Length of Zig-Zag coefficients */ +} jpec_block_t; + +/** Skeleton for an Huffman entropy coder */ +typedef struct jpec_huff_skel_t_ { + void *opq; + void (*del)(void *opq); + void (*encode_block)(void *opq, jpec_block_t *block, jpec_buffer_t *buf); +} jpec_huff_skel_t; + +/** JPEG encoder */ +struct jpec_enc_t_ { + /** Input image data */ + const uint8_t *img; /* image buffer */ + uint16_t w; /* image width */ + uint16_t h; /* image height */ + uint16_t w8; /* w rounded to upper multiple of 8 */ + /** JPEG extensible byte buffer */ + jpec_buffer_t *buf; + /** Compression parameters */ + int qual; /* JPEG quality factor */ + int dqt[64]; /* scaled quantization matrix */ + /** Current 8x8 block */ + int bmax; /* maximum number of blocks (N) */ + int bnum; /* block number in 0..N-1 */ + uint16_t bx; /* block start X */ + uint16_t by; /* block start Y */ + jpec_block_t block; /* block data */ + /** Huffman entropy coder */ + jpec_huff_skel_t *hskel; +}; + +#endif diff --git a/lib/jpec/huff.cpp b/lib/jpec/huff.cpp new file mode 100644 index 0000000..7084cc8 --- /dev/null +++ b/lib/jpec/huff.cpp @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#include "huff.h" +#include "conf.h" + +#ifdef WORD_BIT +#define JPEC_INT_WIDTH WORD_BIT +#else +#define JPEC_INT_WIDTH (int)(sizeof(int) * CHAR_BIT) +#endif + +#if __GNUC__ +#define JPEC_HUFF_NBITS(JPEC_nbits, JPEC_val) \ + JPEC_nbits = (!JPEC_val) ? 0 : JPEC_INT_WIDTH - __builtin_clz(JPEC_val) +#else +#define JPEC_HUFF_NBITS(JPEC_nbits, JPEC_val) \ + JPEC_nbits = 0; \ + while (val) { \ + JPEC_nbits++; \ + val >>= 1; \ + } +#endif + +/* Private function prototypes */ +static void jpec_huff_encode_block_impl(jpec_block_t *block, jpec_huff_state_t *s); +static void jpec_huff_write_bits(jpec_huff_state_t *s, unsigned int bits, int n); + +void jpec_huff_skel_init(jpec_huff_skel_t *skel) { + assert(skel); + memset(skel, 0, sizeof(*skel)); + skel->opq = jpec_huff_new(); + skel->del = (void (*)(void *))jpec_huff_del; + skel->encode_block = (void (*)(void *, jpec_block_t *, jpec_buffer_t *))jpec_huff_encode_block; +} + +jpec_huff_t *jpec_huff_new(void) { + jpec_huff_t *h = (jpec_huff_t *)malloc(sizeof(*h)); + h->state.buffer = 0; + h->state.nbits = 0; + h->state.dc = 0; + h->state.buf = NULL; + return h; +} + +void jpec_huff_del(jpec_huff_t *h) { + assert(h); + /* Flush any remaining bits and fill in the incomple byte (if any) with 1-s */ + jpec_huff_write_bits(&h->state, 0x7F, 7); + free(h); +} + +void jpec_huff_encode_block(jpec_huff_t *h, jpec_block_t *block, jpec_buffer_t *buf) { + assert(h && block && buf); + jpec_huff_state_t state; + state.buffer = h->state.buffer; + state.nbits = h->state.nbits; + state.dc = h->state.dc; + state.buf = buf; + jpec_huff_encode_block_impl(block, &state); + h->state.buffer = state.buffer; + h->state.nbits = state.nbits; + h->state.dc = state.dc; + h->state.buf = state.buf; +} + +static void jpec_huff_encode_block_impl(jpec_block_t *block, jpec_huff_state_t *s) { + assert(block && s); + int val, bits, nbits; + /* DC coefficient encoding */ + if (block->len > 0) { + val = block->zz[0] - s->dc; + s->dc = block->zz[0]; + } + else { + val = -s->dc; + s->dc = 0; + } + bits = val; + if (val < 0) { + val = -val; + bits = ~val; + } + JPEC_HUFF_NBITS(nbits, val); + jpec_huff_write_bits(s, jpec_dc_code[nbits], jpec_dc_len[nbits]); + if (nbits) jpec_huff_write_bits(s, (unsigned int) bits, nbits); + /* AC coefficients encoding (w/ RLE of zeros) */ + int nz = 0; + for (int i = 1; i < block->len; i++) { + if ((val = block->zz[i]) == 0) nz++; + else { + while (nz >= 16) { + jpec_huff_write_bits(s, jpec_ac_code[0xF0], jpec_ac_len[0xF0]); /* ZRL code */ + nz -= 16; + } + bits = val; + if (val < 0) { + val = -val; + bits = ~val; + } + JPEC_HUFF_NBITS(nbits, val); + int j = (nz << 4) + nbits; + jpec_huff_write_bits(s, jpec_ac_code[j], jpec_ac_len[j]); + if (nbits) jpec_huff_write_bits(s, (unsigned int) bits, nbits); + nz = 0; + } + } + if (block->len < 64) { + jpec_huff_write_bits(s, jpec_ac_code[0x00], jpec_ac_len[0x00]); /* EOB marker */ + } +} + +/* Write n bits into the JPEG buffer, with 0 < n <= 16. + * + * == Details + * - 16 bits are large enough to hold any zig-zag coeff or the longest AC code + * - bits are chunked into bytes before being written into the JPEG buffer + * - any remaining bits are kept in memory by the Huffman state + * - at most 7 bits can be kept in memory + * - a 32-bit integer buffer is used internally + * - only the right 24 bits part of this buffer are used + * - the input bits and remaining bits (if any) are left-justified on this part + * - a mask is used to mask off any extra bits: useful when the input value has been + * first transformed by bitwise complement(|initial value|) + * - if an 0xFF byte is detected a 0x00 stuff byte is automatically written right after + */ +static void jpec_huff_write_bits(jpec_huff_state_t *s, unsigned int bits, int n) { + assert(s && n > 0 && n <= 16); + int32_t mask = (((int32_t) 1) << n) - 1; + int32_t buffer = (int32_t) bits; + int nbits = s->nbits + n; + buffer &= mask; + buffer <<= 24 - nbits; + buffer |= s->buffer; + while (nbits >= 8) { + int chunk = (int) ((buffer >> 16) & 0xFF); + jpec_buffer_write_byte(s->buf, chunk); + if (chunk == 0xFF) jpec_buffer_write_byte(s->buf, 0x00); + buffer <<= 8; + nbits -= 8; + } + s->buffer = buffer; + s->nbits = nbits; +} diff --git a/lib/jpec/huff.h b/lib/jpec/huff.h new file mode 100644 index 0000000..b29049d --- /dev/null +++ b/lib/jpec/huff.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#ifndef JPEC_HUFFMAN_H +#define JPEC_HUFFMAN_H + +#include + +#include "enc.h" +#include "buf.h" + +/** Entropy coding data that hold state along blocks */ +typedef struct jpec_huff_state_t_ { + int32_t buffer; /* bits buffer */ + int nbits; /* number of bits remaining in buffer */ + int dc; /* DC coefficient from previous block (or 0) */ + jpec_buffer_t *buf; /* JPEG global buffer */ +} jpec_huff_state_t; + +/** Type of an Huffman JPEG encoder */ +typedef struct jpec_huff_t_ { + jpec_huff_state_t state; /* state from previous block encoding */ +} jpec_huff_t; + +/** Skeleton initialization */ +void jpec_huff_skel_init(jpec_huff_skel_t *skel); + +jpec_huff_t *jpec_huff_new(void); + +void jpec_huff_del(jpec_huff_t *h); + +void jpec_huff_encode_block(jpec_huff_t *h, jpec_block_t *block, jpec_buffer_t *buf); + +#endif diff --git a/lib/jpec/jpec.h b/lib/jpec/jpec.h new file mode 100644 index 0000000..81a40c8 --- /dev/null +++ b/lib/jpec/jpec.h @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2012-2016 Moodstocks SAS + * + * 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 COPYRIGHT 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. + */ + +#ifndef JPEC_H +#define JPEC_H + +#include + +/************************************************* + * JPEG Encoder + *************************************************/ + +/* ------------------------------------------------- + * LIMITATIONS + * ------------------------------------------------- + * - Grayscale *only* (monochrome JPEG file): no support for color + * - Baseline DCT-based (SOF0), JFIF 1.01 (APP0) JPEG + * - Block size of 8x8 pixels *only* + * - Default quantization and Huffman tables *only* + * - No border filling support: the input image *MUST* represent an integer + * number of blocks, i.e. each dimension must be a multiple of 8 + */ + +/** Type of a JPEG encoder object */ +typedef struct jpec_enc_t_ jpec_enc_t; + + +// Callback signatures +typedef void (*jpec_init_callback_t)(jpec_enc_t *jpec); +typedef void (*jpec_enc_callback_t)(int offset, uint8_t val); +typedef void (*jpec_done_callback_t)(jpec_enc_t *jpec); + + +/* + * Create a JPEG encoder with default quality factor + * `img' specifies the pointer to aligned image data. + * `w' specifies the image width in pixels. + * `h' specifies the image height in pixels. + * Because the returned encoder is allocated by this function, it should be + * released with the `jpec_enc_del' call when it is no longer useful. + * Note: for efficiency the image data is *NOT* copied and the encoder just + * retains a pointer to it. Thus the image data must not be deleted + * nor change until the encoder object gets deleted. + */ +jpec_enc_t *jpec_enc_new(const uint8_t *img, uint16_t w, uint16_t h); +/* + * `q` specifies the JPEG quality factor in 0..100 + */ +jpec_enc_t *jpec_enc_new2(const uint8_t *img, uint16_t w, uint16_t h, int q, jpec_enc_callback_t callback); + +/* + * Release a JPEG encoder object + * `e` specifies the encoder object + */ +void jpec_enc_del(jpec_enc_t *e); + +/* + * Run the JPEG encoding + * `e` specifies the encoder object + * `len` specifies the pointer to the variable into which the length of the + * JPEG blob is assigned + * If successful, the return value is the pointer to the JPEG blob. `NULL` is + * returned if an error occurred. + * Note: the caller should take care to copy or save the JPEG blob before + * calling `jpec_enc_del` since the blob will no longer be maintained after. + */ +const uint8_t *jpec_enc_run(jpec_enc_t *e); + +#endif \ No newline at end of file