external image upload, 1bpp buffer, cleanup

- if image only has BW, then sending red buffer is skipped
- jpg image upload from external source: see /upload-test.html
This commit is contained in:
Nic Limper
2023-03-22 19:30:25 +01:00
parent c8b0812759
commit 5205338635
9 changed files with 108 additions and 487 deletions

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Upload Form</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="/imgupload">
<label for="mac">Enter a 6 byte MAC address:</label><br>
<input type="text" id="mac" name="mac"><br>
<label for="image">Select an image to upload. Must have the correct resolution for the tag (rotating is allowed):</label><br>
<input type="file" id="image" name="file"><br>
<input type="submit" value="Upload">
</form>
</body>
</html>

View File

@@ -37,7 +37,7 @@ struct AvailDataReq {
uint16_t batteryMv;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities; // undefined, as of now
uint8_t capabilities;
} __packed;
struct espAvailDataReq {
@@ -53,6 +53,8 @@ struct espAvailDataReq {
#define DATATYPE_IMG_RAW_1BPP 0x20 // 2888 bytes for 1.54" / 4736 2.9" / 15000 4.2"
#define DATATYPE_IMG_RAW_2BPP 0x21 // 5776 bytes for 1.54" / 9472 2.9" / 30000 4.2"
#define DATATYPE_IMG_RAW_1BPP_DIRECT 0x3F // only for 1.54", don't write to EEPROM, but straightaway to the EPD
#define DATATYPE_NFC_RAW_CONTENT 0xA0 // raw memory content for the NT3H1101
#define DATATYPE_NFC_URL_DIRECT 0xA1 // URL format for NT3H1101
#define EPD_LUT_DEFAULT 0
#define EPD_LUT_NO_REPEATS 1

View File

@@ -13,20 +13,20 @@ struct contentTypes {
void (*functionname)();
String description;
String optionList;
};
};
void contentRunner();
void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo);
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin);
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, imgParam &imageParams);
void drawString(TFT_eSprite &spr, String content, uint16_t posx, uint16_t posy, String font, byte align = 0, uint16_t color = TFT_BLACK);
void initSprite(TFT_eSprite &spr, int w, int h);
void drawDate(String &filename, tagRecord *&taginfo);
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo);
void drawWeather(String &filename, String location, tagRecord *&taginfo);
void drawForecast(String &filename, String location, tagRecord *&taginfo);
void drawIdentify(String &filename, tagRecord *&taginfo);
bool getImgURL(String &filename, String URL, time_t fetched);
bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo);
void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams);
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams);
void drawWeather(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams);
void drawForecast(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams);
void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams);
bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams);
bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams);
char *formatHttpDate(time_t t);
String urlEncode(const char *msg);
int windSpeedToBeaufort(float windSpeed);

View File

@@ -3,34 +3,10 @@
#pragma once
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,
struct imgParam {
bool hasRed;
uint8_t dataType;
};
void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout);
void spr2buffer(TFT_eSprite &spr, String &fileout);
void jpg2buffer(String filein, String fileout);
void bmp2grays(String filein, String fileout);
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);
void jpg2buffer(String filein, String fileout, imgParam &imageParams);

View File

@@ -6,7 +6,6 @@ extern bool checkCRC(void* p, uint8_t len);
extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin);
extern bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin);
extern void processJoinNetwork(struct espJoinNetwork* xjn);
extern void processXferComplete(struct espXferComplete* xfc);
extern void processXferTimeout(struct espXferComplete* xfc);
extern void processDataReq(struct espAvailDataReq* adr);

View File

@@ -78,12 +78,17 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
wsLog("Updating " + dst);
taginfo->nextupdate = now + 60;
switch (taginfo->contentMode) {
imgParam imageParams;
imageParams.hasRed = false;
imageParams.dataType = DATATYPE_IMG_RAW_1BPP;
switch (taginfo->contentMode) {
case Image:
if (cfgobj["filename"].as<String>() && cfgobj["filename"].as<String>() != "null" && !cfgobj["#fetched"].as<bool>()) {
jpg2buffer(cfgobj["filename"].as<String>(), filename);
if (prepareDataAvail(&filename, DATATYPE_IMG_RAW_2BPP, mac, cfgobj["timetolive"].as<int>())) {
jpg2buffer(cfgobj["filename"].as<String>(), filename, imageParams);
if (imageParams.hasRed) imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
if (prepareDataAvail(&filename, imageParams.dataType, mac, cfgobj["timetolive"].as<int>())) {
cfgobj["#fetched"] = true;
} else {
wsErr("Error accessing " + filename);
@@ -94,26 +99,26 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
case Today:
drawDate(filename, taginfo);
drawDate(filename, taginfo, imageParams);
taginfo->nextupdate = midnight;
updateTagImage(filename, mac, (midnight - now) / 60 - 10);
updateTagImage(filename, mac, (midnight - now) / 60 - 10, imageParams);
break;
case CountDays:
if (buttonPressed) cfgobj["counter"] = 0;
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo);
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo, imageParams);
taginfo->nextupdate = midnight;
updateTagImage(filename, mac, (buttonPressed ? 0 : 15));
updateTagImage(filename, mac, (buttonPressed ? 0 : 15), imageParams);
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
break;
case CountHours:
if (buttonPressed) cfgobj["counter"] = 0;
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo);
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo, imageParams);
taginfo->nextupdate = now + 3600;
updateTagImage(filename, mac, (buttonPressed ? 0 : 5));
updateTagImage(filename, mac, (buttonPressed ? 0 : 5), imageParams);
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
break;
@@ -124,16 +129,16 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current_weather=true
// https://github.com/erikflowers/weather-icons
drawWeather(filename, cfgobj["location"], taginfo);
drawWeather(filename, cfgobj["location"], taginfo, imageParams);
taginfo->nextupdate = now + 3600;
updateTagImage(filename, mac, 15);
updateTagImage(filename, mac, 15, imageParams);
break;
case Forecast:
drawForecast(filename, cfgobj["location"], taginfo);
drawForecast(filename, cfgobj["location"], taginfo, imageParams);
taginfo->nextupdate = now + 3 * 3600;
updateTagImage(filename, mac, 15);
updateTagImage(filename, mac, 15, imageParams);
break;
case Firmware:
@@ -155,16 +160,16 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
case Memo:
drawIdentify(filename, taginfo);
drawIdentify(filename, taginfo, imageParams);
taginfo->nextupdate = now + 12 * 3600;
updateTagImage(filename, mac, 0);
updateTagImage(filename, mac, 0, imageParams);
break;
case ImageUrl:
if (getImgURL(filename, cfgobj["url"], (time_t)cfgobj["#fetched"])) {
if (getImgURL(filename, cfgobj["url"], (time_t)cfgobj["#fetched"], imageParams)) {
taginfo->nextupdate = now + 60 * (cfgobj["interval"].as<int>() < 5 ? 5 : cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>(), imageParams);
cfgobj["#fetched"] = now;
} else {
taginfo->nextupdate = now + 300;
@@ -173,9 +178,9 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
case RSSFeed:
if (getRSSfeed(filename, cfgobj["url"], cfgobj["title"], taginfo)) {
if (getRSSfeed(filename, cfgobj["url"], cfgobj["title"], taginfo, imageParams)) {
taginfo->nextupdate = now + 60 * (cfgobj["interval"].as<int>() < 5 ? 5 : cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>(), imageParams);
} else {
taginfo->nextupdate = now + 300;
}
@@ -185,8 +190,9 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
taginfo->modeConfigJson = doc.as<String>();
}
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin) {
prepareDataAvail(&filename, DATATYPE_IMG_RAW_2BPP, dst, nextCheckin);
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, imgParam &imageParams) {
if (imageParams.hasRed) imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
prepareDataAvail(&filename, imageParams.dataType, dst, nextCheckin);
return true;
}
@@ -208,8 +214,7 @@ void initSprite(TFT_eSprite &spr, int w, int h) {
spr.fillSprite(PAL_WHITE);
}
void drawDate(String &filename, tagRecord *&taginfo) {
void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
time_t now;
@@ -238,12 +243,11 @@ void drawDate(String &filename, tagRecord *&taginfo) {
drawString(spr, String(timeinfo.tm_mday), 152 / 2, 42, "fonts/numbers2-1", TC_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo) {
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
LittleFS.begin();
@@ -282,12 +286,11 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
void drawWeather(String &filename, String location, tagRecord *&taginfo) {
void drawWeather(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
@@ -417,14 +420,14 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) {
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
}
http.end();
}
void drawForecast(String &filename, String location, tagRecord *&taginfo) {
void drawForecast(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
@@ -520,15 +523,14 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) {
}
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
}
http.end();
}
void drawIdentify(String &filename, tagRecord *&taginfo) {
void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
LittleFS.begin();
@@ -544,11 +546,11 @@ void drawIdentify(String &filename, tagRecord *&taginfo) {
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
bool getImgURL(String &filename, String URL, time_t fetched) {
bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams) {
// https://images.klari.net/kat-bw29.jpg
LittleFS.begin();
@@ -564,7 +566,7 @@ bool getImgURL(String &filename, String URL, time_t fetched) {
if (f) {
http.writeToStream(&f);
f.close();
jpg2buffer("/temp/temp.jpg", filename);
jpg2buffer("/temp/temp.jpg", filename, imageParams);
}
} else {
if (httpCode!=304) {
@@ -579,7 +581,7 @@ bool getImgURL(String &filename, String URL, time_t fetched) {
rssClass reader;
bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo) {
bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) {
// https://github.com/garretlab/shoddyxml2
// http://feeds.feedburner.com/tweakers/nieuws
@@ -627,7 +629,7 @@ bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo)
}
}
spr2buffer(spr, filename);
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
return true;

View File

@@ -14,7 +14,7 @@ bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
return 1;
}
void jpg2buffer(String filein, String fileout) {
void jpg2buffer(String filein, String fileout, imgParam &imageParams) {
LittleFS.begin();
TJpgDec.setSwapBytes(true);
TJpgDec.setJpgScale(1);
@@ -34,7 +34,7 @@ void jpg2buffer(String filein, String fileout) {
spr.fillSprite(TFT_WHITE);
TJpgDec.drawFsJpg(0, 0, filein, LittleFS);
spr2buffer(spr, fileout);
spr2buffer(spr, fileout, imageParams);
spr.deleteSprite();
}
@@ -47,207 +47,6 @@ static uint32_t repackPackedVals(uint32_t val, uint32_t pixelsPerPackedUnit, uin
return ret;
}
void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout) {
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");
}
struct Color {
uint8_t r, g, b;
Color() : r(0), g(0), b(0) {}
@@ -268,7 +67,7 @@ uint32_t colorDistance(const Color &c1, const Color &c2, const Error &e1) {
return round(r_diff * r_diff + g_diff * g_diff + b_diff * b_diff);
}
void spr2buffer(TFT_eSprite &spr, String &fileout) {
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
long t = millis();
LittleFS.begin();
@@ -324,6 +123,7 @@ void spr2buffer(TFT_eSprite &spr, String &fileout) {
if (best_color_index & 1) {
blackBuffer[byteIndex] |= (1 << bitIndex);
} else if (best_color_index & 2) {
imageParams.hasRed = true;
redBuffer[byteIndex] |= (1 << bitIndex);
}
@@ -353,7 +153,7 @@ void spr2buffer(TFT_eSprite &spr, String &fileout) {
}
f_out.write(blackBuffer, bufferSize);
f_out.write(redBuffer, bufferSize);
if (imageParams.hasRed) f_out.write(redBuffer, bufferSize);
delete[] blackBuffer;
delete[] redBuffer;
@@ -361,195 +161,3 @@ void spr2buffer(TFT_eSprite &spr, String &fileout) {
f_out.close();
Serial.println("finished writing buffer " + 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");
}

View File

@@ -328,8 +328,8 @@ void processDataReq(struct espAvailDataReq* eadr) {
taginfo->hwType = eadr->adr.hwType;
taginfo->wakeupReason = eadr->adr.wakeupReason;
taginfo->capabilities = eadr->adr.capabilities;
if (eadr->adr.wakeupReason == WAKEUP_REASON_FIRSTBOOT && !taginfo->pending) {
taginfo->nextupdate = 0;
if (eadr->adr.wakeupReason >= 0xF0) {
if (!taginfo->pending) taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
}

View File

@@ -285,8 +285,12 @@ void init_web() {
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
if (request->hasParam("mac", true)) {
filename = request->getParam("mac", true)->value() + ".jpg";
} else {
filename = "unknown.jpg";
}
Serial.print((String) "UploadStart: " + filename);
// open the file on first call and store the file handle in the request object
request->_tempFile = LittleFS.open("/" + filename, "w");
}
if (len) {
@@ -294,15 +298,26 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
request->_tempFile.write(data, len);
}
if (final) {
Serial.print((String) "UploadEnd: " + filename + "," + index + len);
// close the file handle as the upload is now done
Serial.println((String) " End: " + filename + ", " + index + len);
request->_tempFile.close();
request->send(200, "text/plain", "File Uploaded !");
/*
sscanf() if (request->hasParam("id") && request->hasParam("file")) {
id = request->getParam("id")->value().toInt();
filename = request->getParam("file")->value();
if (request->hasParam("mac", true)) {
String dst = request->getParam("mac", true)->value();
uint8_t mac[6];
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
taginfo->modeConfigJson = "{\"filename\":\"" + dst + ".jpg\",\"timetolive\":\"0\"}";
taginfo->contentMode = 0;
taginfo->nextupdate = 0;
wsSendTaginfo(mac);
request->send(200, "text/plain", "Ok, saved");
} else {
request->send(200, "text/plain", "Error while saving: mac not found");
}
*/
}
} else {
request->send(500, "text/plain", "parameters incomplete");
}
}
}