Use GDlib in Waveshare ePapers command

This commit is contained in:
Marcos Del Sol Vives 2024-01-06 18:53:47 +01:00
commit 778a129f52
No known key found for this signature in database
4 changed files with 290 additions and 509 deletions

View file

@ -332,6 +332,17 @@ endif
LDLIBS += $(QTLDLIBS)
CXXINCLUDES += $(QTINCLUDES)
## GD (optional)
ifneq ($(SKIPGD),1)
GDINCLUDES = $(shell $(PKG_CONFIG_ENV) pkg-config --cflags gdlib 2>/dev/null)
GDLDLIBS = $(shell $(PKG_CONFIG_ENV) pkg-config --libs gdlib 2>/dev/null)
ifneq ($(GDLDLIBS),)
LDLIBS += $(GDLDLIBS)
PM3INCLUDES += $(GDINCLUDES)
GD_FOUND = 1
endif
endif
## Readline
ifneq ($(SKIPREADLINE),1)
ifeq ($(USE_BREW),1)
@ -510,6 +521,16 @@ else
endif
endif
ifeq ($(SKIPGD),1)
$(info GD library: skipped)
else
ifeq ($(GD_FOUND),1)
$(info GD library: GD v$(shell $(PKG_CONFIG_ENV) pkg-config --modversion gdlib) found, enabled)
else
$(info GD library: GD not found, disabled)
endif
endif
ifeq ($(SKIPREADLINE),1)
$(info Readline library: skipped)
else
@ -605,7 +626,6 @@ SRCS = mifare/aiddesfire.c \
cmdhftopaz.c \
cmdhftexkom.c \
cmdhfvas.c \
cmdhfwaveshare.c \
cmdhfxerox.c \
cmdhw.c \
cmdlf.c \
@ -738,6 +758,12 @@ SRCS += bucketsort.c \
lfdemod.c \
util_posix.c
ifeq ($(GD_FOUND),1)
# electronic shelf labels
SRCS += imgutils.c \
cmdhfwaveshare.c
endif
# swig
SWIGSRCS =

View file

@ -28,31 +28,7 @@
#include "fileutils.h"
#include "util_posix.h" // msleep
#include "cliparser.h"
// Currently the largest pixel 880*528 only needs 58.08K bytes
#define WSMAPSIZE 60000
typedef struct {
uint8_t B;
uint8_t M;
uint32_t fsize;
uint16_t res1;
uint16_t res2;
uint32_t offset;
uint32_t Bit_Pixel;
uint32_t BMP_Width;
uint32_t BMP_Height;
uint16_t planes;
uint16_t bpp;
uint32_t ctype;
uint32_t dsize;
uint32_t hppm;
uint32_t vppm;
uint32_t colorsused;
uint32_t colorreq;
uint32_t Color_1; //Color palette
uint32_t Color_2;
} PACKED bmp_header_t;
#include "imgutils.h"
#define EPD_1IN54B 0
#define EPD_1IN54C 1
@ -105,254 +81,24 @@ static model_t models[] = {
static int CmdHelp(const char *Cmd);
static int picture_bit_depth(const uint8_t *bmp, const size_t bmpsize, const uint8_t model_nr) {
if (bmpsize < sizeof(bmp_header_t)) {
return PM3_ESOFT;
static uint8_t * map8to1(gdImagePtr img, int color) {
// Calculate width rounding up
uint16_t width8 = (gdImageSX(img) + 7) / 8;
uint8_t * colormap8 = malloc(width8 * gdImageSY(img));
if (!colormap8) {
return NULL;
}
bmp_header_t *pbmpheader = (bmp_header_t *)bmp;
PrintAndLogEx(DEBUG, "colorsused = %d", pbmpheader->colorsused);
PrintAndLogEx(DEBUG, "pbmpheader->bpp = %d", pbmpheader->bpp);
if ((pbmpheader->BMP_Width != models[model_nr].width) || (pbmpheader->BMP_Height != models[model_nr].height)) {
PrintAndLogEx(WARNING, "Invalid BMP size, expected %ix%i, got %ix%i", models[model_nr].width, models[model_nr].height, pbmpheader->BMP_Width, pbmpheader->BMP_Height);
}
return pbmpheader->bpp;
}
static int read_bmp_bitmap(const uint8_t *bmp, const size_t bmpsize, uint8_t model_nr, uint8_t **black, uint8_t **red) {
bmp_header_t *pbmpheader = (bmp_header_t *)bmp;
// check file is bitmap
if (pbmpheader->bpp != 1) {
return PM3_ESOFT;
}
if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42
PrintAndLogEx(WARNING, "The file is not a BMP!");
return PM3_ESOFT;
}
PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize);
PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset);
if (pbmpheader->fsize > bmpsize) {
PrintAndLogEx(WARNING, "The file is truncated!");
return PM3_ESOFT;
}
uint8_t color_flag = pbmpheader->Color_1;
// Get BMP file data pointer
uint32_t offset = pbmpheader->offset;
uint16_t width = pbmpheader->BMP_Width;
uint16_t height = pbmpheader->BMP_Height;
if ((width + 8) * height > WSMAPSIZE * 8) {
PrintAndLogEx(WARNING, "The file is too large, aborting!");
return PM3_ESOFT;
}
uint16_t X, Y;
uint16_t Image_Width_Byte = (width % 8 == 0) ? (width / 8) : (width / 8 + 1);
uint16_t Bmp_Width_Byte = (Image_Width_Byte % 4 == 0) ? Image_Width_Byte : ((Image_Width_Byte / 4 + 1) * 4);
*black = calloc(WSMAPSIZE, sizeof(uint8_t));
if (*black == NULL) {
return PM3_EMALLOC;
}
// Write data into RAM
for (Y = 0; Y < height; Y++) { // columns
for (X = 0; X < Bmp_Width_Byte; X++) { // lines
if ((X < Image_Width_Byte) && ((X + (height - Y - 1) * Image_Width_Byte) < WSMAPSIZE)) {
(*black)[X + (height - Y - 1) * Image_Width_Byte] = color_flag ? bmp[offset] : ~bmp[offset];
}
offset++;
}
}
if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
// for BW+Red screens:
*red = calloc(WSMAPSIZE, sizeof(uint8_t));
if (*red == NULL) {
free(*black);
return PM3_EMALLOC;
}
}
return PM3_SUCCESS;
}
static void rgb_to_gray(const int16_t *chanR, const int16_t *chanG, const int16_t *chanB,
uint16_t width, uint16_t height, int16_t *chanGrey) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
// greyscale conversion
float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width];
// Csrgb = 12.92 Clinear when Clinear <= 0.0031308
// Csrgb = 1.055 Clinear1/2.4 - 0.055 when Clinear > 0.0031308
chanGrey[X + Y * width] = Clinear;
}
}
}
// Floyd-Steinberg dithering
static void dither_chan_inplace(int16_t *chan, uint16_t width, uint16_t height) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
int16_t oldp = chan[X + Y * width];
int16_t newp = oldp > 127 ? 255 : 0;
chan[X + Y * width] = newp;
int16_t err = oldp - newp;
const float m[] = {7, 3, 5, 1};
if (X < width - 1) {
chan[X + 1 + Y * width] = chan[X + 1 + Y * width] + m[0] / 16 * err;
}
if (Y < height - 1) {
chan[X - 1 + (Y + 1) * width] = chan[X - 1 + (Y + 1) * width] + m[1] / 16 * err;
chan[X + (Y + 1) * width] = chan[X + (Y + 1) * width] + m[2] / 16 * err;
}
if ((X < width - 1) && (Y < height - 1)) {
chan[X + 1 + (Y + 1) * width] = chan[X + 1 + (Y + 1) * width] + m[3] / 16 * err;
}
}
}
}
static uint32_t color_compare(int16_t r1, int16_t g1, int16_t b1, int16_t r2, int16_t g2, int16_t b2) {
// Compute (square of) distance from oldR/G/B to this color
int16_t inR = r1 - r2;
int16_t inG = g1 - g2;
int16_t inB = b1 - b2;
// use RGB-to-grey weighting
float dist = 0.2126 * inR * inR + 0.7152 * inG * inG + 0.0722 * inB * inB;
return dist;
}
static void nearest_color(int16_t oldR, int16_t oldG, int16_t oldB, const uint8_t *palette,
uint16_t palettelen, uint8_t *newR, uint8_t *newG, uint8_t *newB) {
uint32_t bestdist = 0x7FFFFFFF;
for (uint16_t i = 0; i < palettelen; i++) {
uint8_t R = palette[i * 3 + 0];
uint8_t G = palette[i * 3 + 1];
uint8_t B = palette[i * 3 + 2];
uint32_t dist = color_compare(oldR, oldG, oldB, R, G, B);
if (dist < bestdist) {
bestdist = dist;
*newR = R;
*newG = G;
*newB = B;
}
}
}
static void dither_rgb_inplace(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height, uint8_t *palette, uint16_t palettelen) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
// scan odd lines in the opposite direction
uint16_t XX = X;
if (Y % 2) {
XX = width - X - 1;
}
int16_t oldR = chanR[XX + Y * width];
int16_t oldG = chanG[XX + Y * width];
int16_t oldB = chanB[XX + Y * width];
uint8_t newR = 0, newG = 0, newB = 0;
nearest_color(oldR, oldG, oldB, palette, palettelen, &newR, &newG, &newB);
chanR[XX + Y * width] = newR;
chanG[XX + Y * width] = newG;
chanB[XX + Y * width] = newB;
int16_t errR = oldR - newR;
int16_t errG = oldG - newG;
int16_t errB = oldB - newB;
const float m[] = {7, 3, 5, 1};
if (Y % 2) {
if (XX > 0) {
chanR[XX - 1 + Y * width] = (chanR[XX - 1 + Y * width] + m[0] / 16 * errR);
chanG[XX - 1 + Y * width] = (chanG[XX - 1 + Y * width] + m[0] / 16 * errG);
chanB[XX - 1 + Y * width] = (chanB[XX - 1 + Y * width] + m[0] / 16 * errB);
}
if (Y < height - 1) {
chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[3] / 16 * errR);
chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[3] / 16 * errG);
chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[3] / 16 * errB);
chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR);
chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG);
chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB);
}
if ((XX < width - 1) && (Y < height - 1)) {
chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[1] / 16 * errR);
chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[1] / 16 * errG);
chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[1] / 16 * errB);
}
} else {
if (XX < width - 1) {
chanR[XX + 1 + Y * width] = (chanR[XX + 1 + Y * width] + m[0] / 16 * errR);
chanG[XX + 1 + Y * width] = (chanG[XX + 1 + Y * width] + m[0] / 16 * errG);
chanB[XX + 1 + Y * width] = (chanB[XX + 1 + Y * width] + m[0] / 16 * errB);
}
if (Y < height - 1) {
chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[1] / 16 * errR);
chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[1] / 16 * errG);
chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[1] / 16 * errB);
chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR);
chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG);
chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB);
}
if ((XX < width - 1) && (Y < height - 1)) {
chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[3] / 16 * errR);
chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[3] / 16 * errG);
chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[3] / 16 * errB);
}
}
}
}
}
static void rgb_to_gray_red_inplace(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width];
if ((chanR[X + Y * width] < chanG[X + Y * width] && chanR[X + Y * width] < chanB[X + Y * width])) {
chanR[X + Y * width] = Clinear;
chanG[X + Y * width] = Clinear;
chanB[X + Y * width] = Clinear;
}
}
}
}
static void threshold_chan(const int16_t *colorchan, uint16_t width, uint16_t height, uint8_t threshold, uint8_t *colormap) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
colormap[X + Y * width] = colorchan[X + Y * width] < threshold;
}
}
}
static void threshold_rgb_black_red(const int16_t *chanR, const int16_t *chanG, const int16_t *chanB,
uint16_t width, uint16_t height, uint8_t threshold_black,
uint8_t threshold_red, uint8_t *blackmap, uint8_t *redmap) {
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
if ((chanR[X + Y * width] < threshold_black) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) {
blackmap[X + Y * width] = 1;
redmap[X + Y * width] = 0;
} else if ((chanR[X + Y * width] > threshold_red) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) {
blackmap[X + Y * width] = 0;
redmap[X + Y * width] = 1;
} else {
blackmap[X + Y * width] = 0;
redmap[X + Y * width] = 0;
}
}
}
}
static void map8to1(const uint8_t *colormap, uint16_t width, uint16_t height, uint8_t *colormap8) {
uint16_t width8;
if (width % 8 == 0) {
width8 = width / 8;
} else {
width8 = width / 8 + 1;
}
uint8_t data = 0;
uint8_t count = 0;
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
data = data | colormap[X + Y * width];
for (uint16_t Y = 0; Y < gdImageSY(img); Y++) {
for (uint16_t X = 0; X < gdImageSX(img); X++) {
if (gdImageGetPixel(img, X, Y) == color) {
data |= 1;
}
count += 1;
if ((count >= 8) || (X == width - 1)) {
if ((count >= 8) || (X == gdImageSX(img) - 1)) {
colormap8[X / 8 + Y * width8] = (~data) & 0xFF;
count = 0;
data = 0;
@ -360,194 +106,8 @@ static void map8to1(const uint8_t *colormap, uint16_t width, uint16_t height, ui
data = (data << 1) & 0xFF;
}
}
}
static int read_bmp_rgb(uint8_t *bmp, const size_t bmpsize, uint8_t model_nr, uint8_t **black, uint8_t **red, char *filename, bool save_conversions) {
bmp_header_t *pbmpheader = (bmp_header_t *)bmp;
// check file is full color
if ((pbmpheader->bpp != 24) && (pbmpheader->bpp != 32)) {
return PM3_ESOFT;
}
if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42
PrintAndLogEx(WARNING, "The file is not a BMP!");
return PM3_ESOFT;
}
PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize);
PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset);
if (pbmpheader->fsize > bmpsize) {
PrintAndLogEx(WARNING, "The file is truncated!");
return PM3_ESOFT;
}
// Get BMP file data pointer
uint32_t offset = pbmpheader->offset;
uint16_t width = pbmpheader->BMP_Width;
uint16_t height = pbmpheader->BMP_Height;
if ((width + 8) * height > WSMAPSIZE * 8) {
PrintAndLogEx(WARNING, "The file is too large, aborting!");
return PM3_ESOFT;
}
int16_t *chanR = calloc(((size_t)width) * height, sizeof(int16_t));
if (chanR == NULL) {
return PM3_EMALLOC;
}
int16_t *chanG = calloc(((size_t)width) * height, sizeof(int16_t));
if (chanG == NULL) {
free(chanR);
return PM3_EMALLOC;
}
int16_t *chanB = calloc(((size_t)width) * height, sizeof(int16_t));
if (chanB == NULL) {
free(chanR);
free(chanG);
return PM3_EMALLOC;
}
// Extracting BMP chans
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
chanB[X + (height - Y - 1) * width] = bmp[offset++];
chanG[X + (height - Y - 1) * width] = bmp[offset++];
chanR[X + (height - Y - 1) * width] = bmp[offset++];
if (pbmpheader->bpp == 32) // Skip Alpha chan
offset++;
}
// Skip line padding
offset += width % 4;
}
if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
// for BW+Red screens:
uint8_t *mapBlack = calloc(((size_t)width) * height, sizeof(uint8_t));
if (mapBlack == NULL) {
free(chanR);
free(chanG);
free(chanB);
return PM3_EMALLOC;
}
uint8_t *mapRed = calloc(((size_t)width) * height, sizeof(uint8_t));
if (mapRed == NULL) {
free(chanR);
free(chanG);
free(chanB);
free(mapBlack);
return PM3_EMALLOC;
}
rgb_to_gray_red_inplace(chanR, chanG, chanB, width, height);
uint8_t palette[] = {0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}; // black, white, red
dither_rgb_inplace(chanR, chanG, chanB, width, height, palette, sizeof(palette) / 3);
threshold_rgb_black_red(chanR, chanG, chanB, width, height, 128, 128, mapBlack, mapRed);
if (save_conversions) {
// fill BMP chans
offset = pbmpheader->offset;
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
bmp[offset++] = chanB[X + (height - Y - 1) * width] & 0xFF;
bmp[offset++] = chanG[X + (height - Y - 1) * width] & 0xFF;
bmp[offset++] = chanR[X + (height - Y - 1) * width] & 0xFF;
if (pbmpheader->bpp == 32) // Fill Alpha chan
bmp[offset++] = 0xFF;
}
// Skip line padding
offset += width % 4;
}
PrintAndLogEx(INFO, "Saving red+black dithered version...");
if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename);
free(chanR);
free(chanG);
free(chanB);
free(mapBlack);
free(mapRed);
return PM3_EIO;
}
}
free(chanR);
free(chanG);
free(chanB);
*black = calloc(WSMAPSIZE, sizeof(uint8_t));
if (*black == NULL) {
free(mapBlack);
free(mapRed);
return PM3_EMALLOC;
}
map8to1(mapBlack, width, height, *black);
free(mapBlack);
*red = calloc(WSMAPSIZE, sizeof(uint8_t));
if (*red == NULL) {
free(mapRed);
free(*black);
return PM3_EMALLOC;
}
map8to1(mapRed, width, height, *red);
free(mapRed);
} else {
// for BW-only screens:
int16_t *chanGrey = calloc(((size_t)width) * height, sizeof(int16_t));
if (chanGrey == NULL) {
free(chanR);
free(chanG);
free(chanB);
return PM3_EMALLOC;
}
rgb_to_gray(chanR, chanG, chanB, width, height, chanGrey);
dither_chan_inplace(chanGrey, width, height);
uint8_t *mapBlack = calloc(((size_t)width) * height, sizeof(uint8_t));
if (mapBlack == NULL) {
free(chanR);
free(chanG);
free(chanB);
free(chanGrey);
return PM3_EMALLOC;
}
threshold_chan(chanGrey, width, height, 128, mapBlack);
if (save_conversions) {
// fill BMP chans
offset = pbmpheader->offset;
for (uint16_t Y = 0; Y < height; Y++) {
for (uint16_t X = 0; X < width; X++) {
bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
if (pbmpheader->bpp == 32) // Fill Alpha chan
bmp[offset++] = 0xFF;
}
// Skip line padding
offset += width % 4;
}
PrintAndLogEx(INFO, "Saving black dithered version...");
if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename);
free(chanGrey);
free(chanR);
free(chanG);
free(chanB);
free(mapBlack);
return PM3_EIO;
}
}
free(chanGrey);
free(chanR);
free(chanG);
free(chanB);
*black = calloc(WSMAPSIZE, sizeof(uint8_t));
if (*black == NULL) {
free(mapBlack);
return PM3_EMALLOC;
}
map8to1(mapBlack, width, height, *black);
free(mapBlack);
}
return PM3_SUCCESS;
return colormap8;
}
static void read_black(uint32_t i, uint8_t *l, uint8_t model_nr, const uint8_t *black) {
@ -1003,7 +563,7 @@ static int start_drawing(uint8_t model_nr, uint8_t *black, uint8_t *red) {
return PM3_SUCCESS;
}
static int CmdHF14AWSLoadBmp(const char *Cmd) {
static int CmdHF14AWSLoad(const char *Cmd) {
char desc[800] = {0};
for (uint8_t i = 0; i < MEND; i++) {
@ -1029,24 +589,25 @@ static int CmdHF14AWSLoadBmp(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_int1("m", NULL, "<nr>", modeldesc),
arg_lit0("s", "save", "save dithered version in filename-[n].bmp, only for RGB BMP"),
arg_str1("f", "file", "<fn>", "specify filename[.bmp] to upload to tag"),
arg_str1("f", "file", "<fn>", "specify image to upload to tag"),
arg_str0("s", "save", "<fn>", "save paletized version in file"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int model_nr = arg_get_int_def(ctx, 1, -1);
bool save_conversions = arg_get_lit(ctx, 2);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
int infilelen, outfilelen;
char infile[FILE_PATH_SIZE];
char outfile[FILE_PATH_SIZE];
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)infile, FILE_PATH_SIZE, &infilelen);
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)outfile, FILE_PATH_SIZE, &outfilelen);
CLIParserFree(ctx);
//Validations
if (fnlen < 1) {
PrintAndLogEx(WARNING, "Missing filename");
if (infilelen < 1) {
PrintAndLogEx(WARNING, "Missing input file");
return PM3_EINVARG;
}
if (model_nr == -1) {
@ -1058,63 +619,76 @@ static int CmdHF14AWSLoadBmp(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t *bmp = NULL;
uint8_t *black = NULL;
uint8_t *red = NULL;
size_t bytes_read = 0;
if (loadFile_safe(filename, ".bmp", (void **)&bmp, &bytes_read) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Could not find file " _YELLOW_("%s"), filename);
bool model_has_red = model_nr == M1in54B || model_nr == M2in13B;
gdImagePtr rgb_img = gdImageCreateFromFile(infile);
if (!rgb_img) {
PrintAndLogEx(WARNING, "Could not load image from " _YELLOW_("%s"), infile);
return PM3_EFILE;
}
if (bmp == NULL) {
if (
gdImageSX(rgb_img) != models[model_nr].width ||
gdImageSY(rgb_img) != models[model_nr].height
) {
PrintAndLogEx(WARNING, "Image size does not match panel size");
gdImageDestroy(rgb_img);
return PM3_EFILE;
}
int pal_len = 2;
int pal[3];
pal[0] = gdTrueColorAlpha(0xFF, 0xFF, 0xFF, 0); // White
pal[1] = gdTrueColorAlpha(0x00, 0x00, 0x00, 0); // Black
if (model_has_red) {
pal_len = 3;
pal[2] = gdTrueColorAlpha(0xFF, 0x00, 0x00, 0); // Red
}
gdImagePtr pal_img = img_palettize(rgb_img, pal, pal_len);
gdImageDestroy(rgb_img);
if (!pal_img) {
PrintAndLogEx(WARNING, "Could not convert image");
return PM3_EMALLOC;
}
if (bytes_read < sizeof(bmp_header_t)) {
free(bmp);
return PM3_ESOFT;
if (outfilelen && !gdImageFile(pal_img, outfile)) {
PrintAndLogEx(WARNING, "Could not save converted image");
}
int depth = picture_bit_depth(bmp, bytes_read, model_nr);
if (depth == PM3_ESOFT) {
PrintAndLogEx(ERR, "Error, BMP file is too small");
free(bmp);
return PM3_ESOFT;
} else if (depth == 1) {
PrintAndLogEx(DEBUG, "BMP file is a bitmap");
if (read_bmp_bitmap(bmp, bytes_read, model_nr, &black, &red) != PM3_SUCCESS) {
free(bmp);
return PM3_ESOFT;
}
} else if (depth == 24) {
PrintAndLogEx(DEBUG, "BMP file is a RGB");
if (read_bmp_rgb(bmp, bytes_read, model_nr, &black, &red, filename, save_conversions) != PM3_SUCCESS) {
free(bmp);
return PM3_ESOFT;
}
} else if (depth == 32) {
PrintAndLogEx(DEBUG, "BMP file is a RGBA, we will ignore the Alpha channel");
if (read_bmp_rgb(bmp, bytes_read, model_nr, &black, &red, filename, save_conversions) != PM3_SUCCESS) {
free(bmp);
return PM3_ESOFT;
}
} else {
PrintAndLogEx(ERR, "Error, BMP color depth %i not supported. Must be 1 (BW), 24 (RGB) or 32 (RGBA)", depth);
free(bmp);
return PM3_ESOFT;
uint8_t * black_plane = map8to1(pal_img, 1);
if (!black_plane) {
PrintAndLogEx(WARNING, "Could not convert image to bit plane");
gdImageDestroy(pal_img);
return PM3_EMALLOC;
}
free(bmp);
start_drawing(model_nr, black, red);
free(black);
if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
free(red);
uint8_t * red_plane = NULL;
if (model_has_red) {
red_plane = map8to1(pal_img, 2);
if (!red_plane) {
PrintAndLogEx(WARNING, "Could not convert image to bit plane");
free(black_plane);
gdImageDestroy(pal_img);
return PM3_EMALLOC;
}
}
return PM3_SUCCESS;
gdImageDestroy(pal_img);
int res = start_drawing(model_nr, black_plane, red_plane);
free(black_plane);
if (red_plane) {
free(red_plane);
}
return res;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"loadbmp", CmdHF14AWSLoadBmp, IfPm3Iso14443a, "Load BMP file to Waveshare NFC ePaper"},
{"load", CmdHF14AWSLoad, IfPm3Iso14443a, "Load image file to Waveshare NFC ePaper"},
{NULL, NULL, NULL, NULL}
};

156
client/src/imgutils.c Normal file
View file

@ -0,0 +1,156 @@
#include <assert.h>
#include "imgutils.h"
struct ycbcr_t {
int y;
int cb;
int cr;
};
static void rgb_to_ycbcr(int rgb, struct ycbcr_t * ycbcr) {
int r = gdTrueColorGetRed(rgb);
int g = gdTrueColorGetGreen(rgb);
int b = gdTrueColorGetBlue(rgb);
/*
* Below is a fixed-point version of the following code:
* ycbcr->y = r * 0.29900 + g * 0.58700 + b * 0.11400;
* ycbcr->cb = r * -0.16874 + g * -0.33126 + b * 0.50000 + 128;
* ycbcr->cr = r * 0.50000 + g * -0.41869 + b * -0.08131 + 128;
*/
ycbcr->y = (r * 19595 + g * 38470 + b * 7471) / 65536;
ycbcr->cb = (r * -11059 + g * -21709 + b * 32768) / 65536 + 128;
ycbcr->cr = (r * 32768 + g * -27439 + b * -5329) / 65536 + 128;
}
static inline void cap_comp(int * x) {
if (*x < 0) {
*x = 0;
} else if (*x > 255) {
*x = 255;
}
}
/*
* The following function implements a Floyd-Steinberg in YCbCr color space.
*
* Using this colorspace, the Euclidean distance between colors is closer to human perception than
* in sRGB, which results in a more accurate color rendering.
*
* A comparison can be found at https://twitter.com/Socram4x8/status/1733157380097995205/photo/1.
*/
gdImagePtr img_palettize(gdImagePtr rgb, int * palette, int palette_size) {
assert(rgb != NULL);
assert(palette != NULL);
assert(palette_size >= 2 && palette_size < 256);
// Create paletized image
gdImagePtr res = gdImageCreate(gdImageSX(rgb), gdImageSY(rgb));
if (!res) {
return NULL;
}
// Allocate space for palette in YCbCr
struct ycbcr_t * pal_ycbcr = malloc(palette_size * sizeof(struct ycbcr_t));
if (!pal_ycbcr) {
gdImageDestroy(res);
return NULL;
}
/*
* Initialize the column's error array.
*
* Note that we are storing two extra values so we don't have to do boundary checking at
* the left and right edges of the image.
*/
struct ycbcr_t * forward = malloc((gdImageSX(rgb) + 2) * sizeof(struct ycbcr_t));
if (!forward) {
free(pal_ycbcr);
gdImageDestroy(res);
return NULL;
}
// Convert palette to YCbCr and allocate in image
for (int i = 0; i < palette_size; i++) {
int c = palette[i];
rgb_to_ycbcr(c, pal_ycbcr + i);
gdImageColorAllocate(res, gdTrueColorGetRed(c), gdTrueColorGetGreen(c), gdTrueColorGetBlue(c));
}
for (int y = 0; y < gdImageSY(rgb); y++) {
// Load current row error and reset its storage
struct ycbcr_t row_err = forward[1];
forward[1].y = forward[1].cb = forward[1].cr = 0;
for (int x = 0; x < gdImageSX(rgb); x++) {
struct ycbcr_t pix;
rgb_to_ycbcr(gdImageGetTrueColorPixel(rgb, x, y), &pix);
// Add error for current pixel
pix.y += row_err.y;
pix.cb += row_err.cb;
pix.cr += row_err.cr;
// Cap in case it went to imaginary color territory
cap_comp(&pix.y);
cap_comp(&pix.cb);
cap_comp(&pix.cr);
/*
* Iterate through all candidate colors and find the nearest one using the
* squared Euclidean distance.
*/
int best_idx = 0;
struct ycbcr_t best_err = { 0 };
int best_score = 0x7FFFFFFF;
for (int can_idx = 0; can_idx < palette_size; can_idx++) {
struct ycbcr_t can_err = {
.y = pix.y - pal_ycbcr[can_idx].y,
.cb = pix.cb - pal_ycbcr[can_idx].cb,
.cr = pix.cr - pal_ycbcr[can_idx].cr,
};
int can_score = (
can_err.y * can_err.y +
can_err.cb * can_err.cb +
can_err.cr * can_err.cr
);
if (can_score < best_score) {
best_idx = can_idx;
best_score = can_score;
best_err = can_err;
}
}
// Set current pixel
gdImageSetPixel(res, x, y, best_idx);
// Propagate error within the current row, to the pixel to the right
row_err.y = best_err.y * 7 / 16 + forward[x + 2].y;
row_err.cb = best_err.cb * 7 / 16 + forward[x + 2].cb;
row_err.cr = best_err.cr * 7 / 16 + forward[x + 2].cr;
// Add error to bottom left
forward[x + 0].y += best_err.y * 3 / 16;
forward[x + 0].cb += best_err.cb * 3 / 16;
forward[x + 0].cr += best_err.cr * 3 / 16;
// Add error to bottom center
forward[x + 1].y += best_err.y * 5 / 16;
forward[x + 1].cb += best_err.cb * 5 / 16;
forward[x + 1].cr += best_err.cr * 5 / 16;
// Set error to bottom right
forward[x + 2].y = best_err.y * 1 / 16;
forward[x + 2].cb = best_err.cb * 1 / 16;
forward[x + 2].cr = best_err.cr * 1 / 16;
}
}
free(forward);
free(pal_ycbcr);
return res;
}

25
client/src/imgutils.h Normal file
View file

@ -0,0 +1,25 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Image utilities
//-----------------------------------------------------------------------------
#ifndef IMGUTILS_H__
#define IMGUTILS_H__
#include <gd.h>
gdImagePtr img_palettize(gdImagePtr rgb, int * palette, int palette_size);
#endif