- switch to canvas for print screen functionality
- implement jpg encoder for print screen
pull/1/head
Thomas Ballmann 4 years ago
parent 2159b78ac4
commit 22e8b6df19

@ -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));
}

@ -1,6 +1,8 @@
#include <Arduino.h>
#include "display.h"
#include <SPIFFS.h>
#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<GxEPD2_750, GxEPD2_750::HEIGHT> 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);
}

@ -1,24 +1,23 @@
#ifndef DISPLAY_H
#define DISPLAY_H
//#include "EPD2_BW.h"
#define ENABLE_GxEPD2_GFX 0
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
//#define ENABLE_GxEPD2_GFX 0
extern GxEPD2_BW<GxEPD2_750, GxEPD2_750::HEIGHT> display;
//extern GxEPD2_BW<GxEPD2_750, GxEPD2_750::HEIGHT> display(GxEPD2_750(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4));
//GxEPD2_BW<GxEPD2_750, GxEPD2_750::HEIGHT> displayGet();
#include <Adafruit_GFX.h>
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

@ -10,6 +10,7 @@
#include "faceWeatherIcons.h"
#include <Fonts/FreeMono12pt7b.h> // weekday - month year
#include <Fonts/FreeMonoBold9pt7b.h> //
#include <Fonts/FreeSans24pt7b.h> // current day
#include <Fonts/FreeSansBold24pt7b.h> // 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);
}
*/
}

@ -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);
}
}

@ -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;

@ -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);
}

@ -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 <stdint.h>
// 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

@ -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
};

@ -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 <assert.h>
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/** 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

@ -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 <Arduino.h>
#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;
}
}

@ -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 <stdint.h>
#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

@ -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;
}

@ -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 <stdint.h>
#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

@ -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 <stdint.h>
/*************************************************
* 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
Loading…
Cancel
Save