diff --git a/esp32_fw/data/calibrib50.vlw b/esp32_fw/data/calibrib50.vlw new file mode 100644 index 00000000..6c0b0dd1 Binary files /dev/null and b/esp32_fw/data/calibrib50.vlw differ diff --git a/esp32_fw/data/calibrib62.vlw b/esp32_fw/data/calibrib62.vlw new file mode 100644 index 00000000..97ea7ad3 Binary files /dev/null and b/esp32_fw/data/calibrib62.vlw differ 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