Added bmp dither.

This commit is contained in:
nitko12 2020-09-09 14:18:37 +02:00
parent e5ca63391a
commit 82a082003a
10 changed files with 656 additions and 640 deletions

View File

@ -16,15 +16,17 @@
*/ */
#include "Inkplate.h" //Include Inkplate library to the sketch #include "Inkplate.h" //Include Inkplate library to the sketch
#include <WiFi.h> //Include ESP32 WiFi library to our sketch
#include <HTTPClient.h> //Include HTTP library to this sketch #include <HTTPClient.h> //Include HTTP library to this sketch
#include <WiFi.h> //Include ESP32 WiFi library to our sketch
#define ssid "" // Name of the WiFi network (SSID) that you want to connect Inkplate to #define ssid "" // Name of the WiFi network (SSID) that you want to connect Inkplate to
#define pass "" // Password of that WiFi network #define pass "" // Password of that WiFi network
Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) Inkplate display(
INKPLATE_1BIT); // Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome)
void setup() { void setup()
{
display.begin(); // Init Inkplate library (you should call this function ONLY ONCE) display.begin(); // Init Inkplate library (you should call this function ONLY ONCE)
display.clearDisplay(); // Clear frame buffer of display display.clearDisplay(); // Clear frame buffer of display
display.display(); // Put clear image on display display.display(); // Put clear image on display
@ -34,14 +36,21 @@ void setup() {
display.println("Scanning for WiFi networks..."); // Write text display.println("Scanning for WiFi networks..."); // Write text
display.display(); // Send everything to display (refresh display) display.display(); // Send everything to display (refresh display)
int n = WiFi.scanNetworks(); //Start searching WiFi networks and put the nubmer of found WiFi networks in variable n int n = WiFi.scanNetworks(); // Start searching WiFi networks and put the nubmer of found WiFi networks in variable
if (n == 0) { //If you did not find any network, show the message and stop the program. // n
if (n == 0)
{ // If you did not find any network, show the message and stop the program.
display.print("No WiFi networks found!"); display.print("No WiFi networks found!");
display.partialUpdate(); display.partialUpdate();
while (true); while (true)
} else { ;
if (n > 10) n = 0; //If you did find, print name (SSID), encryption and signal strength of first 10 networks }
for (int i = 0; i < n; i++) { else
{
if (n > 10)
n = 0; // If you did find, print name (SSID), encryption and signal strength of first 10 networks
for (int i = 0; i < n; i++)
{
display.print(WiFi.SSID(i)); display.print(WiFi.SSID(i));
display.print((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? 'O' : '*'); display.print((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? 'O' : '*');
display.print('\n'); display.print('\n');
@ -55,8 +64,10 @@ void setup() {
display.print("Connecting to "); // Print the name of WiFi network display.print("Connecting to "); // Print the name of WiFi network
display.print(ssid); display.print(ssid);
WiFi.begin(ssid, pass); // Try to connect to WiFi network WiFi.begin(ssid, pass); // Try to connect to WiFi network
while (WiFi.status() != WL_CONNECTED) { while (WiFi.status() != WL_CONNECTED)
delay(1000); //While it is connecting to network, display dot every second, just to know that Inkplate is alive. {
delay(1000); // While it is connecting to network, display dot every second, just to know that Inkplate is
// alive.
display.print('.'); display.print('.');
display.partialUpdate(); display.partialUpdate();
} }
@ -64,8 +75,10 @@ void setup() {
display.partialUpdate(); display.partialUpdate();
HTTPClient http; HTTPClient http;
if(http.begin("http://example.com/index.html")) { //Now try to connect to some web page (in this example www.example.com. And yes, this is a valid Web page :)) if (http.begin("http://example.com/index.html"))
if(http.GET()>0) { //If connection was successful, try to read content of the Web page and display it on screen { // Now try to connect to some web page (in this example www.example.com. And yes, this is a valid Web page :))
if (http.GET() > 0)
{ // If connection was successful, try to read content of the Web page and display it on screen
String htmlText; String htmlText;
htmlText = http.getString(); htmlText = http.getString();
display.setTextSize(1); // Set smaller text size, so everything can fit on screen display.setTextSize(1); // Set smaller text size, so everything can fit on screen
@ -77,6 +90,7 @@ void setup() {
} }
} }
void loop() { void loop()
{
// Nothing // Nothing
} }

View File

@ -23,10 +23,12 @@
#include "Inkplate.h" //Include Inkplate library to the sketch #include "Inkplate.h" //Include Inkplate library to the sketch
#include "SdFat.h" //Include library for SD card #include "SdFat.h" //Include library for SD card
Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) Inkplate display(
INKPLATE_1BIT); // Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome)
SdFile file; // Create SdFile object used for accessing files on SD card SdFile file; // Create SdFile object used for accessing files on SD card
void setup() { void setup()
{
Serial.begin(115200); Serial.begin(115200);
display.begin(); // Init Inkplate library (you should call this function ONLY ONCE) display.begin(); // Init Inkplate library (you should call this function ONLY ONCE)
@ -34,39 +36,45 @@ void setup() {
display.display(); // Put clear image on display display.display(); // Put clear image on display
// Init SD card. Display if SD card is init propery or not. // Init SD card. Display if SD card is init propery or not.
if (display.sdCardInit()) { if (display.sdCardInit())
{
display.println("SD Card OK! Reading image..."); display.println("SD Card OK! Reading image...");
display.partialUpdate(); display.partialUpdate();
// If card is properly init, try to load image and display it on e-paper at position X=0, Y=0 // If card is properly init, try to load image and display it on e-paper at position X=0, Y=0
//NOTE: Both drawBitmapFromSD methods allow for an optional fourth "invert" parameter. Setting this parameter to true // NOTE: Both drawBitmapFromSd methods allow for an optional fourth "invert" parameter. Setting this parameter
//will flip all colors on the image, making black white and white black. This may be necessary when exporting bitmaps from // to true will flip all colors on the image, making black white and white black. This may be necessary when
//certain softwares. // exporting bitmaps from certain softwares.
if (!display.drawBitmapFromSD("image1.bmp", 0, 0, 1)) { if (!display.drawBitmapFromSd("image1.bmp", 0, 0, 1))
{
// If is something failed (wrong filename or wrong bitmap format), write error message on the screen. // If is something failed (wrong filename or wrong bitmap format), write error message on the screen.
//REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no compression! // REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no
//You can turn of dithering for somewhat faster image load by changing the last 1 to 0, or removing the 1 argument completely // compression! You can turn of dithering for somewhat faster image load by changing the last 1 to 0, or
// removing the 1 argument completely
display.println("Image open error"); display.println("Image open error");
display.display(); display.display();
} }
display.display(); display.display();
} }
else { else
{
// If SD card init not success, display error on screen and stop the program (using infinite loop) // If SD card init not success, display error on screen and stop the program (using infinite loop)
display.println("SD Card error!"); display.println("SD Card error!");
display.partialUpdate(); display.partialUpdate();
while (true); while (true)
;
} }
delay(5000); delay(5000);
// Now try to load image using SdFat library class (for more advanced users) and display image on epaper. // Now try to load image using SdFat library class (for more advanced users) and display image on epaper.
if (file.open("image2.bmp", O_RDONLY)) { if (file.open("image2.bmp", O_RDONLY))
display.drawBitmapFromSD(&file, 0, 0); {
display.drawBitmapFromSd(&file, 0, 0);
display.display(); display.display();
} }
} }
void loop() { void loop()
{
// Nothing... // Nothing...
} }

View File

@ -13,7 +13,8 @@
If we missed some function, you can modify this and make yor own. If we missed some function, you can modify this and make yor own.
Also, every Inkplate comes with this slave mode right from the factory. Also, every Inkplate comes with this slave mode right from the factory.
Learn more about Slave Mode in this update: https://www.crowdsupply.com/e-radionica/inkplate-6/updates/successfully-funded-also-third-party-master-controllers-and-partial-updates Learn more about Slave Mode in this update:
https://www.crowdsupply.com/e-radionica/inkplate-6/updates/successfully-funded-also-third-party-master-controllers-and-partial-updates
UART settings are: 115200 baud, standard parity, ending with "\n\r" (both) UART settings are: 115200 baud, standard parity, ending with "\n\r" (both)
You can send commands via USB port or by directly connecting to ESP32 TX and RX pins. You can send commands via USB port or by directly connecting to ESP32 TX and RX pins.
@ -31,17 +32,22 @@ Inkplate display(INKPLATE_1BIT);
#define BUFFER_SIZE 1000 #define BUFFER_SIZE 1000
char commandBuffer[BUFFER_SIZE + 1]; char commandBuffer[BUFFER_SIZE + 1];
char strTemp[2001]; char strTemp[2001];
void setup() { void setup()
{
display.begin(); display.begin();
Serial.begin(115200); Serial.begin(115200);
memset(commandBuffer, 0, BUFFER_SIZE); memset(commandBuffer, 0, BUFFER_SIZE);
} }
void loop() { void loop()
{
// put your main code here, to run repeatedly: // put your main code here, to run repeatedly:
if (Serial.available()) { if (Serial.available())
while (Serial.available()) { {
for (int i = 0; i < (BUFFER_SIZE - 1); i++) { while (Serial.available())
{
for (int i = 0; i < (BUFFER_SIZE - 1); i++)
{
commandBuffer[i] = commandBuffer[i + 1]; commandBuffer[i] = commandBuffer[i + 1];
} }
commandBuffer[BUFFER_SIZE - 1] = Serial.read(); commandBuffer[BUFFER_SIZE - 1] = Serial.read();
@ -49,16 +55,22 @@ void loop() {
} }
char *s = NULL; char *s = NULL;
char *e = NULL; char *e = NULL;
for (int i = 0; i < BUFFER_SIZE; i++) { for (int i = 0; i < BUFFER_SIZE; i++)
if (commandBuffer[i] == '#' && s == NULL) s = &commandBuffer[i]; {
if (commandBuffer[i] == '*' && e == NULL) e = &commandBuffer[i]; if (commandBuffer[i] == '#' && s == NULL)
s = &commandBuffer[i];
if (commandBuffer[i] == '*' && e == NULL)
e = &commandBuffer[i];
} }
if (s != NULL && e != NULL) { if (s != NULL && e != NULL)
if ((e - s) > 0) { {
if ((e - s) > 0)
{
int x, x1, x2, y, y1, y2, x3, y3, l, c, w, h, r, n; int x, x1, x2, y, y1, y2, x3, y3, l, c, w, h, r, n;
char b; char b;
char temp[150]; char temp[150];
switch (*(s + 1)) { switch (*(s + 1))
{
case '?': case '?':
Serial.print("OK"); Serial.print("OK");
break; break;
@ -150,10 +162,12 @@ void loop() {
case 'C': case 'C':
sscanf(s + 3, "\"%2000[^\"]\"", strTemp); sscanf(s + 3, "\"%2000[^\"]\"", strTemp);
n = strlen(strTemp); n = strlen(strTemp);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
{
strTemp[i] = toupper(strTemp[i]); strTemp[i] = toupper(strTemp[i]);
} }
for (int i = 0; i < n; i += 2) { for (int i = 0; i < n; i += 2)
{
strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F); strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F);
} }
strTemp[n / 2] = 0; strTemp[n / 2] = 0;
@ -181,8 +195,10 @@ void loop() {
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
// sprintf(temp, "display.setTextWrap(%s)\n", b == 'T' ? "True" : "False"); // sprintf(temp, "display.setTextWrap(%s)\n", b == 'T' ? "True" : "False");
// Serial.print(temp); // Serial.print(temp);
if (b == 'T') display.setTextWrap(true); if (b == 'T')
if (b == 'F') display.setTextWrap(false); display.setTextWrap(true);
if (b == 'F')
display.setTextWrap(false);
break; break;
case 'G': case 'G':
@ -196,23 +212,28 @@ void loop() {
case 'H': case 'H':
sscanf(s + 3, "%d,%d,\"%149[^\"]\"", &x, &y, strTemp); sscanf(s + 3, "%d,%d,\"%149[^\"]\"", &x, &y, strTemp);
n = strlen(strTemp); n = strlen(strTemp);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
{
strTemp[i] = toupper(strTemp[i]); strTemp[i] = toupper(strTemp[i]);
} }
for (int i = 0; i < n; i += 2) { for (int i = 0; i < n; i += 2)
{
strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F); strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F);
} }
strTemp[n / 2] = 0; strTemp[n / 2] = 0;
r = display.sdCardInit(); r = display.sdCardInit();
if (r) { if (r)
r = display.drawBitmapFromSD(strTemp, x, y); {
r = display.drawBitmapFromSd(strTemp, x, y);
Serial.print("#H("); Serial.print("#H(");
Serial.print(r, DEC); Serial.print(r, DEC);
Serial.println(")*"); Serial.println(")*");
Serial.flush(); Serial.flush();
// sprintf(temp, "display.drawBitmap(%d, %d, %s)\n", x, y, strTemp); // sprintf(temp, "display.drawBitmap(%d, %d, %s)\n", x, y, strTemp);
// Serial.print(temp); // Serial.print(temp);
} else { }
else
{
Serial.println("#H(-1)*"); Serial.println("#H(-1)*");
Serial.flush(); Serial.flush();
} }
@ -222,23 +243,28 @@ void loop() {
sscanf(s + 3, "%d", &c); sscanf(s + 3, "%d", &c);
// sprintf(temp, "display.setDisplayMode(%s)\n", c == 0 ? "INKPLATE_1BIT" : "INKPLATE_3BIT"); // sprintf(temp, "display.setDisplayMode(%s)\n", c == 0 ? "INKPLATE_1BIT" : "INKPLATE_3BIT");
// Serial.print(temp); // Serial.print(temp);
if (c == INKPLATE_1BIT) display.selectDisplayMode(INKPLATE_1BIT); if (c == INKPLATE_1BIT)
if (c == INKPLATE_3BIT) display.selectDisplayMode(INKPLATE_3BIT); display.selectDisplayMode(INKPLATE_1BIT);
if (c == INKPLATE_3BIT)
display.selectDisplayMode(INKPLATE_3BIT);
break; break;
case 'J': case 'J':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '?') { if (b == '?')
{
// if (0 == 0) { // if (0 == 0) {
// Serial.println("#J(0)*"); // Serial.println("#J(0)*");
//} else { //} else {
// Serial.println("#J(1)*"); // Serial.println("#J(1)*");
//} //}
if (display.getDisplayMode() == INKPLATE_1BIT) { if (display.getDisplayMode() == INKPLATE_1BIT)
{
Serial.println("#J(0)*"); Serial.println("#J(0)*");
Serial.flush(); Serial.flush();
} }
if (display.getDisplayMode() == INKPLATE_3BIT) { if (display.getDisplayMode() == INKPLATE_3BIT)
{
Serial.println("#J(1)*"); Serial.println("#J(1)*");
Serial.flush(); Serial.flush();
} }
@ -247,7 +273,8 @@ void loop() {
case 'K': case 'K':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '1') { if (b == '1')
{
// Serial.print("display.clearDisplay();\n"); // Serial.print("display.clearDisplay();\n");
display.clearDisplay(); display.clearDisplay();
} }
@ -255,7 +282,8 @@ void loop() {
case 'L': case 'L':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '1') { if (b == '1')
{
// Serial.print("display.display();\n"); // Serial.print("display.display();\n");
display.display(); display.display();
} }
@ -270,7 +298,8 @@ void loop() {
case 'N': case 'N':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '?') { if (b == '?')
{
Serial.print("#N("); Serial.print("#N(");
Serial.print(display.readTemperature(), DEC); Serial.print(display.readTemperature(), DEC);
// Serial.print(23, DEC); // Serial.print(23, DEC);
@ -281,7 +310,8 @@ void loop() {
case 'O': case 'O':
sscanf(s + 3, "%d", &c); sscanf(s + 3, "%d", &c);
if (c >= 0 && c <= 2) { if (c >= 0 && c <= 2)
{
Serial.print("#O("); Serial.print("#O(");
Serial.print(display.readTouchpad(c), DEC); Serial.print(display.readTouchpad(c), DEC);
// Serial.print(0, DEC); // Serial.print(0, DEC);
@ -292,7 +322,8 @@ void loop() {
case 'P': case 'P':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '?') { if (b == '?')
{
Serial.print("#P("); Serial.print("#P(");
Serial.print(display.readBattery(), 2); Serial.print(display.readBattery(), 2);
// Serial.print(3.54, 2); // Serial.print(3.54, 2);
@ -306,13 +337,16 @@ void loop() {
c &= 1; c &= 1;
// if (c == 0) Serial.print("display.einkOff();\n"); // if (c == 0) Serial.print("display.einkOff();\n");
// if (c == 1) Serial.print("display.einkOn();\n"); // if (c == 1) Serial.print("display.einkOn();\n");
if (c == 0) display.einkOff(); if (c == 0)
if (c == 1) display.einkOn(); display.einkOff();
if (c == 1)
display.einkOn();
break; break;
case 'R': case 'R':
sscanf(s + 3, "%c", &b); sscanf(s + 3, "%c", &b);
if (b == '?') { if (b == '?')
{
Serial.print("#R("); Serial.print("#R(");
Serial.print(display.getPanelState(), DEC); Serial.print(display.getPanelState(), DEC);
// Serial.print(1, DEC); // Serial.print(1, DEC);
@ -327,7 +361,8 @@ void loop() {
} }
} }
int hexToChar(char c) { int hexToChar(char c)
{
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
return c - '0'; return c - '0';
if (c >= 'A' && c <= 'F') if (c >= 'A' && c <= 'F')

View File

@ -29,7 +29,7 @@ bool Image::drawImage(const char *path, int x, int y, bool dither, bool invert)
else else
{ {
if (strstr(path, ".bmp") != NULL) if (strstr(path, ".bmp") != NULL)
return drawBitmapFromSD(path, x, y, dither, invert); return drawBitmapFromSd(path, x, y, dither, invert);
if (strstr(path, ".jpg") != NULL || strstr(path, ".jpeg") != NULL) if (strstr(path, ".jpg") != NULL || strstr(path, ".jpeg") != NULL)
return drawJpegFromSD(path, x, y, dither, invert); return drawJpegFromSD(path, x, y, dither, invert);
} }
@ -43,143 +43,6 @@ bool Image::drawImage(const WiFiClient *s, int x, int y, int len, bool dither, b
}; };
// Loads first line in current dither buffer
void Image::ditherStart(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits)
{
for (int i = 0; i < w; ++i)
if (bits == 24)
{
if (invert)
ditherBuffer[0][i] = ((255 - *(bufferPtr++)) * 2126 / 10000) + ((255 - *(bufferPtr++)) * 7152 / 10000) +
((255 - *(bufferPtr++)) * 722 / 10000);
else
ditherBuffer[0][i] =
(*(bufferPtr++) * 2126 / 10000) + (*(bufferPtr++) * 7152 / 10000) + (*(bufferPtr++) * 722 / 10000);
}
else if (bits == 8)
{
if (invert)
ditherBuffer[0][i] = 255 - *(bufferPtr++);
else
ditherBuffer[0][i] = *(bufferPtr++);
}
if (bits == 4)
{
int _w = w / 8;
int paddingBits = w % 8;
for (int i = 0; i < _w; ++i)
{
for (int n = 0; n < 4; n++)
{
uint8_t temp = *(bufferPtr++);
ditherBuffer[0][i * 8 + n * 2] = temp & 0xF0;
ditherBuffer[0][i * 8 + n * 2 + 1] = (temp & 0x0F) << 4;
if (invert)
{
ditherBuffer[0][i * 8 + n * 2] = ~ditherBuffer[0][i * 8 + n * 2];
ditherBuffer[0][i * 8 + n * 2 + 1] = ~ditherBuffer[0][i * 8 + n * 2 + 1];
}
}
}
if (paddingBits)
{
uint32_t pixelRow = *(bufferPtr++) << 24 | *(bufferPtr++) << 16 | *(bufferPtr++) << 8 | *(bufferPtr++);
if (invert)
pixelRow = ~pixelRow;
for (int n = 0; n < paddingBits; n++)
{
ditherBuffer[0][_w * 8 + n] = (pixelRow & (0xFULL << ((7 - n) * 4))) >> ((7 - n) * 4 - 4);
}
}
}
}
// Loads next line, after this ditherGetPixel can be called and alters values in next line
void Image::ditherLoadNextLine(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits)
{
for (int i = 0; i < w; ++i)
{
if (bits == 24)
{
if (invert)
ditherBuffer[1][i] = ((255 - *(bufferPtr++)) * 2126 / 10000) + ((255 - *(bufferPtr++)) * 7152 / 10000) +
((255 - *(bufferPtr++)) * 722 / 10000);
else
ditherBuffer[1][i] =
(*(bufferPtr++) * 2126 / 10000) + (*(bufferPtr++) * 7152 / 10000) + (*(bufferPtr++) * 722 / 10000);
}
else if (bits == 8)
{
if (invert)
ditherBuffer[1][i] = 255 - *(bufferPtr++);
else
ditherBuffer[1][i] = *(bufferPtr++);
}
}
if (bits == 4)
{
int _w = w / 8;
int paddingBits = w % 8;
for (int i = 0; i < _w; ++i)
{
for (int n = 0; n < 4; n++)
{
uint8_t temp = *(bufferPtr++);
ditherBuffer[0][i * 8 + n * 2] = temp & 0xF0;
ditherBuffer[0][i * 8 + n * 2 + 1] = (temp & 0x0F) << 4;
if (invert)
{
ditherBuffer[0][i * 8 + n * 2] = ~ditherBuffer[0][i * 8 + n * 2];
ditherBuffer[0][i * 8 + n * 2 + 1] = ~ditherBuffer[0][i * 8 + n * 2 + 1];
}
}
}
if (paddingBits)
{
uint32_t pixelRow = *(bufferPtr++) << 24 | *(bufferPtr++) << 16 | *(bufferPtr++) << 8 | *(bufferPtr++);
if (invert)
pixelRow = ~pixelRow;
for (int n = 0; n < paddingBits; n++)
{
ditherBuffer[1][_w * 8 + n] = (pixelRow & (0xFULL << (28 - n * 4))) >> (28 - n * 4 - 4);
}
}
}
}
// Gets specific pixel, mainly at i, j is just used for bound checking when changing next line values
uint8_t Image::ditherGetPixel(int i, int j, int w, int h)
{
uint8_t oldpixel = ditherBuffer[0][i];
uint8_t newpixel = (oldpixel & B11100000);
ditherBuffer[0][i] = newpixel;
uint8_t quant_error = oldpixel - newpixel;
if (i + 1 < w)
ditherBuffer[0][i + 1] = min(255, (int)ditherBuffer[0][i + 1] + (((int)quant_error * 7) >> 4));
if (j + 1 < h && 0 <= i - 1)
ditherBuffer[1][i - 1] = min(255, (int)ditherBuffer[1][i - 1] + (((int)quant_error * 3) >> 4));
if (j + 1 < h)
ditherBuffer[1][i + 0] = min(255, (int)ditherBuffer[1][i + 0] + (((int)quant_error * 5) >> 4));
if (j + 1 < h && i + 1 < w)
ditherBuffer[1][i + 1] = min(255, (int)ditherBuffer[1][i + 1] + (((int)quant_error * 1) >> 4));
return newpixel;
}
// Swaps current and next line, for next one to be overwritten
uint8_t Image::ditherSwap(int w)
{
for (int i = 0; i < w; ++i)
ditherBuffer[0][i] = ditherBuffer[1][i];
}
void Image::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg) void Image::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg)
{ {
int16_t byteWidth = (w + 7) >> 3; // Bitmap scanline pad = whole byte int16_t byteWidth = (w + 7) >> 3; // Bitmap scanline pad = whole byte

View File

@ -28,8 +28,8 @@ class Image : virtual public Network
uint16_t bg = 0xFFFF); uint16_t bg = 0xFFFF);
void drawBitmap3Bit(int16_t _x, int16_t _y, const unsigned char *_p, int16_t _w, int16_t _h); void drawBitmap3Bit(int16_t _x, int16_t _y, const unsigned char *_p, int16_t _w, int16_t _h);
bool drawBitmapFromSD(SdFile *p, int x, int y, bool dither = 0, bool invert = 0); bool drawBitmapFromSd(SdFile *p, int x, int y, bool dither = 0, bool invert = 0);
bool drawBitmapFromSD(const char *fileName, int x, int y, bool dither = 0, bool invert = 0); bool drawBitmapFromSd(const char *fileName, int x, int y, bool dither = 0, bool invert = 0);
bool drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool dither = 0, bool invert = 0); bool drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool dither = 0, bool invert = 0);
bool drawBitmapFromWeb(const char *url, int x, int y, bool dither = 0, bool invert = 0); bool drawBitmapFromWeb(const char *url, int x, int y, bool dither = 0, bool invert = 0);
@ -54,15 +54,16 @@ class Image : virtual public Network
bool _invert); bool _invert);
uint8_t pixelBuffer[800 * 4 + 5]; uint8_t pixelBuffer[800 * 4 + 5];
uint8_t ditherBuffer[800 * 3 + 5][2]; uint8_t ditherBuffer[2][800 + 5];
uint8_t pallete[128]; // 2 colors per byte, _###_### uint8_t ditherPalette[256]; // 8 bit colors
uint8_t palette[128]; // 2 3 bit colors per byte, _###_###
bool legalBmp(bitmapHeader *bmpHeader); bool legalBmp(bitmapHeader *bmpHeader);
void ditherStart(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits); void ditherStart(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits);
void ditherLoadNextLine(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits); void ditherLoadNextLine(uint8_t *pixelBuffer, uint8_t *bufferPtr, int w, bool invert, uint8_t bits);
uint8_t ditherGetPixel(int i, int j, int w, int h); uint8_t ditherGetPixel(uint8_t px, int i, int w, bool paletted);
uint8_t ditherSwap(int w); void ditherSwap(int w);
void readBmpHeader(uint8_t *buf, bitmapHeader *_h); void readBmpHeader(uint8_t *buf, bitmapHeader *_h);
void readBmpHeaderSd(SdFile *_f, bitmapHeader *_h); void readBmpHeaderSd(SdFile *_f, bitmapHeader *_h);

View File

@ -1,5 +1,12 @@
#include "Image.h" #include "Image.h"
bool Image::legalBmp(bitmapHeader *bmpHeader)
{
return bmpHeader->signature == 0x4D42 && bmpHeader->compression == 0 &&
(bmpHeader->color == 1 || bmpHeader->color == 4 || bmpHeader->color == 8 || bmpHeader->color == 16 ||
bmpHeader->color == 24 || bmpHeader->color == 32);
}
void Image::readBmpHeaderSd(SdFile *_f, bitmapHeader *_h) void Image::readBmpHeaderSd(SdFile *_f, bitmapHeader *_h)
{ {
uint8_t header[55]; uint8_t header[55];
@ -7,8 +14,8 @@ void Image::readBmpHeaderSd(SdFile *_f, bitmapHeader *_h)
_f->rewind(); _f->rewind();
_f->read(header, 55); _f->read(header, 55);
uint16_t color = read16(header + 28); uint16_t color = READ16(header + 28);
uint32_t totalColors = read32(header + 46); uint32_t totalColors = READ32(header + 46);
if (color <= 8) if (color <= 8)
{ {
@ -31,16 +38,16 @@ void Image::readBmpHeaderSd(SdFile *_f, bitmapHeader *_h)
void Image::readBmpHeader(uint8_t *buf, bitmapHeader *_h) void Image::readBmpHeader(uint8_t *buf, bitmapHeader *_h)
{ {
_h->signature = read16(buf + 0); _h->signature = READ16(buf + 0);
_h->fileSize = read32(buf + 2); _h->fileSize = READ32(buf + 2);
_h->startRAW = read32(buf + 10); _h->startRAW = READ32(buf + 10);
_h->dibHeaderSize = read32(buf + 14); _h->dibHeaderSize = READ32(buf + 14);
_h->width = read32(buf + 18); _h->width = READ32(buf + 18);
_h->height = read32(buf + 22); _h->height = READ32(buf + 22);
_h->color = read16(buf + 28); _h->color = READ16(buf + 28);
_h->compression = read32(buf + 30); _h->compression = READ32(buf + 30);
uint32_t totalColors = read32(buf + 46); uint32_t totalColors = READ32(buf + 46);
uint8_t paletteRGB[1024]; uint8_t paletteRGB[1024];
@ -50,110 +57,33 @@ void Image::readBmpHeader(uint8_t *buf, bitmapHeader *_h)
totalColors = (1ULL << _h->color); totalColors = (1ULL << _h->color);
memcpy(paletteRGB, buf + 53, totalColors * 4); memcpy(paletteRGB, buf + 53, totalColors * 4);
memset(pallete, 0, sizeof pallete); memset(palette, 0, sizeof palette);
for (int i = 0; i < totalColors; ++i) for (int i = 0; i < totalColors; ++i)
{ {
uint32_t c = read32(paletteRGB + (i << 2)); uint32_t c = READ32(paletteRGB + (i << 2));
uint8_t r = (c & 0xFF000000) >> 24; uint8_t r = (c & 0xFF000000) >> 24;
uint8_t g = (c & 0x00FF0000) >> 16; uint8_t g = (c & 0x00FF0000) >> 16;
uint8_t b = (c & 0x0000FF00) >> 8; uint8_t b = (c & 0x0000FF00) >> 8;
pallete[i >> 1] |= RGB3BIT(r, g, b) << (i & 1 ? 0 : 4); palette[i >> 1] |= RGB3BIT(r, g, b) << (i & 1 ? 0 : 4);
ditherPalette[i] = RGB8BIT(r, g, b);
} }
} }
}; };
bool Image::legalBmp(bitmapHeader *bmpHeader)
{
return bmpHeader->signature == 0x4D42 && bmpHeader->compression == 0 &&
(bmpHeader->color == 1 || bmpHeader->color == 4 || bmpHeader->color == 8 || bmpHeader->color == 16 ||
bmpHeader->color == 24 || bmpHeader->color == 32);
}
void Image::displayBmpLine(int16_t x, int16_t y, bitmapHeader *bmpHeader, bool dither, bool invert) bool Image::drawBitmapFromSd(const char *fileName, int x, int y, bool dither, bool invert)
{
int16_t w = bmpHeader->width, h = bmpHeader->height;
int8_t c = bmpHeader->color;
startWrite();
for (int j = 0; j < w; ++j)
{
switch (c)
{
case 1:
// Should we ignore palette on 1 bit?
writePixel(x + j, y, (invert ^ (pallete[0] < pallete[1])) ^ !!(pixelBuffer[j >> 3] & (1 << (7 - j & 7))));
break;
// as for 2 bit, literally cannot find an example online or in PS, so skipped
case 4: {
uint8_t px = pixelBuffer[j >> 1] & (j & 1 ? 0x0F : 0xF0) >> (j & 1 ? 0 : 4);
if (invert)
writePixel(x + j, y, 7 - (pallete[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4)));
else
writePixel(x + j, y, pallete[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4));
break;
}
case 8: {
uint8_t px = pixelBuffer[j];
if (invert)
writePixel(x + j, y, 7 - (pallete[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4)));
else
writePixel(x + j, y, pallete[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4));
break;
}
case 16: {
uint16_t px = ((uint16_t)pixelBuffer[(j << 1) | 1] << 8) | pixelBuffer[(j << 1)];
uint8_t r = (px & 0x7C00) >> 7;
uint8_t g = (px & 0x3E0) >> 2;
uint8_t b = (px & 0x1F) << 3;
if (invert)
writePixel(x + j, y, 7 - RGB3BIT(r, g, b));
else
writePixel(x + j, y, RGB3BIT(r, g, b));
break;
}
case 24: {
uint8_t r = pixelBuffer[j * 3];
uint8_t g = pixelBuffer[j * 3 + 1];
uint8_t b = pixelBuffer[j * 3 + 2];
if (invert)
writePixel(x + j, y, 7 - RGB3BIT(r, g, b));
else
writePixel(x + j, y, RGB3BIT(r, g, b));
break;
}
case 32:
uint8_t r = pixelBuffer[j * 4];
uint8_t g = pixelBuffer[j * 4 + 1];
uint8_t b = pixelBuffer[j * 4 + 2];
if (invert)
writePixel(x + j, y, 7 - RGB3BIT(r, g, b));
else
writePixel(x + j, y, RGB3BIT(r, g, b));
break;
}
}
endWrite();
}
bool Image::drawBitmapFromSD(const char *fileName, int x, int y, bool dither, bool invert)
{ {
SdFile dat; SdFile dat;
if (dat.open(fileName, O_RDONLY)) if (dat.open(fileName, O_RDONLY))
return drawBitmapFromSD(&dat, x, y, dither, invert); return drawBitmapFromSd(&dat, x, y, dither, invert);
else else
return 0; return 0;
} }
bool Image::drawBitmapFromSD(SdFile *p, int x, int y, bool dither, bool invert) bool Image::drawBitmapFromSd(SdFile *p, int x, int y, bool dither, bool invert)
{ {
bitmapHeader bmpHeader; bitmapHeader bmpHeader;
@ -174,11 +104,11 @@ bool Image::drawBitmapFromSD(SdFile *p, int x, int y, bool dither, bool invert)
int8_t c = bmpHeader.color; int8_t c = bmpHeader.color;
p->seekSet(bmpHeader.startRAW); p->seekSet(bmpHeader.startRAW);
if (dither)
memset(ditherBuffer, 0, sizeof ditherBuffer);
for (int i = 0; i < h; ++i) for (int i = 0; i < h; ++i)
{ {
int16_t n = rowSize(w, c); int16_t n = ROWSIZE(w, c);
p->read(pixelBuffer, n); p->read(pixelBuffer, n);
displayBmpLine(x, y + bmpHeader.height - i, &bmpHeader, dither, invert); displayBmpLine(x, y + bmpHeader.height - i, &bmpHeader, dither, invert);
} }
@ -203,15 +133,125 @@ bool Image::drawBitmapFromWeb(const char *url, int x, int y, bool dither, bool i
getDisplayMode() != INKPLATE_3BIT) getDisplayMode() != INKPLATE_3BIT)
selectDisplayMode(INKPLATE_3BIT); selectDisplayMode(INKPLATE_3BIT);
if (dither)
memset(ditherBuffer, 0, sizeof ditherBuffer);
uint8_t *bufferPtr = buf + bmpHeader.startRAW; uint8_t *bufferPtr = buf + bmpHeader.startRAW;
for (int i = 0; i < bmpHeader.height; ++i) for (int i = 0; i < bmpHeader.height; ++i)
{ {
memcpy(pixelBuffer, bufferPtr, rowSize(bmpHeader.width, bmpHeader.color)); memcpy(pixelBuffer, bufferPtr, ROWSIZE(bmpHeader.width, bmpHeader.color));
displayBmpLine(x, y + bmpHeader.height - i, &bmpHeader, dither, invert); displayBmpLine(x, y + bmpHeader.height - i, &bmpHeader, dither, invert);
bufferPtr += rowSize(bmpHeader.width, bmpHeader.color); bufferPtr += ROWSIZE(bmpHeader.width, bmpHeader.color);
} }
free(buf); free(buf);
return 1; return 1;
} }
void Image::displayBmpLine(int16_t x, int16_t y, bitmapHeader *bmpHeader, bool dither, bool invert)
{
int16_t w = bmpHeader->width, h = bmpHeader->height;
int8_t c = bmpHeader->color;
startWrite();
for (int j = 0; j < w; ++j)
{
switch (c)
{
case 1:
writePixel(x + j, y, (invert ^ (palette[0] < palette[1])) ^ !!(pixelBuffer[j >> 3] & (1 << (7 - j & 7))));
break;
// as for 2 bit, literally cannot find an example online or in PS, so skipped
case 4: {
uint8_t px = pixelBuffer[j >> 1] & (j & 1 ? 0x0F : 0xF0) >> (j & 1 ? 0 : 4);
uint8_t val;
if (dither)
val = ditherGetPixel(px, j, w, 1);
else
val = palette[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4);
if (invert)
writePixel(x + j, y, 7 - val);
else
writePixel(x + j, y, val);
break;
}
case 8: {
uint8_t px = pixelBuffer[j];
uint8_t val;
if (dither)
val = ditherGetPixel(px, j, w, 1);
else
{
val = palette[px >> 1] & (px & 1 ? 0x0F : 0xF0) >> (px & 1 ? 0 : 4);
}
if (invert)
writePixel(x + j, y, 7 - val);
else
writePixel(x + j, y, val);
break;
}
case 16: {
uint16_t px = ((uint16_t)pixelBuffer[(j << 1) | 1] << 8) | pixelBuffer[(j << 1)];
uint8_t r = (px & 0x7C00) >> 7;
uint8_t g = (px & 0x3E0) >> 2;
uint8_t b = (px & 0x1F) << 3;
uint8_t val;
if (dither)
val = ditherGetPixel(RGB8BIT(r, g, b), j, w, 0);
else
val = RGB3BIT(r, g, b);
if (invert)
writePixel(x + j, y, 7 - val);
else
writePixel(x + j, y, val);
break;
}
case 24: {
uint8_t r = pixelBuffer[j * 3];
uint8_t g = pixelBuffer[j * 3 + 1];
uint8_t b = pixelBuffer[j * 3 + 2];
uint8_t val;
if (dither)
val = ditherGetPixel(RGB8BIT(r, g, b), j, w, 0);
else
val = RGB3BIT(r, g, b);
if (invert)
writePixel(x + j, y, 7 - val);
else
writePixel(x + j, y, val);
break;
}
case 32:
uint8_t r = pixelBuffer[j * 4];
uint8_t g = pixelBuffer[j * 4 + 1];
uint8_t b = pixelBuffer[j * 4 + 2];
uint8_t val;
if (dither)
val = ditherGetPixel(RGB8BIT(r, g, b), j, w, 0);
else
val = RGB3BIT(r, g, b);
if (invert)
writePixel(x + j, y, 7 - RGB3BIT(r, g, b));
else
writePixel(x + j, y, RGB3BIT(r, g, b));
break;
}
}
ditherSwap(w);
endWrite();
}

View File

@ -0,0 +1,32 @@
#include "Image.h"
uint8_t Image::ditherGetPixel(uint8_t px, int i, int w, bool paletted)
{
if (paletted)
px = ditherPalette[px];
uint8_t oldPixel = min((uint16_t)0xFF, (uint16_t)((uint16_t)ditherBuffer[0][i] + px));
uint8_t newPixel = oldPixel & B11100000;
uint8_t quantError = oldPixel - newPixel;
ditherBuffer[1][i + 0] += (quantError * 5) >> 4;
if (i != w - 1)
{
ditherBuffer[0][i + 1] += (quantError * 7) >> 4;
ditherBuffer[1][i + 1] += (quantError * 1) >> 4;
}
if (i != 0)
ditherBuffer[1][i - 1] += (quantError * 3) >> 4;
return newPixel >> 5;
}
void Image::ditherSwap(int w)
{
for (int i = 0; i < w; ++i)
{
ditherBuffer[0][i] = ditherBuffer[1][i];
ditherBuffer[1][i] = 0;
}
}

View File

@ -16,8 +16,10 @@
#define PAD3 2 #define PAD3 2
#define RGB3BIT(r, g, b) ((54UL * (r) + 183UL * (g) + 19UL * (b)) >> 13) #define RGB3BIT(r, g, b) ((54UL * (r) + 183UL * (g) + 19UL * (b)) >> 13)
#define read32(c) (uint32_t)(*(c) | (*((c) + 1) << 8) | (*((c) + 2) << 16) | (*((c) + 3) << 24)); #define RGB8BIT(r, g, b) ((54UL * (r) + 183UL * (g) + 19UL * (b)) >> 8)
#define read16(c) (uint16_t)(*(c) | (*((c) + 1) << 8));
#define rowSize(w, c) (((int16_t)c * w + 31) >> 5) << 2 #define READ32(c) (uint32_t)(*(c) | (*((c) + 1) << 8) | (*((c) + 2) << 16) | (*((c) + 3) << 24));
#define READ16(c) (uint16_t)(*(c) | (*((c) + 1) << 8));
#define ROWSIZE(w, c) (((int16_t)c * w + 31) >> 5) << 2
#endif #endif

View File

@ -6,7 +6,17 @@
Inkplate display(INKPLATE_1BIT); Inkplate display(INKPLATE_1BIT);
#define DELAYMS 1000
const char *images[] = {"1bit.bmp", "4bit.bmp", "8bit.bmp", "16bit.bmp", "24bit.bmp", "32bit.bmp"}; const char *images[] = {"1bit.bmp", "4bit.bmp", "8bit.bmp", "16bit.bmp", "24bit.bmp", "32bit.bmp"};
const char *imageUrls[] = {
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/1bit.bmp",
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/4bit.bmp",
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/8bit.bmp",
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/16bit.bmp",
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/24bit.bmp",
"https://raw.githubusercontent.com/nitko12/Inkplate-revision/master/test/bitmaps/32bit.bmp",
};
const bool depth[] = {INKPLATE_1BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT}; const bool depth[] = {INKPLATE_1BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT, INKPLATE_3BIT};
void setup() void setup()
@ -91,7 +101,7 @@ void loop()
display.clearDisplay(); display.clearDisplay();
delay(5000); delay(5000);
display.drawBitmapFromSD(images[j], 0, 0, dither, invert); display.drawBitmapFromSd(images[j], 0, 0, dither, invert);
display.display(); display.display();
display.clearDisplay(); display.clearDisplay();
delay(5000); delay(5000);
@ -111,6 +121,7 @@ void loop()
display.setCursor(100, 100); display.setCursor(100, 100);
display.print("Displaying "); display.print("Displaying ");
display.print(images[j]); display.print(images[j]);
display.print(" from web");
if (!dither) if (!dither)
display.print(" non"); display.print(" non");
display.print(" dithered and"); display.print(" dithered and");
@ -122,7 +133,7 @@ void loop()
display.clearDisplay(); display.clearDisplay();
delay(5000); delay(5000);
display.drawBitmapFromSD(images[j], 0, 0, dither, invert); display.drawBitmapFromWeb(imageUrls[j], 0, 0, dither, invert);
display.display(); display.display();
display.clearDisplay(); display.clearDisplay();
delay(5000); delay(5000);

View File

@ -22,7 +22,17 @@ void loop()
if (display.sdCardInit()) if (display.sdCardInit())
{ {
Serial.println(display.drawBitmapFromSD("8bit.bmp", 0, 0)); Serial.println(display.drawBitmapFromSd("Lenna.bmp", 0, 0, 0, 0));
}
display.display();
delay(5000);
if (display.sdCardInit())
{
int16_t t = millis();
Serial.println(display.drawBitmapFromSd("Lenna.bmp", 0, 0, 1, 0));
Serial.println(millis() - t);
} }
display.display(); display.display();