From 30812dff49621fc0a39c7fba8fffef922bbe345b Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Tue, 10 Dec 2024 23:01:02 +0100 Subject: [PATCH] color related improvements - added "image" to json commands to insert a jpg image/icon from the flash partition - added optional center/right alignment to "textbox" json command - google calendar content: added optional colors, different color per calendarid - improved ordered dithering, works also with unevenly spaced color tables. This is to be used with graphs etc., not suitable for photos (use floyd steinberg for photos) - improved flyod steinberg dithering (fix some bugs) - added preview rendering for 4bpp images - log tab now scrolls to the top on entering - added optional perceptual color table to tagtypes (for rendering previews, for example to display darker yellows on screen while keeping the 255,255,0 color to the tag - drag/dropping in an image to a tag while holding shift key now uses ordered dithering (default is floyd steinberg) - some tagtype improvements --- ESP32_AP-Flasher/data/fonts/tahoma11.vlw | Bin 0 -> 39578 bytes ESP32_AP-Flasher/include/contentmanager.h | 2 +- ESP32_AP-Flasher/include/makeimage.h | 1 - ESP32_AP-Flasher/include/tag_db.h | 3 +- ESP32_AP-Flasher/src/contentmanager.cpp | 91 ++++++++++++--- ESP32_AP-Flasher/src/makeimage.cpp | 135 +++++++++++++++------- ESP32_AP-Flasher/src/newproto.cpp | 17 +-- ESP32_AP-Flasher/wwwroot/edit.html | 33 +++--- ESP32_AP-Flasher/wwwroot/main.js | 41 ++++--- resources/tagtypes/06.json | 7 +- resources/tagtypes/07.json | 7 +- resources/tagtypes/36.json | 10 +- resources/tagtypes/3C.json | 7 +- resources/tagtypes/60.json | 7 +- resources/tagtypes/66.json | 7 +- resources/tagtypes/67.json | 7 +- resources/tagtypes/68.json | 7 +- resources/tagtypes/C0.json | 8 +- resources/tagtypes/C1.json | 21 +++- resources/tagtypes/C2.json | 22 +++- 20 files changed, 310 insertions(+), 123 deletions(-) create mode 100644 ESP32_AP-Flasher/data/fonts/tahoma11.vlw diff --git a/ESP32_AP-Flasher/data/fonts/tahoma11.vlw b/ESP32_AP-Flasher/data/fonts/tahoma11.vlw new file mode 100644 index 0000000000000000000000000000000000000000..890e90a92d63a9a2196d0d0655715eef6ebbb57b GIT binary patch literal 39578 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 #endif +#include #include #include @@ -212,7 +213,6 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { imageParams.hasRed = false; imageParams.dataType = DATATYPE_IMG_RAW_1BPP; imageParams.dither = 2; - // if (taginfo->hasCustomLUT && taginfo->lut != 1) imageParams.grayLut = true; imageParams.invert = taginfo->invert; imageParams.symbols = 0; @@ -240,10 +240,6 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { imageParams.lut = EPD_LUT_DEFAULT; taginfo->lastfullupdate = now; } - if (taginfo->hasCustomLUT && taginfo->capabilities & CAPABILITY_SUPPORTS_CUSTOM_LUTS && taginfo->lut != 1) { - Serial.println("using custom LUT"); - imageParams.lut = EPD_LUT_OTA; - } int32_t interval = cfgobj["interval"].as() * 60; if (interval == -1440 * 60) { @@ -696,7 +692,7 @@ void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, St } } -void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy, int16_t boxwidth, int16_t boxheight, String font, uint16_t color, uint16_t bgcolor, float lineheight) { +void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy, int16_t boxwidth, int16_t boxheight, String font, uint16_t color, uint16_t bgcolor, float lineheight, byte align) { replaceVariables(content); switch (processFontPath(font)) { case 2: { @@ -706,7 +702,7 @@ void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy case 3: { // vlw bitmap font // spr.drawRect(posx, posy, boxwidth, boxheight, TFT_BLACK); - spr.setTextDatum(TL_DATUM); + spr.setTextDatum(align); if (font != "") spr.loadFont(font.substring(1), *contentFS); spr.setTextWrap(false, false); spr.setTextColor(color, bgcolor); @@ -1195,7 +1191,7 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa wsLog("get calendar"); - StaticJsonDocument<512> loc; + StaticJsonDocument<1024> loc; getTemplate(loc, 11, taginfo->hwType); String URL = cfgobj["apps_script_url"].as() + "?days=" + loc["days"].as(); @@ -1293,6 +1289,13 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa int calYOffset = loc["gridparam"][1].as(); int lineHeight = loc["gridparam"][5].as(); + uint16_t backgroundLight = getColor("lightgray"); + uint16_t backgroundDark = getColor("darkgray"); + if (imageParams.hwdata.bpp >= 3) { + backgroundLight = getColor("#BEFAFF"); + backgroundDark = getColor("#79FAFF"); + } + // drawString(spr, String(timeinfo.tm_mday), calWidth / 2, -calHeight/5, "Signika-SB.ttf", TC_DATUM, imageParams.highlightColor, calHeight * 1.2); for (int i = 0; i < calDays; i++) { @@ -1306,9 +1309,9 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa drawString(spr, String(languageDaysShort[dayInfo->tm_wday]) + " " + String(dayInfo->tm_mday), colStart + colWidth / 2, calTop, loc["gridparam"][3], TC_DATUM, TFT_BLACK); if (dayInfo->tm_wday == 0 || dayInfo->tm_wday == 6) { - spr.fillRect(colStart + 1, calTop + calYOffset, colWidth - 1, calHeight - 1, getColor("darkgray")); + spr.fillRect(colStart + 1, calTop + calYOffset, colWidth - 1, calHeight - 1, backgroundDark); } else { - spr.fillRect(colStart + 1, calTop + calYOffset, colWidth - 1, calHeight - 1, getColor("lightgray")); + spr.fillRect(colStart + 1, calTop + calYOffset, colWidth - 1, calHeight - 1, backgroundLight); } } @@ -1329,6 +1332,7 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa const time_t startdatetime = obj["start"]; const time_t enddatetime = obj["end"]; const bool isallday = obj["isallday"]; + const int calendarId = obj["calendar"]; if (!isallday) { localtime_r(&startdatetime, &timeinfo); @@ -1360,8 +1364,15 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa int16_t eventX = colLeft + fulldaystart * colWidth + 3; int16_t eventY = calTop + calYOffset + (line - 1) * lineHeight + 3; - spr.drawRect(eventX - 2, eventY - 3, colWidth * (fulldayend - fulldaystart) - 1, lineHeight + 1, TFT_BLACK); - spr.fillRect(eventX - 1, eventY - 2, colWidth * (fulldayend - fulldaystart) - 3, lineHeight - 1, TFT_WHITE); + uint16_t background = TFT_WHITE; + uint16_t border = TFT_BLACK; + if (imageParams.hwdata.bpp >= 3 && loc["colors1"].is() && loc["colors1"].size() > calendarId) { + background = getColor(loc["colors1"][calendarId]); + border = getColor(loc["colors2"][calendarId]); + Serial.println("cal " + String(calendarId) + ": " + String(loc["colors2"][calendarId])); + } + spr.fillRect(eventX - 1, eventY - 2, colWidth * (fulldayend - fulldaystart) - 3, lineHeight - 1, background); + spr.drawRect(eventX - 2, eventY - 3, colWidth * (fulldayend - fulldaystart) - 1, lineHeight + 1, border); drawTextBox(spr, eventtitle, eventX, eventY, colWidth * (fulldayend - fulldaystart) - 3, 15, loc["gridparam"][4], TFT_BLACK); block[i] = line; @@ -1393,6 +1404,7 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa const time_t startdatetime = obj["start"]; const time_t enddatetime = obj["end"]; const bool isallday = obj["isallday"]; + const int calendarId = obj["calendar"]; if (!isallday) { int fulldaystart = constrain((startdatetime - midnightEpoch) / (24 * 3600), 0, calDays); @@ -1431,12 +1443,22 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa block[i] = indent; int16_t eventX = colLeft + day * colWidth + (indent - 1) * 5; int16_t eventY = calTop + calYOffset + (starttime * hourHeight / 60); - spr.drawRect(eventX + 1, eventY, colWidth - 1, (duration * hourHeight / 60) + 1, TFT_BLACK); - spr.fillRect(eventX + 2, eventY + 1, colWidth - 3, (duration * hourHeight / 60) - 1, TFT_WHITE); + uint16_t background = TFT_WHITE; + uint16_t border = TFT_BLACK; + if (imageParams.hwdata.bpp >= 3 && loc["colors1"].is() && loc["colors1"].size() > calendarId) { + background = getColor(loc["colors1"][calendarId]); + border = getColor(loc["colors2"][calendarId]); + Serial.println("cal " + String(calendarId) + ": " + String(loc["colors2"][calendarId])); + } + spr.fillRect(eventX + 2, eventY + 1, colWidth - 3, (duration * hourHeight / 60) - 1, background); + spr.drawRect(eventX + 1, eventY, colWidth - 1, (duration * hourHeight / 60) + 1, border); eventX += 2; eventY += 2; if (day == fulldaystart) { eventtitle = formattedTimeString + " " + String(eventtitle); + drawTextBox(spr, formattedTimeString, eventX, eventY, colWidth - 1, (duration * hourHeight / 60) - 1, loc["gridparam"][4], TFT_BLACK, TFT_WHITE, 1); + eventX++; + eventY = calTop + calYOffset + (starttime * hourHeight / 60) + 2; } else { eventtitle = obj["title"].as(); } @@ -1613,12 +1635,13 @@ bool getDayAheadFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, minPrice = yAxisScale.min; uint16_t yAxisX = loc["yaxis"][1].as(); + uint16_t yAxisY = loc["yaxis"][3].as() | 9; uint16_t barBottom = loc["bars"][3].as(); for (double i = minPrice; i <= maxPrice; i += yAxisScale.step) { int y = mapDouble(i, minPrice, maxPrice, spr.height() - barBottom, spr.height() - barBottom - loc["bars"][2].as()); spr.drawLine(0, y, spr.width(), y, TFT_BLACK); - if (loc["yaxis"][0]) drawString(spr, String(int(i * units)), yAxisX, y - 9, loc["yaxis"][0], TL_DATUM, TFT_BLACK); + if (loc["yaxis"][0]) drawString(spr, String(int(i * units)), yAxisX, y - yAxisY, loc["yaxis"][0], TL_DATUM, TFT_BLACK); } uint16_t barwidth = loc["bars"][1].as() / n; @@ -2209,6 +2232,11 @@ void rotateBuffer(uint8_t rotation, uint8_t ¤tOrientation, TFT_eSprite &sp } } +TFT_eSprite sprDraw = TFT_eSprite(&tft); +bool spr_draw(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) { + sprDraw.pushImage(x, y, w, h, bitmap); + return 1; +} void drawElement(const JsonObject &element, TFT_eSprite &spr, imgParam &imageParams, uint8_t ¤tOrientation) { if (element.containsKey("text")) { const JsonArray &textArray = element["text"]; @@ -2218,13 +2246,15 @@ void drawElement(const JsonObject &element, TFT_eSprite &spr, imgParam &imagePar const uint16_t bgcolor = (bgcolorstr.length() > 0) ? getColor(bgcolorstr) : TFT_WHITE; drawString(spr, textArray[2], textArray[0].as(), textArray[1].as(), textArray[3], align, getColor(textArray[4]), size, bgcolor); } else if (element.containsKey("textbox")) { + // posx, posy, width, height, text, font, color, lineheight, align const JsonArray &textArray = element["textbox"]; float lineheight = textArray[7].as(); if (lineheight == 0) lineheight = 1; int16_t posx = textArray[0] | 0; int16_t posy = textArray[1] | 0; String text = textArray[4]; - drawTextBox(spr, text, posx, posy, textArray[2], textArray[3], textArray[5], getColor(textArray[6]), TFT_WHITE, lineheight); + const uint16_t align = textArray[8] | 0; + drawTextBox(spr, text, posx, posy, textArray[2], textArray[3], textArray[5], getColor(textArray[6]), TFT_WHITE, lineheight, align); } else if (element.containsKey("box")) { const JsonArray &boxArray = element["box"]; spr.fillRect(boxArray[0].as(), boxArray[1].as(), boxArray[2].as(), boxArray[3].as(), getColor(boxArray[4])); @@ -2240,6 +2270,33 @@ void drawElement(const JsonObject &element, TFT_eSprite &spr, imgParam &imagePar } else if (element.containsKey("circle")) { const JsonArray &circleArray = element["circle"]; spr.fillCircle(circleArray[0].as(), circleArray[1].as(), circleArray[2].as(), getColor(circleArray[3])); + } else if (element.containsKey("image")) { + const JsonArray &imgArray = element["image"]; + + TJpgDec.setSwapBytes(true); + TJpgDec.setJpgScale(1); + TJpgDec.setCallback(spr_draw); + uint16_t w = 0, h = 0; + String filename = imgArray[0]; + if (filename[0] != '/') { + filename = "/" + filename; + } + TJpgDec.getFsJpgSize(&w, &h, filename, *contentFS); + if (w == 0 && h == 0) { + wsErr("invalid jpg"); + return; + } + Serial.println("jpeg conversion " + String(w) + "x" + String(h)); + sprDraw.setColorDepth(16); + sprDraw.createSprite(w, h); + if (sprDraw.getPointer() == nullptr) { + wsErr("Failed to create sprite in contentmanager"); + } else { + TJpgDec.drawFsJpg(0, 0, filename, *contentFS); + sprDraw.pushToSprite(&spr, imgArray[1].as(), imgArray[2].as()); + sprDraw.deleteSprite(); + } + } else if (element.containsKey("rotate")) { uint8_t rotation = element["rotate"].as(); rotateBuffer(rotation, currentOrientation, spr, imageParams); @@ -2379,7 +2436,7 @@ void prepareConfigFile(const uint8_t *dst, const JsonObject &config) { void getTemplate(JsonDocument &json, const uint8_t id, const uint8_t hwtype) { StaticJsonDocument<80> filter; - DynamicJsonDocument doc(2048); + DynamicJsonDocument doc(4096); const String idstr = String(id); constexpr const char *templateKey = "template"; diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp index 46d1a45d..2383bf3b 100644 --- a/ESP32_AP-Flasher/src/makeimage.cpp +++ b/ESP32_AP-Flasher/src/makeimage.cpp @@ -75,14 +75,43 @@ struct Error { int32_t b; }; -uint32_t colorDistance(Color &c1, Color &c2, Error &e1) { - e1.r = constrain(e1.r, -255, 255); - e1.g = constrain(e1.g, -255, 255); - e1.b = constrain(e1.b, -255, 255); +uint32_t colorDistance(const Color &c1, const Color &c2, const Error &e1) { int32_t r_diff = c1.r + e1.r - c2.r; int32_t g_diff = c1.g + e1.g - c2.g; int32_t b_diff = c1.b + e1.b - c2.b; - return 3 * r_diff * r_diff + 6 * g_diff * g_diff + b_diff * b_diff; + if (abs(c1.r - c1.g) < 20 && abs(c1.b - c1.g) < 20) { + if (abs(c2.r - c2.g) > 20 || abs(c2.b - c2.g) > 20) return 4294967295; // don't select color pixels on black and white + } + return 3 * r_diff * r_diff + 5.47 * g_diff * g_diff + 1.53 * b_diff * b_diff; +} + +std::tuple findClosestColors(const Color &pixel, const std::vector &palette) { + int closestIndex = -1, secondClosestIndex = -1; + float closestDist = std::numeric_limits::max(); + float secondClosestDist = std::numeric_limits::max(); + for (size_t i = 0; i < palette.size(); ++i) { + float dist = colorDistance(pixel, palette[i], (Error){0, 0, 0}); + if (dist < closestDist) { + secondClosestIndex = closestIndex; + secondClosestDist = closestDist; + closestIndex = i; + closestDist = dist; + } else if (dist < secondClosestDist) { + secondClosestIndex = i; + secondClosestDist = dist; + } + } + if (closestIndex != -1 && secondClosestIndex != -1) { + auto rgbValue = [](const Color &color) { + return (color.r << 16) | (color.g << 8) | color.b; + }; + + if (rgbValue(palette[secondClosestIndex]) > rgbValue(palette[closestIndex])) { + std::swap(closestIndex, secondClosestIndex); + std::swap(closestDist, secondClosestDist); + } + } + return { closestIndex, secondClosestIndex, closestDist, secondClosestDist}; } void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t buffer_size, bool is_red) { @@ -112,11 +141,6 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t Error *error_bufferold = new Error[bufw + 4]; Error *error_buffernew = new Error[bufw + 4]; - const uint8_t ditherMatrix[4][4] = { - {0, 9, 2, 10}, - {12, 5, 14, 6}, - {3, 11, 1, 8}, - {15, 7, 13, 4}}; size_t bitOffset = 0; memset(error_bufferold, 0, bufw * sizeof(Error)); @@ -138,23 +162,36 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t break; } + int best_color_index = 0; if (imageParams.dither == 2) { - // Ordered dithering - uint8_t ditherValue = ditherMatrix[y % 4][x % 4] << (imageParams.bpp >= 3 ? 2 : 4); - error_bufferold[x].r = ditherValue - (imageParams.bpp >= 3 ? 30 : 120); // * 256 / 16 - 128 + 8 - error_bufferold[x].g = ditherValue - (imageParams.bpp >= 3 ? 30 : 120); - error_bufferold[x].b = ditherValue - (imageParams.bpp >= 3 ? 30 : 120); + // special ordered dithering + auto [c1Index, c2Index, distC1, distC2] = findClosestColors(color, palette); + Color c1 = palette[c1Index]; + Color c2 = palette[c2Index]; + float weight = distC1 / (distC1 + distC2); + if (weight <= 0.03) { + best_color_index = c1Index; + } else if (weight < 0.30) { + best_color_index = ((y % 2 && ((y / 2 + x) % 2)) ? c2Index : c1Index); + } else if (weight < 0.70) { + best_color_index = ((x + y) % 2 ? c2Index : c1Index); + } else if (weight < 0.97) { + best_color_index = ((y % 2 && ((y / 2 + x) % 2)) % 2 ? c1Index : c2Index); + } else { + best_color_index = c2Index; + } } - int best_color_index = 0; - uint32_t best_color_distance = colorDistance(color, palette[0], error_bufferold[x]); + if (imageParams.dither == 1 || imageParams.dither == 0) { + uint32_t best_color_distance = colorDistance(color, palette[0], error_bufferold[x]); - for (int i = 1; i < num_colors; i++) { - if (best_color_distance == 0) break; - uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]); - if (distance < best_color_distance) { - best_color_distance = distance; - best_color_index = i; + for (int i = 1; i < num_colors; i++) { + if (best_color_distance == 0) break; + uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]); + if (distance < best_color_distance) { + best_color_distance = distance; + best_color_index = i; + } } } @@ -201,34 +238,44 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t color.g + error_bufferold[x].g - palette[best_color_index].g, color.b + error_bufferold[x].b - palette[best_color_index].b}; - error_buffernew[x].r += error.r >> 2; - error_buffernew[x].g += error.g >> 2; - error_buffernew[x].b += error.b >> 2; + float scaling_factor = 255.0f / std::max(std::abs(error.r), std::max(std::abs(error.g), std::abs(error.b))); + if (scaling_factor < 1.0f) { + error.r *= scaling_factor; + error.g *= scaling_factor; + error.b *= scaling_factor; + } + + error_buffernew[x].r += error.r / 4; + error_buffernew[x].g += error.g / 4; + error_buffernew[x].b += error.b / 4; + if (x > 0) { - error_buffernew[x - 1].r += error.r >> 3; - error_buffernew[x - 1].g += error.g >> 3; - error_buffernew[x - 1].b += error.b >> 3; + error_buffernew[x - 1].r += error.r / 8; + error_buffernew[x - 1].g += error.g / 8; + error_buffernew[x - 1].b += error.b / 8; } + if (x > 1) { - error_buffernew[x - 2].r += error.r >> 4; - error_buffernew[x - 2].g += error.g >> 4; - error_buffernew[x - 2].b += error.b >> 4; + error_buffernew[x - 2].r += error.r / 16; + error_buffernew[x - 2].g += error.g / 16; + error_buffernew[x - 2].b += error.b / 16; } - error_buffernew[x + 1].r += error.r >> 3; - error_buffernew[x + 1].g += error.g >> 3; - error_buffernew[x + 1].b += error.b >> 3; - error_bufferold[x + 1].r += error.r >> 2; - error_bufferold[x + 1].g += error.g >> 2; - error_bufferold[x + 1].b += error.b >> 2; + error_buffernew[x + 1].r += error.r / 8; + error_buffernew[x + 1].g += error.g / 8; + error_buffernew[x + 1].b += error.b / 8; - error_buffernew[x + 2].r += error.r >> 4; - error_buffernew[x + 2].g += error.g >> 4; - error_buffernew[x + 2].b += error.b >> 4; + error_bufferold[x + 1].r += error.r / 4; + error_bufferold[x + 1].g += error.g / 4; + error_bufferold[x + 1].b += error.b / 4; - error_bufferold[x + 2].r += error.r >> 3; - error_bufferold[x + 2].g += error.g >> 3; - error_bufferold[x + 2].b += error.b >> 3; + error_buffernew[x + 2].r += error.r / 16; + error_buffernew[x + 2].g += error.g / 16; + error_buffernew[x + 2].b += error.b / 16; + + error_bufferold[x + 2].r += error.r / 8; + error_bufferold[x + 2].g += error.g / 8; + error_bufferold[x + 2].b += error.b / 8; } } memcpy(error_bufferold, error_buffernew, bufw * sizeof(Error)); diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp index ffade1f4..c8131b60 100644 --- a/ESP32_AP-Flasher/src/newproto.cpp +++ b/ESP32_AP-Flasher/src/newproto.cpp @@ -275,7 +275,8 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { case DATATYPE_IMG_RAW_1BPP: case DATATYPE_IMG_RAW_2BPP: case DATATYPE_IMG_G5: - case DATATYPE_IMG_RAW_3BPP: { + case DATATYPE_IMG_RAW_3BPP: + case DATATYPE_IMG_RAW_4BPP: { char hexmac[17]; mac2hex(pending->targetMac, hexmac); String filename = "/current/" + String(hexmac) + "_" + String(millis() % 1000000) + ".pending"; @@ -338,8 +339,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { break; } case DATATYPE_NFC_RAW_CONTENT: - case DATATYPE_NFC_URL_DIRECT: - case DATATYPE_CUSTOM_LUT_OTA: { + case DATATYPE_NFC_URL_DIRECT: { char hexmac[17]; mac2hex(pending->targetMac, hexmac); char dataUrl[80]; @@ -348,7 +348,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { snprintf(dataUrl, sizeof(dataUrl), "http://%s/getdata?mac=%s&md5=%s", remoteIP.toString().c_str(), hexmac, md5); wsLog("GET " + String(dataUrl)); HTTPClient http; - logLine("http DATATYPE_CUSTOM_LUT_OTA " + String(dataUrl)); + logLine("http DATATYPE_NFC_* " + String(dataUrl)); http.begin(dataUrl); int httpCode = http.GET(); if (httpCode == 200) { @@ -447,9 +447,11 @@ void processXferComplete(struct espXferComplete* xfc, bool local) { contentFS->remove(dst_path); } if (contentFS->exists(queueItem->filename)) { - if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_3BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_G5 || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_ZLIB)) { + uint8_t dataType = queueItem->pendingdata.availdatainfo.dataType; + if (config.preview && dataType != DATATYPE_FW_UPDATE && dataType != DATATYPE_NOUPDATE) { contentFS->rename(queueItem->filename, String(dst_path)); - } else { + } + else { if (queueItem->pendingdata.availdatainfo.dataType != DATATYPE_FW_UPDATE) contentFS->remove(queueItem->filename); } } @@ -968,7 +970,8 @@ bool queueDataAvail(struct pendingData* pending, bool local) { } newPending.len = taginfo->len; - if ((pending->availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_3BPP || pending->availdatainfo.dataType == DATATYPE_IMG_ZLIB || pending->availdatainfo.dataType == DATATYPE_IMG_G5) && (pending->availdatainfo.dataTypeArgument & 0xF8) == 0x00) { + uint8_t dataType = pending->availdatainfo.dataType; + if (dataType != DATATYPE_FW_UPDATE && dataType != DATATYPE_NOUPDATE && pending->availdatainfo.dataTypeArgument & 0xF8 == 0x00) { // in case of an image (no preload), remove already queued images pendingQueue.erase(std::remove_if(pendingQueue.begin(), pendingQueue.end(), [pending](const PendingItem& item) { diff --git a/ESP32_AP-Flasher/wwwroot/edit.html b/ESP32_AP-Flasher/wwwroot/edit.html index 2c29dbcf..ea76ad47 100644 --- a/ESP32_AP-Flasher/wwwroot/edit.html +++ b/ESP32_AP-Flasher/wwwroot/edit.html @@ -462,26 +462,23 @@ imageData.data[i * 4 + 2] = is16Bit ? (rgb & 0x1F) << 3 : ((rgb & 0x03) << 6) * 1.3; imageData.data[i * 4 + 3] = 255; } - } else if (tagTypes[hwtype].bpp == 3) { + } else if ([3, 4].includes(tagTypes[hwtype].bpp)) { + const bpp = tagTypes[hwtype].bpp; const colorTable = tagTypes[hwtype].colortable; - let pixelIndex = 0; - for (let i = 0; i < data.length; i += 3) { - for (let j = 0; j < 8; j++) { - let bitPos = j * 3; - let bytePos = Math.floor(bitPos / 8); - let bitOffset = bitPos % 8; - let pixelValue = (data[i + bytePos] >> (5 - bitOffset)) & 0x07; - if (bitOffset > 5) { - pixelValue = ((data[i + bytePos] & (0xFF >> bitOffset)) << (bitOffset - 5)) | - (data[i + bytePos + 1] >> (13 - bitOffset)); - } - imageData.data[pixelIndex * 4] = colorTable[pixelValue][0]; - imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1]; - imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2]; - imageData.data[pixelIndex * 4 + 3] = 255; - pixelIndex++; - } + let bitOffset = 0; + + while (bitOffset < data.length * 8) { + let byteIndex = bitOffset >> 3; + let startBit = bitOffset & 7; + let pixelValue = (data[byteIndex] << 8 | data[byteIndex + 1] || 0) >> (16 - bpp - startBit) & ((1 << bpp) - 1); + let color = colorTable[pixelValue]; + imageData.data[pixelIndex * 4] = color[0]; + imageData.data[pixelIndex * 4 + 1] = color[1]; + imageData.data[pixelIndex * 4 + 2] = color[2]; + imageData.data[pixelIndex * 4 + 3] = 255; + pixelIndex++; + bitOffset += bpp; } } else { diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js index ce2b9aae..ad708568 100644 --- a/ESP32_AP-Flasher/wwwroot/main.js +++ b/ESP32_AP-Flasher/wwwroot/main.js @@ -133,6 +133,7 @@ function initTabs() { tabLinks.forEach(link => { link.classList.remove("active"); }); + if (targetId == "logtab") document.getElementById(targetId).scrollTop = 0; document.getElementById(targetId).style.display = "block"; this.classList.add("active"); }); @@ -1253,28 +1254,24 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) { imageData.data[i * 4 + 3] = 255; } - } else if (tagTypes[hwtype].bpp == 3) { + } else if ([3, 4].includes(tagTypes[hwtype].bpp)) { + const bpp = tagTypes[hwtype].bpp; const colorTable = tagTypes[hwtype].colortable; - let pixelIndex = 0; - for (let i = 0; i < data.length; i += 3) { - for (let j = 0; j < 8; j++) { - let bitPos = j * 3; - let bytePos = Math.floor(bitPos / 8); - let bitOffset = bitPos % 8; - let pixelValue = (data[i + bytePos] >> (5 - bitOffset)) & 0x07; - if (bitOffset > 5) { - pixelValue = ((data[i + bytePos] & (0xFF >> bitOffset)) << (bitOffset - 5)) | - (data[i + bytePos + 1] >> (13 - bitOffset)); - } - imageData.data[pixelIndex * 4] = colorTable[pixelValue][0]; - imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1]; - imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2]; - imageData.data[pixelIndex * 4 + 3] = 255; - pixelIndex++; - } - } + let bitOffset = 0; + while (bitOffset < data.length * 8) { + let byteIndex = bitOffset >> 3; + let startBit = bitOffset & 7; + let pixelValue = (data[byteIndex] << 8 | data[byteIndex + 1] || 0) >> (16 - bpp - startBit) & ((1 << bpp) - 1); + let color = colorTable[pixelValue]; + imageData.data[pixelIndex * 4] = color[0]; + imageData.data[pixelIndex * 4 + 1] = color[1]; + imageData.data[pixelIndex * 4 + 2] = color[2]; + imageData.data[pixelIndex * 4 + 3] = 255; + pixelIndex++; + bitOffset += bpp; + } } else { const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0; @@ -1527,7 +1524,7 @@ async function getTagtype(hwtype) { height: parseInt(jsonData.height), bpp: parseInt(jsonData.bpp), rotatebuffer: jsonData.rotatebuffer, - colortable: Object.values(jsonData.colortable), + colortable: Object.values(jsonData.perceptual ?? jsonData.colortable), contentids: Object.values(jsonData.contentids ?? []), options: Object.values(jsonData.options ?? []), zlib: parseInt(jsonData.zlib_compression || "0", 16), @@ -1571,6 +1568,7 @@ function dropUpload() { dropZone.addEventListener('drop', (event) => { event.preventDefault(); + const shiftKey = event.shiftKey; const file = event.dataTransfer.files[0]; const tagCard = event.target.closest('.tagcard'); const mac = tagCard.dataset.mac; @@ -1604,6 +1602,7 @@ function dropUpload() { canvas.toBlob(async (blob) => { const formData = new FormData(); formData.append('mac', mac); + if (shiftKey) formData.append('dither', '2'); formData.append('file', blob, 'image.jpg'); try { @@ -1906,7 +1905,7 @@ function openPreview(mac, w, h) { previewWindow.document.body.style.backgroundColor = "#dddddd"; previewWindow.document.body.style.margin = "15px"; previewWindow.document.body.style.overflow = "hidden"; - previewWindow.document.body.innerHTML = ``; + previewWindow.document.body.innerHTML = ``; showPreview(previewWindow, element); diff --git a/resources/tagtypes/06.json b/resources/tagtypes/06.json index 7dcfc367..24184fd3 100644 --- a/resources/tagtypes/06.json +++ b/resources/tagtypes/06.json @@ -1,5 +1,5 @@ { - "version": 3, + "version": 4, "name": "Opticon 2.2\"", "width": 250, "height": 128, @@ -11,6 +11,11 @@ "red": [ 255, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "g5_compression": "29", "shortlut": 0, "options": [ "led" ], diff --git a/resources/tagtypes/07.json b/resources/tagtypes/07.json index 5e6cefb0..780c18f3 100644 --- a/resources/tagtypes/07.json +++ b/resources/tagtypes/07.json @@ -1,5 +1,5 @@ { - "version": 3, + "version": 4, "name": "Opticon 2.9\"", "width": 296, "height": 128, @@ -11,6 +11,11 @@ "red": [ 255, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "g5_compression": "29", "shortlut": 2, "options": [ "led" ], diff --git a/resources/tagtypes/36.json b/resources/tagtypes/36.json index d7d552db..5472f224 100644 --- a/resources/tagtypes/36.json +++ b/resources/tagtypes/36.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "name": "M3 7.5\"", "width": 800, "height": 480, @@ -51,7 +51,13 @@ "rotate": 0, "mode": 1, "days": 7, - "gridparam": [ 3, 17, 30, "calibrib16.vlw", "tahoma9.vlw", 14 ] + "gridparam": [ 3, 17, 30, "calibrib16.vlw", "tahoma11.vlw", 14 ] + }, + "27": { + "bars": [ 40, 690, 380, 25, 50 ], + "time": [ "fonts/bahnschrift20" ], + "yaxis": [ "fonts/bahnschrift20", 5, 12, 14 ], + "head": [ "fonts/calibrib50.vlw" ] } } } diff --git a/resources/tagtypes/3C.json b/resources/tagtypes/3C.json index a29b594e..fea68662 100644 --- a/resources/tagtypes/3C.json +++ b/resources/tagtypes/3C.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "name": "M3 4.2\" BWY", "width": 400, "height": 300, @@ -10,6 +10,11 @@ "black": [ 0, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "shortlut": 0, "zlib_compression": "27", "options": [ "button", "led" ], diff --git a/resources/tagtypes/60.json b/resources/tagtypes/60.json index d76f9dc6..361d78fd 100644 --- a/resources/tagtypes/60.json +++ b/resources/tagtypes/60.json @@ -1,5 +1,5 @@ { - "version": 4, + "version": 5, "name": "HS BWY 3.5\"", "width": 384, "height": 184, @@ -10,6 +10,11 @@ "black": [ 0, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "highlight_color": 3, "shortlut": 0, "zlib_compression": "27", diff --git a/resources/tagtypes/66.json b/resources/tagtypes/66.json index 2ff55849..40a9d0dd 100644 --- a/resources/tagtypes/66.json +++ b/resources/tagtypes/66.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "name": "HS BWY 7,5\"", "width": 800, "height": 480, @@ -10,6 +10,11 @@ "black": [ 0, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "highlight_color": 3, "shortlut": 0, "zlib_compression": "27", diff --git a/resources/tagtypes/67.json b/resources/tagtypes/67.json index 522764e4..811625de 100644 --- a/resources/tagtypes/67.json +++ b/resources/tagtypes/67.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "name": "HS 2.00\" BWY", "width": 152, "height": 200, @@ -10,6 +10,11 @@ "black": [ 0, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "highlight_color": 3, "shortlut": 2, "zlib_compression": "27", diff --git a/resources/tagtypes/68.json b/resources/tagtypes/68.json index 52e4617d..f5eb7068 100644 --- a/resources/tagtypes/68.json +++ b/resources/tagtypes/68.json @@ -1,5 +1,5 @@ { - "version": 3, + "version": 4, "name": "HS BWY 3.46\"", "width": 480, "height": 176, @@ -10,6 +10,11 @@ "black": [ 0, 0, 0 ], "yellow": [ 255, 255, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ] + }, "highlight_color": 3, "shortlut": 0, "zlib_compression": "27", diff --git a/resources/tagtypes/C0.json b/resources/tagtypes/C0.json index 6080e2fc..26eb477c 100644 --- a/resources/tagtypes/C0.json +++ b/resources/tagtypes/C0.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "name": "BWRY example", "width": 360, "height": 184, @@ -11,6 +11,12 @@ "yellow": [ 255, 255, 0 ], "red": [ 255, 0, 0 ] }, + "perceptual": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "yellow": [ 200, 200, 0 ], + "red": [ 255, 0, 0 ] + }, "shortlut": 0, "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 26 ], "usetemplate": 1 diff --git a/resources/tagtypes/C1.json b/resources/tagtypes/C1.json index fd172f6a..12815d33 100644 --- a/resources/tagtypes/C1.json +++ b/resources/tagtypes/C1.json @@ -14,8 +14,27 @@ "yellow": [ 255, 255, 0 ], "orange": [ 255, 128, 0 ] }, + "perceptual": { + "black": [ 0, 0, 0 ], + "white": [ 248, 248, 248 ], + "green": [ 0, 200, 0 ], + "blue": [ 0, 0, 200 ], + "red": [ 200, 0, 0 ], + "yellow": [ 240, 240, 0 ], + "orange": [ 200, 128, 0 ] + }, "highlight_color": 8, "shortlut": 0, "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 26, 27 ], - "usetemplate": 5 + "usetemplate": 5, + "template": { + "11": { + "rotate": 0, + "mode": 1, + "days": 7, + "gridparam": [ 3, 17, 30, "calibrib16.vlw", "tahoma9.vlw", 14 ], + "colors1": [ "#4EFFFF", "#E6FF3D", "#FF7DF0", "#5491FF", "#FF83FF", "#FFFF00", "#84FE39", "#84FE39" ], + "colors2": [ "green", "yellow", "red", "blue", "blue", "orange", "black", "green" ] + } + } } diff --git a/resources/tagtypes/C2.json b/resources/tagtypes/C2.json index 8c5a6284..552e3310 100644 --- a/resources/tagtypes/C2.json +++ b/resources/tagtypes/C2.json @@ -10,11 +10,31 @@ "white": [ 255, 255, 255 ], "yellow": [ 255, 255, 0 ], "red": [ 255, 0, 0 ], + "orange": [ 255, 128, 0 ], "blue": [ 0, 0, 255 ], "green": [ 0, 255, 0 ] }, + "perceptual": { + "black": [ 0, 0, 0 ], + "white": [ 248, 248, 248 ], + "yellow": [ 240, 240, 0 ], + "red": [ 200, 0, 0 ], + "orange": [ 200, 64, 0 ], + "blue": [ 0, 0, 200 ], + "green": [ 0, 200, 0 ] + }, "highlight_color": 8, "shortlut": 0, "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 26, 27 ], - "usetemplate": 54 + "usetemplate": 54, + "template": { + "11": { + "rotate": 0, + "mode": 1, + "days": 7, + "gridparam": [ 3, 17, 30, "calibrib16.vlw", "tahoma11.vlw", 14 ], + "colors1": [ "#4EFFFF", "#E6FF3D", "#FF7DF0", "#5491FF", "#FF83FF", "#FFFF00", "#84FE39", "#84FE39" ], + "colors2": [ "green", "yellow", "red", "blue", "blue", "orange", "black", "green" ] + } + } }