mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 10:06:07 +01:00
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:
19
esp32_fw/data/www/upload-test.html
Normal file
19
esp32_fw/data/www/upload-test.html
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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¤t_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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user