dithering/raw previews/css

- fixed preview images on webpage (raw buffer is rendered client side)
- added responsive css, webpage works on mobile now
- content is now rendered on 4 bit sprite using indexed color
- added floyd steinberg dithering
This commit is contained in:
Nic Limper
2023-03-16 22:16:42 +01:00
parent 3a80c3f5e9
commit ef09cf4999
5 changed files with 220 additions and 73 deletions

View File

@@ -60,7 +60,7 @@
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><img class="tagimg" src=""></div>
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>

View File

@@ -1,4 +1,4 @@
*{
* {
margin:0;
padding:0;
border:0;
@@ -40,7 +40,6 @@ label {
height: 50px;
text-indent: 50px;
overflow:hidden;
background-size: 50px 50px;
font-size: 2.5em;
color: white;
}
@@ -84,9 +83,20 @@ label {
flex: 1;
}
textarea,
input.text,
input[type="text"],
input[type="button"],
input[type="submit"],
button {
-webkit-appearance: none;
border-radius: 0;
}
input {
border: solid 1px #666666;
padding: 4px;
-webkit-border-radius: 0px;
}
input[type=button] {
@@ -99,6 +109,7 @@ input[type=button]:hover {
}
select {
padding: 4px;
-webkit-border-radius: 0px;
}
#configbox {
@@ -179,7 +190,7 @@ select {
.tagcard {
width: 225px;
position: relative;
height: 170px;
min-height: 170px;
margin: 5px;
padding: 5px;
background-color: #dddddd;
@@ -203,7 +214,7 @@ select {
float: right;
}
.currimg img {
.currimg img, .currimg canvas {
max-width: 50px;
}
@@ -326,3 +337,46 @@ ul.messages li.new {
50% { background-color: lightblue;}
100% { background-color: lightgray;}
}
@media screen and (max-width: 480px) {
/* styles for mobile devices in portrait mode */
body {
font-size: 14px;
}
.tagcard {
width: 100%;
min-height: 200px;
}
.logbox #sysinfo {
float: none;
display: block;
}
#configbox {
top: 0px;
left: 0px;
width: 100%;
}
.currimg {
float: none;
position: absolute;
right: 5px;
}
.currimg img,
.currimg canvas {
max-width: 60px;
}
.logo {
padding-top: 10px;
text-indent: 0px;
font-size: 1.8em;
text-align: center;
}
}

View File

@@ -10,6 +10,8 @@ const WAKEUP_REASON_WDT_RESET = 0xFE;
const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast","RSS feed"];
const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"];
const displaySizeLookup = { 0: [152, 152], 1: [128, 296], 2: [400, 300] };
const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [255, 0, 0] };
const contentModeOptions = [];
contentModeOptions[0] = ["filename","timetolive"];
contentModeOptions[1] = [];
@@ -63,6 +65,7 @@ function connect() {
if (msg.sys) {
$('#sysinfo').innerHTML = 'free heap: ' + msg.sys.heap + ' bytes &#x2507; db size: ' + msg.sys.dbsize + ' bytes &#x2507; db record count: ' + msg.sys.recordcount + ' &#x2507; littlefs free: ' + msg.sys.littlefsfree + ' bytes';
servertimediff = (Date.now() / 1000) - msg.sys.currtime;
console.log("timediff: " + servertimediff);
}
});
@@ -82,13 +85,10 @@ function processTags(tagArray) {
div = $('#tagtemplate').cloneNode(true);
div.setAttribute('id', 'tag'+tagmac);
div.dataset.mac = tagmac;
div.dataset.hwtype = -1;
$('#taglist').appendChild(div);
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
var img = $('#tag' + tagmac + ' .tagimg');
img.addEventListener('error', function handleError() {
img.style.display = 'none';
});
}
div.style.display = 'block';
@@ -97,10 +97,9 @@ function processTags(tagArray) {
if (!alias) alias = tagmac;
$('#tag' + tagmac + ' .alias').innerHTML = alias;
if (div.dataset.hash != element.hash) loadImage(tagmac, '/current/' + tagmac + '.bmp?' + (new Date()).getTime());
$('#tag' + tagmac + ' .contentmode').innerHTML = contentModes[element.contentMode];
if (element.RSSI) {
div.dataset.hwtype = element.hwType;
$('#tag' + tagmac + ' .model').innerHTML = models[element.hwType];
$('#tag' + tagmac + ' .rssi').innerHTML = element.RSSI;
$('#tag' + tagmac + ' .lqi').innerHTML = element.LQI;
@@ -112,6 +111,11 @@ function processTags(tagArray) {
$('#tag' + tagmac + ' .received').style.opacity = "0";
}
if (div.dataset.hash != element.hash && div.dataset.hwtype > -1) {
loadImage(tagmac, '/current/' + tagmac + '.raw?' + (new Date()).getTime());
div.dataset.hash = element.hash;
}
if (element.nextupdate > 1672531200 && element.nextupdate!=3216153600) {
var date = new Date(element.nextupdate * 1000);
var options = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
@@ -130,7 +134,6 @@ function processTags(tagArray) {
$('#tag' + tagmac + ' .lastseen').style.color = "black";
div.classList.remove("tagpending");
div.dataset.lastseen = element.lastseen;
div.dataset.hash = element.hash;
div.dataset.wakeupreason = element.wakeupReason;
$('#tag' + tagmac + ' .warningicon').style.display = 'none';
$('#tag' + tagmac).style.background = "inherit";
@@ -174,9 +177,9 @@ function updatecards() {
let tagmac = item.dataset.mac;
if (item.dataset.lastseen && item.dataset.lastseen > 1672531200) {
let idletime = (Date.now() / 1000) + servertimediff - item.dataset.lastseen;
let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen;
$('#tag' + tagmac + ' .lastseen').innerHTML = "<span>last seen</span>"+displayTime(Math.floor(idletime))+" ago";
if ((Date.now() / 1000) + servertimediff - 300 > item.dataset.nextcheckin) {
if ((Date.now() / 1000) - servertimediff - 300 > item.dataset.nextcheckin) {
$('#tag' + tagmac + ' .warningicon').style.display='inline-block';
$('#tag' + tagmac).classList.remove("tagpending")
$('#tag' + tagmac).style.background = '#ffffcc';
@@ -190,7 +193,7 @@ function updatecards() {
}
if (item.dataset.nextcheckin > 1672531200 && parseInt(item.dataset.wakeupreason)==0) {
let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) + servertimediff);
let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) - servertimediff);
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<span>expected checkin</span>" + displayTime(Math.floor(nextcheckin));
}
})
@@ -338,16 +341,36 @@ function processQueue() {
}
isProcessing = true;
const { id, imageSrc } = imageQueue.shift();
const image = $('#tag' + id + ' .tagimg');
image.onload = function () {
image.style.display = 'block';
processQueue();
}
image.onerror = function () {
image.style.display = 'none';
processQueue();
};
image.src = imageSrc;
const canvas = $('#tag' + id + ' .tagimg');
const hwtype = $('#tag' + id).dataset.hwtype;
fetch(imageSrc)
.then(response => response.arrayBuffer())
.then(buffer => {
[canvas.width, canvas.height] = displaySizeLookup[hwtype] || [0,0];
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(canvas.width, canvas.height);
const data = new Uint8ClampedArray(buffer);
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;
var pixelValue = 0;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < 8; j++) {
const pixelIndex = i * 8 + j;
if (offsetRed) {
pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0) | (((data[i + offsetRed] & (1 << (7 - j))) ? 1 : 0) << 1);
} else {
pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0);
}
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;
}
}
ctx.putImageData(imageData, 0, 0);
processQueue();
});
}
function displayTime(seconds) {

View File

@@ -14,6 +14,10 @@
#include "makeimage.h"
#include "web.h"
#define PAL_BLACK 0
#define PAL_WHITE 9
#define PAL_RED 2
enum contentModes {
Image,
Today,
@@ -27,7 +31,6 @@ enum contentModes {
RSSFeed,
};
void contentRunner() {
time_t now;
time(&now);
@@ -78,10 +81,9 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
switch (taginfo->contentMode) {
case Image:
filename = cfgobj["filename"].as<String>();
//***FIXME... convert jpg to raw
if (filename && filename !="null" && !cfgobj["#fetched"].as<bool>()) {
if (prepareDataAvail(&filename, DATATYPE_IMG_BMP, mac, cfgobj["timetolive"].as<int>())) {
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>())) {
cfgobj["#fetched"] = true;
} else {
wsErr("Error accessing " + filename);
@@ -189,21 +191,21 @@ bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin) {
}
void drawString(TFT_eSprite &spr, String content, uint16_t posx, uint16_t posy, String font, byte align,uint16_t color) {
// drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,TFT_RED);
// drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,PAL_RED);
spr.setTextDatum(align);
if (font != "") spr.loadFont(font, LittleFS);
spr.setTextColor(color, TFT_WHITE);
spr.setTextColor(color, PAL_WHITE);
spr.drawString(content, posx, posy);
if (font != "") spr.unloadFont();
}
void initSprite(TFT_eSprite &spr, int w, int h) {
spr.setColorDepth(8);
spr.setColorDepth(4); // 4 bits per pixel, uses indexed color
spr.createSprite(w, h);
if (spr.getPointer() == nullptr) {
wsErr("Failed to create sprite");
}
spr.fillSprite(TFT_WHITE);
spr.fillSprite(PAL_WHITE);
}
void drawDate(String &filename, tagRecord *&taginfo) {
@@ -225,7 +227,7 @@ void drawDate(String &filename, tagRecord *&taginfo) {
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr,296,128);
drawString(spr, Dag[timeinfo.tm_wday], 296 / 2, 10, "fonts/calibrib62", TC_DATUM, TFT_RED);
drawString(spr, Dag[timeinfo.tm_wday], 296 / 2, 10, "fonts/calibrib62", TC_DATUM, PAL_RED);
drawString(spr, String(timeinfo.tm_mday) + " " + Maand[timeinfo.tm_mon], 296 / 2, 73, "fonts/calibrib50", TC_DATUM);
} else if (taginfo->hwType == SOLUM_154_033) {
@@ -233,7 +235,7 @@ void drawDate(String &filename, tagRecord *&taginfo) {
initSprite(spr, 152, 152);
drawString(spr, Dag[timeinfo.tm_wday], 152 / 2, 10, "fonts/calibrib30", TC_DATUM);
drawString(spr, String(Maand[timeinfo.tm_mon]), 152 / 2, 120, "fonts/calibrib30", TC_DATUM);
drawString(spr, String(timeinfo.tm_mday), 152 / 2, 42, "fonts/numbers2-1", TC_DATUM, TFT_RED);
drawString(spr, String(timeinfo.tm_mday), 152 / 2, 42, "fonts/numbers2-1", TC_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
@@ -251,9 +253,9 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord
initSprite(spr, 296, 128);
spr.setTextDatum(MC_DATUM);
if (count > thresholdred) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
String font = "fonts/numbers1-2";
if (count > 999) font = "fonts/numbers2-2";
@@ -267,15 +269,15 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord
initSprite(spr, 152, 152);
spr.setTextDatum(MC_DATUM);
if (count > thresholdred) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
String font = "fonts/numbers1-1";
if (count > 99) font = "fonts/numbers2-1";
if (count > 999) font = "fonts/numbers3-1";
spr.loadFont(font, LittleFS);
spr.drawString(String(count), 152 / 2, 152 / 2 + 10);
spr.drawString(String(count), 152 / 2, 152 / 2 + 7);
spr.unloadFont();
}
@@ -348,17 +350,17 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) {
initSprite(spr, 296, 128);
drawString(spr, location, 5, 5, "fonts/bahnschrift30");
drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK));
drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
char tmpOutput[5];
dtostrf(temperature, 2, 1, tmpOutput);
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons70", LittleFS);
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setCursor(185, 32);
@@ -366,11 +368,11 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) {
spr.unloadFont();
spr.loadFont("fonts/weathericons30", LittleFS);
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(235, -3);
spr.printToSprite(windDirectionIcon(winddirection));
if (weathercode > 10) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
spr.setCursor(190, 0);
spr.printToSprite("\uf084");
}
@@ -382,32 +384,32 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) {
spr.setTextDatum(TL_DATUM);
spr.setTextFont(2);
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.drawString(location, 10, 130);
char tmpOutput[5];
dtostrf(temperature, 2, 1, tmpOutput);
drawString(spr, String(tmpOutput), 10, 10, "fonts/bahnschrift30", TL_DATUM, (temperature < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(tmpOutput), 10, 10, "fonts/bahnschrift30", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons78", LittleFS);
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setCursor(30, 33);
spr.printToSprite(weatherIcons[weathercode]);
spr.unloadFont();
drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK));
drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons30", LittleFS);
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(100, -2);
spr.printToSprite(windDirectionIcon(winddirection));
if (weathercode > 10) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
spr.setCursor(115, 110);
spr.printToSprite("\uf084");
}
@@ -475,28 +477,28 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) {
initSprite(spr, 296, 128);
spr.setTextFont(2);
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.drawString(location, 5, 0);
for (uint8_t dag = 0; dag < 5; dag++) {
time_t weatherday = doc["daily"]["time"][dag].as<time_t>();
struct tm *datum = localtime(&weatherday);
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, TFT_BLACK);
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, PAL_BLACK);
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
if (weathercode > 40) weathercode -= 40;
spr.loadFont("fonts/weathericons30", LittleFS);
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
spr.setTextColor(TFT_RED, TFT_WHITE);
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setTextDatum(TL_DATUM);
spr.setCursor(12 + dag * 59, 58);
spr.printToSprite(weatherIcons[weathercode]);
spr.setTextColor(TFT_BLACK, TFT_WHITE);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(17 + dag * 59, 27);
spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]));
spr.unloadFont();
@@ -506,13 +508,13 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) {
uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as<double>());
spr.loadFont("fonts/GillSC20", LittleFS);
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? TFT_RED : TFT_BLACK));
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
spr.unloadFont();
if (dag>0) {
for (int i = 20; i < 128; i+=3) {
spr.drawPixel(dag * 59, i, TFT_BLACK);
spr.drawPixel(dag * 59, i, PAL_BLACK);
}
}
}
@@ -534,12 +536,12 @@ void drawIdentify(String &filename, tagRecord *&taginfo) {
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr, 296, 128);
drawString(spr, taginfo->alias, 10, 10, "fonts/bahnschrift20");
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, TFT_RED);
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
} else if (taginfo->hwType == SOLUM_154_033) {
initSprite(spr, 152, 152);
drawString(spr, taginfo->alias, 5, 5, "fonts/bahnschrift20");
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, TFT_RED);
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
@@ -602,13 +604,13 @@ bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo)
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr, 296, 128);
if (title=="" || title=="null") title="RSS feed";
drawString(spr, title, 5, 3, "fonts/bahnschrift20", TL_DATUM, TFT_RED);
drawString(spr, title, 5, 3, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
u8f.setFontMode(0);
u8f.setFontDirection(0);
u8f.setForegroundColor(TFT_BLACK);
u8f.setBackgroundColor(TFT_WHITE);
u8f.setForegroundColor(PAL_BLACK);
u8f.setBackgroundColor(PAL_WHITE);
u8f.setCursor(220, 20);
u8f.print(header);

View File

@@ -20,6 +20,9 @@ void jpg2buffer(String filein, String fileout) {
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(spr_output);
uint16_t w = 0, h = 0;
if (filein.c_str()[0] != '/') {
filein = "/" + filein;
}
TJpgDec.getFsJpgSize(&w, &h, filein, LittleFS);
Serial.println("jpeg conversion " + String(w) + "x" + String(h));
@@ -245,6 +248,26 @@ void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout) {
Serial.println("finished writing BMP " + String(millis() - t) + "ms");
}
struct Color {
uint8_t r, g, b;
Color() : r(0), g(0), b(0) {}
Color(uint16_t value_) : r((value_ >> 8) & 0xF8 | (value_ >> 13) & 0x07), g((value_ >> 3) & 0xFC | (value_ >> 9) & 0x03), b((value_ << 3) & 0xF8 | (value_ >> 2) & 0x07) {}
Color(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
};
struct Error {
float r;
float g;
float b;
};
uint32_t colorDistance(const Color &c1, const Color &c2, const Error &e1) {
float r_diff = c1.r + e1.r - c2.r;
float g_diff = c1.g + e1.g - c2.g;
float b_diff = c1.b + e1.b - c2.b;
return round(r_diff * r_diff + g_diff * g_diff + b_diff * b_diff);
}
void spr2buffer(TFT_eSprite &spr, String &fileout) {
long t = millis();
LittleFS.begin();
@@ -261,27 +284,72 @@ void spr2buffer(TFT_eSprite &spr, String &fileout) {
}
int bufferSize = (bufw * bufh) / 8;
uint16_t color;
uint8_t *blackBuffer = new uint8_t[bufferSize];
uint8_t *redBuffer = new uint8_t[bufferSize];
memset(blackBuffer, 0, bufferSize);
memset(redBuffer, 0, bufferSize);
std::vector<Color> palette = {
{255, 255, 255}, // White
{0, 0, 0}, // Black
{255, 0, 0} // Red
};
int num_colors = palette.size();
Color color;
Error error_bufferold[bufw];
Error error_buffernew[bufw];
memset(error_bufferold, 0, sizeof(error_bufferold));
for (uint16_t y = 0; y < bufh; y++) {
memset(error_buffernew, 0, sizeof(error_buffernew));
for (uint16_t x = 0; x < bufw; x++) {
if (rotated) {
color = spr.readPixel(bufh - 1 - y, x);
color = Color(spr.readPixel(bufh - 1 - y, x));
} else {
color = spr.readPixel(x, y);
color = Color(spr.readPixel(x, y));
}
int best_color_index = 0;
uint32_t best_color_distance = colorDistance(color, palette[0], error_bufferold[x]);
for (int i = 1; i < num_colors; i++) {
uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]);
if (distance < best_color_distance) {
best_color_distance = distance;
best_color_index = i;
}
}
uint16_t bitIndex = 7 - (x % 8);
uint16_t byteIndex = (y * bufw + x) / 8;
if (color == TFT_BLACK) {
if (best_color_index & 1) {
blackBuffer[byteIndex] |= (1 << bitIndex);
} else if (color == TFT_RED) {
} else if (best_color_index & 2) {
redBuffer[byteIndex] |= (1 << bitIndex);
}
Error error = {
((float)color.r + error_bufferold[x].r - palette[best_color_index].r) / 16.0f,
((float)color.g + error_bufferold[x].g - palette[best_color_index].g) / 16.0f,
((float)color.b + error_bufferold[x].b - palette[best_color_index].b) / 16.0f};
error_buffernew[x].r += error.r * 5.0f;
error_buffernew[x].g += error.g * 5.0f;
error_buffernew[x].b += error.b * 5.0f;
if (x > 0) {
error_buffernew[x - 1].r += error.r * 3.0f;
error_buffernew[x - 1].g += error.g * 3.0f;
error_buffernew[x - 1].b += error.b * 3.0f;
}
if (x < bufw - 1) {
error_buffernew[x + 1].r += error.r * 1.0f;
error_buffernew[x + 1].g += error.g * 1.0f;
error_buffernew[x + 1].b += error.b * 1.0f;
error_bufferold[x + 1].r += error.r * 7.0f;
error_bufferold[x + 1].g += error.g * 7.0f;
error_bufferold[x + 1].b += error.b * 7.0f;
}
}
memcpy(error_bufferold, error_buffernew, sizeof(error_buffernew));
}
f_out.write(blackBuffer, bufferSize);