Files
OpenEPaperLink/ESP32_AP-Flasher/src/makeimage.cpp
2023-03-23 09:51:50 +01:00

440 lines
14 KiB
C++

#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
#include <TFT_eSPI.h>
#include <TJpg_Decoder.h>
#include <makeimage.h>
#include <web.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {
spr.pushImage(x, y, w, h, bitmap);
return 1;
}
void jpg2grays(String filein, String fileout) {
LittleFS.begin();
TJpgDec.setSwapBytes(true);
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(spr_output);
uint16_t w = 0, h = 0;
TJpgDec.getFsJpgSize(&w, &h, filein, LittleFS);
Serial.println("jpeg conversion " + String(w) + "x" + String(h));
spr.createSprite(w, h);
if (spr.getPointer() == nullptr) {
wsErr("Failed to create sprite in jpg2grays");
}
spr.setColorDepth(8);
spr.fillSprite(TFT_WHITE);
TJpgDec.drawFsJpg(0, 0, filein, LittleFS);
spr2grays(spr, w, h, fileout);
spr.deleteSprite();
}
static uint32_t repackPackedVals(uint32_t val, uint32_t pixelsPerPackedUnit, uint32_t packedMultiplyVal) {
uint32_t ret = 0, i;
for (i = 0; i < pixelsPerPackedUnit; i++) {
ret = ret * packedMultiplyVal + val % packedMultiplyVal;
val /= packedMultiplyVal;
}
return ret;
}
void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout) {
// based on bmp2grays function by Dmitry.GR
long t = millis();
LittleFS.begin();
fs::File f_out = LittleFS.open(fileout, "w");
uint32_t c, rowBytesOut, rowBytesIn, outBpp, i, numRows, pixelsPerPackedUnit = 1, packedMultiplyVal = 0x01000000, packedOutBpp = 0;
uint32_t numGrays, extraColor = 0;
struct BitmapFileHeader hdr;
memset(&hdr, 0, sizeof(hdr));
enum EinkClut clutType;
uint8_t clut[256][3];
bool dither = true, rotated = false;
int skipBytes;
srand(0);
clutType = EinkClutTwoBlacksAndRed;
if (w > h) {
hdr.width = h;
hdr.height = w;
rotated = true;
} else {
hdr.width = w;
hdr.height = h;
}
hdr.bpp = 24;
hdr.sig[0] = 'B';
hdr.sig[1] = 'M';
hdr.colorplanes = 1;
switch (clutType) {
case EinkClutTwoBlacks:
numGrays = 2;
outBpp = 1;
break;
case EinkClutTwoBlacksAndRed:
extraColor = 0xff0000;
numGrays = 2;
outBpp = 2;
break;
case EinkClutFourBlacks:
numGrays = 4;
outBpp = 2;
break;
case EinkClutThreeBlacksAndRed:
numGrays = 3;
extraColor = 0xff0000;
outBpp = 2;
break;
}
packedOutBpp = outBpp;
rowBytesIn = (hdr.width * hdr.bpp + 31) / 32 * 4;
rowBytesOut = ((hdr.width + pixelsPerPackedUnit - 1) / pixelsPerPackedUnit) * packedOutBpp;
rowBytesOut = (rowBytesOut + 31) / 32 * 4;
numRows = hdr.height < 0 ? -hdr.height : hdr.height;
hdr.bpp = outBpp;
hdr.numColors = 1 << outBpp;
hdr.numImportantColors = 1 << outBpp;
hdr.dataOfst = sizeof(struct BitmapFileHeader) + 4 * hdr.numColors;
hdr.dataLen = numRows * rowBytesOut;
hdr.fileSz = hdr.dataOfst + hdr.dataLen;
hdr.headerSz = 40;
hdr.compression = 0;
f_out.write((uint8_t *)&hdr, sizeof(hdr));
// emit & record grey clut entries
for (i = 0; i < numGrays; i++) {
uint32_t val = 255 * i / (numGrays - 1);
f_out.write(val);
f_out.write(val);
f_out.write(val);
f_out.write(val);
clut[i][0] = val;
clut[i][1] = val;
clut[i][2] = val;
}
if (extraColor) {
f_out.write((extraColor >> 0) & 0xff); // B
f_out.write((extraColor >> 8) & 0xff); // G
f_out.write((extraColor >> 16) & 0xff); // R
f_out.write(0x00); // A
clut[i][0] = (extraColor >> 0) & 0xff;
clut[i][1] = (extraColor >> 8) & 0xff;
clut[i][2] = (extraColor >> 16) & 0xff;
}
// pad clut to size
for (i = numGrays + (extraColor ? 1 : 0); i < hdr.numColors; i++) {
f_out.write(0x00);
f_out.write(0x00);
f_out.write(0x00);
f_out.write(0x00);
}
const int dither_matrix[4][4] = {
{1, 9, 3, 11},
{13, 5, 15, 7},
{4, 12, 2, 10},
{0, 8, 14, 6}};
while (numRows--) {
uint32_t pixelValsPackedSoFar = 0, numPixelsPackedSoFar = 0, valSoFar = 0, bytesIn = 0, bytesOut = 0, bitsSoFar = 0;
for (c = 0; c < hdr.width; c++, bytesIn += 3) {
int64_t bestDist = 0x7fffffffffffffffll;
uint8_t bestIdx = 0;
int32_t ditherFudge = 0;
uint16_t color565;
if (rotated) {
color565 = spr.readPixel(hdr.height - 1 - numRows, c);
} else {
color565 = spr.readPixel(c, numRows);
}
uint8_t red = ((color565 >> 11) & 0x1F) * 8;
uint8_t green = ((color565 >> 5) & 0x3F) * 4;
uint8_t blue = (color565 & 0x1F) * 8;
if (dither) {
// ditherFudge = (rand() % 255 - 127) / (int)numGrays; // -64 to 64
ditherFudge = (dither_matrix[numRows % 4][c % 4] - 8) * 16 / (int)numGrays;
}
for (i = 0; i < hdr.numColors; i++) {
int64_t dist = 0;
dist += (blue - clut[i][0] + ditherFudge) * (blue - clut[i][0] + ditherFudge) * 4750ll;
dist += (green - clut[i][1] + ditherFudge) * (green - clut[i][1] + ditherFudge) * 47055ll;
dist += (red - clut[i][2] + ditherFudge) * (red - clut[i][2] + ditherFudge) * 13988ll;
if (dist < bestDist) {
bestDist = dist;
bestIdx = i;
}
}
// pack pixels as needed
pixelValsPackedSoFar = pixelValsPackedSoFar * packedMultiplyVal + bestIdx;
if (++numPixelsPackedSoFar != pixelsPerPackedUnit)
continue;
numPixelsPackedSoFar = 0;
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
pixelValsPackedSoFar = 0;
bitsSoFar += packedOutBpp;
if (bitsSoFar >= 8) {
f_out.write(valSoFar >> (bitsSoFar -= 8));
valSoFar &= (1 << bitsSoFar) - 1;
bytesOut++;
}
}
// see if we have unfinished pixel packages to write
if (numPixelsPackedSoFar) {
while (numPixelsPackedSoFar++ != pixelsPerPackedUnit)
pixelValsPackedSoFar *= packedMultiplyVal;
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
pixelValsPackedSoFar = 0;
bitsSoFar += packedOutBpp;
if (bitsSoFar >= 8) {
f_out.write(valSoFar >> (bitsSoFar -= 8));
valSoFar &= (1 << bitsSoFar) - 1;
bytesOut++;
}
}
if (bitsSoFar) {
valSoFar <<= 8 - bitsSoFar; // left-align it as is expected
f_out.write(valSoFar);
bytesOut++;
}
while (bytesOut++ < rowBytesOut)
f_out.write(0);
}
f_out.close();
Serial.println("finished writing BMP " + String(millis() - t) + "ms");
}
void bmp2grays(String filein, String fileout) {
// based on bmp2grays function by Dmitry.GR
long t = millis();
LittleFS.begin();
fs::File f_in = LittleFS.open(filein, "r");
fs::File f_out = LittleFS.open(fileout, "w");
uint32_t c, rowBytesOut, rowBytesIn, outBpp, i, numRows, pixelsPerPackedUnit = 1, packedMultiplyVal = 0x01000000, packedOutBpp = 0;
uint32_t numGrays, extraColor = 0;
struct BitmapFileHeader hdr;
enum EinkClut clutType;
uint8_t clut[256][3];
bool dither = false;
int skipBytes;
srand(0);
clutType = EinkClutTwoBlacksAndRed;
f_in.read((uint8_t *)&hdr, sizeof(hdr));
if (hdr.sig[0] != 'B' || hdr.sig[1] != 'M' || hdr.headerSz < 40 || hdr.colorplanes != 1 || hdr.bpp != 24 || hdr.compression) {
Serial.println("BITMAP HEADER INVALID, use uncompressed 24 bits RGB");
return;
}
switch (clutType) {
case EinkClutTwoBlacks:
numGrays = 2;
outBpp = 1;
break;
case EinkClutTwoBlacksAndRed:
extraColor = 0xff0000;
numGrays = 2;
outBpp = 2;
break;
case EinkClutFourBlacks:
numGrays = 4;
outBpp = 2;
break;
case EinkClutThreeBlacksAndRed:
numGrays = 3;
extraColor = 0xff0000;
outBpp = 2;
break;
}
packedOutBpp = outBpp;
skipBytes = hdr.dataOfst - sizeof(hdr);
if (skipBytes < 0) {
fprintf(stderr, "file header was too short!\n");
exit(-1);
}
f_in.read(NULL, skipBytes);
rowBytesIn = (hdr.width * hdr.bpp + 31) / 32 * 4;
rowBytesOut = ((hdr.width + pixelsPerPackedUnit - 1) / pixelsPerPackedUnit) * packedOutBpp;
rowBytesOut = (rowBytesOut + 31) / 32 * 4;
numRows = hdr.height < 0 ? -hdr.height : hdr.height;
hdr.bpp = outBpp;
hdr.numColors = 1 << outBpp;
hdr.numImportantColors = 1 << outBpp;
hdr.dataOfst = sizeof(struct BitmapFileHeader) + 4 * hdr.numColors;
hdr.dataLen = numRows * rowBytesOut;
hdr.fileSz = hdr.dataOfst + hdr.dataLen;
hdr.headerSz = 40;
hdr.compression = 0;
f_out.write((uint8_t *)&hdr, sizeof(hdr));
// emit & record grey clut entries
for (i = 0; i < numGrays; i++) {
uint32_t val = 255 * i / (numGrays - 1);
f_out.write(val);
f_out.write(val);
f_out.write(val);
f_out.write(val);
clut[i][0] = val;
clut[i][1] = val;
clut[i][2] = val;
}
// if there is a color CLUT entry, emit that
if (extraColor) {
f_out.write((extraColor >> 0) & 0xff); // B
f_out.write((extraColor >> 8) & 0xff); // G
f_out.write((extraColor >> 16) & 0xff); // R
f_out.write(0x00); // A
clut[i][0] = (extraColor >> 0) & 0xff;
clut[i][1] = (extraColor >> 8) & 0xff;
clut[i][2] = (extraColor >> 16) & 0xff;
}
// pad clut to size
for (i = numGrays + (extraColor ? 1 : 0); i < hdr.numColors; i++) {
f_out.write(0x00);
f_out.write(0x00);
f_out.write(0x00);
f_out.write(0x00);
}
while (numRows--) {
uint32_t pixelValsPackedSoFar = 0, numPixelsPackedSoFar = 0, valSoFar = 0, bytesIn = 0, bytesOut = 0, bitsSoFar = 0;
for (c = 0; c < hdr.width; c++, bytesIn += 3) {
int64_t bestDist = 0x7fffffffffffffffll;
uint8_t rgb[3], bestIdx = 0;
int32_t ditherFudge = 0;
f_in.read(rgb, sizeof(rgb));
if (dither)
ditherFudge = (rand() % 255 - 127) / (int)numGrays;
for (i = 0; i < hdr.numColors; i++) {
int64_t dist = 0;
dist += (rgb[0] - clut[i][0] + ditherFudge) * (rgb[0] - clut[i][0] + ditherFudge) * 4750ll;
dist += (rgb[1] - clut[i][1] + ditherFudge) * (rgb[1] - clut[i][1] + ditherFudge) * 47055ll;
dist += (rgb[2] - clut[i][2] + ditherFudge) * (rgb[2] - clut[i][2] + ditherFudge) * 13988ll;
if (dist < bestDist) {
bestDist = dist;
bestIdx = i;
}
}
// pack pixels as needed
pixelValsPackedSoFar = pixelValsPackedSoFar * packedMultiplyVal + bestIdx;
if (++numPixelsPackedSoFar != pixelsPerPackedUnit)
continue;
numPixelsPackedSoFar = 0;
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
pixelValsPackedSoFar = 0;
bitsSoFar += packedOutBpp;
if (bitsSoFar >= 8) {
f_out.write(valSoFar >> (bitsSoFar -= 8));
valSoFar &= (1 << bitsSoFar) - 1;
bytesOut++;
}
}
// see if we have unfinished pixel packages to write
if (numPixelsPackedSoFar) {
while (numPixelsPackedSoFar++ != pixelsPerPackedUnit)
pixelValsPackedSoFar *= packedMultiplyVal;
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
pixelValsPackedSoFar = 0;
bitsSoFar += packedOutBpp;
if (bitsSoFar >= 8) {
f_out.write(valSoFar >> (bitsSoFar -= 8));
valSoFar &= (1 << bitsSoFar) - 1;
bytesOut++;
}
}
if (bitsSoFar) {
valSoFar <<= 8 - bitsSoFar; // left-align it as is expected
f_out.write(valSoFar);
bytesOut++;
}
while (bytesIn++ < rowBytesIn)
f_in.read(NULL, 1);
while (bytesOut++ < rowBytesOut)
f_out.write(0);
}
f_in.close();
f_out.close();
Serial.println("finished converting BMP " + String(millis() - t) + "ms");
}