From 1d24b563d76caccd2c49925a129acd7e2498f1f5 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Sat, 28 Jan 2023 19:16:30 +0100 Subject: [PATCH] added image handling - bmp2grays to convert 24 bits BMP to 2 bits - spr2grays to create image using tft_espi library --- esp32_fw/data/calibrib50.vlw | Bin 0 -> 22807 bytes esp32_fw/data/calibrib62.vlw | Bin 0 -> 34393 bytes esp32_fw/include/makeimage.h | 32 +++ esp32_fw/platformio.ini | 1 + esp32_fw/src/main.cpp | 2 + esp32_fw/src/makeimage.cpp | 428 +++++++++++++++++++++++++++++++++++ 6 files changed, 463 insertions(+) create mode 100644 esp32_fw/data/calibrib50.vlw create mode 100644 esp32_fw/data/calibrib62.vlw create mode 100644 esp32_fw/include/makeimage.h create mode 100644 esp32_fw/src/makeimage.cpp diff --git a/esp32_fw/data/calibrib50.vlw b/esp32_fw/data/calibrib50.vlw new file mode 100644 index 0000000000000000000000000000000000000000..6c0b0dd1fd3e197c8ce9193855862b4e90c5988d GIT binary patch literal 22807 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 U{_O4do?ZUt-RJL=_v7&X51ljV8~^|S literal 0 HcmV?d00001 diff --git a/esp32_fw/data/calibrib62.vlw b/esp32_fw/data/calibrib62.vlw new file mode 100644 index 0000000000000000000000000000000000000000..97ea7ad38cf5cfd10b09571014ee5c99229cf9d4 GIT binary patch literal 34393 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 literal 0 HcmV?d00001 diff --git a/esp32_fw/include/makeimage.h b/esp32_fw/include/makeimage.h new file mode 100644 index 00000000..ff04e87d --- /dev/null +++ b/esp32_fw/include/makeimage.h @@ -0,0 +1,32 @@ +#include +#include + +struct BitmapFileHeader { + uint8_t sig[2]; + uint32_t fileSz; + uint8_t rfu[4]; + uint32_t dataOfst; + uint32_t headerSz; //40 + int32_t width; + int32_t height; + uint16_t colorplanes; //must be one + uint16_t bpp; + uint32_t compression; + uint32_t dataLen; //may be 0 + uint32_t pixelsPerMeterX; + uint32_t pixelsPerMeterY; + uint32_t numColors; //if zero, assume 2^bpp + uint32_t numImportantColors; + +} __attribute__((packed)); + +enum EinkClut { + EinkClutTwoBlacks = 0, + EinkClutTwoBlacksAndRed, + EinkClutFourBlacks, + EinkClutThreeBlacksAndRed, +}; + +void tftinit(); +void spr2grays(TFT_eSprite &spr, long w, long h, String fileout); +void bmp2grays(String filein,String fileout); diff --git a/esp32_fw/platformio.ini b/esp32_fw/platformio.ini index c22487e0..4ffae9cb 100644 --- a/esp32_fw/platformio.ini +++ b/esp32_fw/platformio.ini @@ -22,5 +22,6 @@ lib_deps = https://github.com/me-no-dev/ESPAsyncWebServer https://github.com/tzapu/WiFiManager.git#feature_asyncwebserver bblanchon/ArduinoJson + bodmer/TFT_eSPI upload_port = COM5 monitor_port = COM5 diff --git a/esp32_fw/src/main.cpp b/esp32_fw/src/main.cpp index efe79a2d..89f8bbc8 100644 --- a/esp32_fw/src/main.cpp +++ b/esp32_fw/src/main.cpp @@ -8,6 +8,7 @@ #include "serial.h" #include "soc/rtc_wdt.h" #include "web.h" +#include "makeimage.h" void freeHeapTask(void* parameter) { while (1) { @@ -20,6 +21,7 @@ void setup() { Serial.begin(115200); Serial.print(">\n"); init_web(); + tftinit(); long timezone = 2; byte daysavetime = 1; diff --git a/esp32_fw/src/makeimage.cpp b/esp32_fw/src/makeimage.cpp new file mode 100644 index 00000000..28de9339 --- /dev/null +++ b/esp32_fw/src/makeimage.cpp @@ -0,0 +1,428 @@ +#include +#include +#include +#include +#include + +void tftinit() { + TFT_eSPI tft = TFT_eSPI(); + TFT_eSprite spr = TFT_eSprite(&tft); + + LittleFS.begin(); + + long w = 296, h = 128; // mag staand of liggend + spr.createSprite(w, h); + spr.setColorDepth(8); + spr.fillSprite(TFT_WHITE); + spr.setTextDatum(TC_DATUM); + spr.loadFont("calibrib62", LittleFS); + spr.setTextColor(TFT_RED, TFT_WHITE); + spr.drawString("zondag", w / 2, 10); + spr.loadFont("calibrib50", LittleFS); + spr.setTextColor(TFT_BLACK, TFT_WHITE); + spr.drawString("28 januari", w / 2, 73); + spr.unloadFont(); + + spr2grays(spr, w, h, "/testspr3.bmp"); + + spr.deleteSprite(); + // bmp2grays("/test.bmp", "/testgrays3.bmp"); +} + +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 + + Serial.println("start writing BMP"); + 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 = false, rotated = false; + int skipBytes; + + 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); + } + + 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; + + 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(millis() - t); + Serial.println("finished writing BMP"); +} + +void bmp2grays(String filein, String fileout) { + // based on bmp2grays function by Dmitry.GR + + Serial.println("start writing BMP2"); + 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; + + 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(millis() - t); + Serial.println("finished writing BMP2"); +} \ No newline at end of file