diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 37d628655..d7726f6e0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -35,7 +35,7 @@ jobs: run: brew tap RfidResearchGroup/proxmark3 - name: Install dependencies - run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl + run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl gd continue-on-error: true - name: Install Python dependencies @@ -75,7 +75,7 @@ jobs: run: brew tap RfidResearchGroup/proxmark3 - name: Install dependencies - run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl + run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl gd continue-on-error: true - name: Install Python dependencies @@ -116,7 +116,7 @@ jobs: run: brew tap RfidResearchGroup/proxmark3 - name: Install dependencies - run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl + run: brew install readline coreutils qt5 RfidResearchGroup/proxmark3/arm-none-eabi-gcc openssl gd continue-on-error: true - name: Install Python dependencies diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d81ea5db2..969fe2335 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -26,7 +26,7 @@ jobs: run: sudo apt-get update - name: Install dependencies - run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev + run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev libgd-dev - name: Install Python dependencies run: | @@ -56,7 +56,7 @@ jobs: run: sudo apt-get update - name: Install dependencies - run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev + run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev libgd-dev - name: Install Python dependencies run: | @@ -87,7 +87,7 @@ jobs: run: sudo apt-get update - name: Install dependencies - run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev + run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.2-dev liblua5.2-0 lua5.2 sed libssl-dev libgd-dev - name: Install Python dependencies run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d913a35fc..77b160edf 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -119,6 +119,7 @@ jobs: python3-dev libpython3-all-dev libssl-dev + libgd-dev - name: Install Python dependencies run: | diff --git a/.gitignore b/.gitignore index 90e9858f8..9c42779fa 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ tools/jtag_openocd/openocd_configuration tools/mfd_aes_brute/mfd_aes_brute tools/mfd_aes_brute/mfd_multi_brute tools/mfd_aes_brute/brute_key +!tools/lena.bmp fpga/__build* diff --git a/CHANGELOG.md b/CHANGELOG.md index f3da0a0ff..e41c2e35c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added troubleshooting entry - ARM architecture error (@francis2054) - Fixed `lf pyramid sim` - wrong parameter handling (@iceman1001) - Fixed bootloader - Ignore jitters when pressing the button (@wh201906) + - Changed `hf waveshare` - image loading and processing is now done using [GDlib](https://github.com/libgd/libgd) (@socram8888) ## [Steamboat Willie.4.17768][2024-01-03] - Changed `mem spiffs dump -t` - now supports downloading direct into trace buffer (@hazardousvoltage) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d92c15fbe..88bbb9ead 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -97,11 +97,12 @@ if (CMAKE_TOOLCHAIN_FILE) set(EMBED_READLINE ON) set(EMBED_BZIP2 ON) set(EMBED_LZ4 ON) + set(EMBED_GD ON) endif (CMAKE_TOOLCHAIN_FILE) -if (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4) +if (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4 OR EMBED_GD) include(ExternalProject) -endif (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4) +endif (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4 OR EMBED_GD) if (NOT SKIPREADLINE EQUAL 1) if (APPLE) @@ -213,6 +214,27 @@ if (LZ4_INCLUDE_DIRS AND LZ4_LIBRARIES) set(LZ4_FOUND ON) endif (LZ4_INCLUDE_DIRS AND LZ4_LIBRARIES) +if (NOT SKIPGD EQUAL 1) + if (EMBED_GD) + cmake_policy(SET CMP0114 NEW) + set(GD_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/deps/gd) + # Specify SOURCE_DIR will cause some errors + ExternalProject_Add(gd + URL https://github.com/libgd/libgd/releases/download/gd-2.3.3/libgd-2.3.3.tar.gz + URL_HASH SHA256=dd3f1f0bb016edcc0b2d082e8229c822ad1d02223511997c80461481759b1ed2 + PREFIX deps/gd + CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DENABLE_CPP=0 -DCMAKE_INSTALL_PREFIX=. + PATCH_COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/deps/gd-static.patch | patch -p1 + ) + ExternalProject_Add_StepTargets(gd configure build install) + set(GD_INCLUDE_DIRS ${GD_BUILD_DIR}/src/gd-build/include) + set(GD_LIBRARIES ${GD_BUILD_DIR}/src/gd-build/lib/libgd.a) + set(GD_FOUND ON) + else (EMBED_GD) + pkg_search_module(GD QUIET gdlib) + endif (EMBED_GD) +endif (NOT SKIPGD EQUAL 1) + if (NOT SKIPWHEREAMISYSTEM EQUAL 1) find_path(WHEREAMI_INCLUDE_DIRS whereami.h) find_library(WHEREAMI_LIBRARIES whereami) @@ -332,7 +354,6 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfthinfilm.c ${PM3_ROOT}/client/src/cmdhftopaz.c ${PM3_ROOT}/client/src/cmdhfvas.c - ${PM3_ROOT}/client/src/cmdhfwaveshare.c ${PM3_ROOT}/client/src/cmdhfxerox.c ${PM3_ROOT}/client/src/cmdhw.c ${PM3_ROOT}/client/src/cmdlf.c @@ -495,6 +516,17 @@ if (LZ4_FOUND) set(ADDITIONAL_LNK ${LZ4_LIBRARIES} ${ADDITIONAL_LNK}) endif (LZ4_FOUND) +if (NOT SKIPGD EQUAL 1 AND GD_FOUND) + set(ADDITIONAL_DIRS ${GD_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) + set(ADDITIONAL_LNK ${GD_LIBRARIES} ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNKDIRS ${GD_LIBRARY_DIRS} ${ADDITIONAL_LNKDIRS}) + set(TARGET_SOURCES + ${PM3_ROOT}/client/src/imgutils.c + ${PM3_ROOT}/client/src/cmdhfwaveshare.c + ${TARGET_SOURCES}) + add_definitions("-DHAVE_GD") +endif (NOT SKIPGD EQUAL 1 AND GD_FOUND) + if (WHEREAMI_FOUND) set(ADDITIONAL_DIRS ${WHEREAMI_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) set(ADDITIONAL_LNK ${WHEREAMI_LIBRARIES} ${ADDITIONAL_LNK}) @@ -547,6 +579,18 @@ else (LZ4_FOUND) message(SEND_ERROR "LZ4 library: LZ4 not found") endif (LZ4_FOUND) +if (SKIPGD EQUAL 1) + message(STATUS "GD library: skipped") +elseif (GD_FOUND) + if (EMBED_GD) + message(STATUS "GD library: embedded") + else (EMBED_GD) + message(STATUS "GD library: system library found") + endif (EMBED_GD) +else (SKIPGD EQUAL 1) + message(STATUS "GD library: GD not found, disabled") +endif (SKIPGD EQUAL 1) + if (SKIPJANSSONSYSTEM EQUAL 1) message(STATUS "Jansson library: local library forced") else (SKIPJANSSONSYSTEM EQUAL 1) @@ -721,14 +765,14 @@ endif (NOT SKIPPTHREAD EQUAL 1) if (NOT SKIPPYTHON EQUAL 1) # OSX have a hard time compiling python3 dependency with older cmake. if (PYTHON3EMBED_FOUND OR PYTHON3_FOUND) - if (NOT CMAKE_VERSION VERSION_LESS 3.13) - target_link_directories(proxmark3 PRIVATE ${ADDITIONAL_LNKDIRS}) - elseif (APPLE) - message( SEND_ERROR "Your CMAKE version is too old for Apple platform, please update to a version >=3.13" ) - endif (NOT CMAKE_VERSION VERSION_LESS 3.13) + if (CMAKE_VERSION VERSION_LESS 3.13) + message( SEND_ERROR "Your CMAKE version is too old for Apple platform, please update to a version >=3.13" ) + endif (CMAKE_VERSION VERSION_LESS 3.13) endif (PYTHON3EMBED_FOUND OR PYTHON3_FOUND) endif (NOT SKIPPYTHON EQUAL 1) +target_link_directories(proxmark3 PRIVATE ${ADDITIONAL_LNKDIRS}) + install(TARGETS proxmark3 DESTINATION "bin") install(DIRECTORY cmdscripts lualibs luascripts pyscripts resources dictionaries DESTINATION "share/proxmark3") diff --git a/client/Makefile b/client/Makefile index fc0dedb82..adbd2c0ce 100644 --- a/client/Makefile +++ b/client/Makefile @@ -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) @@ -410,6 +421,10 @@ ifeq ($(PYTHON_FOUND),1) PM3CFLAGS += -DHAVE_PYTHON endif +ifeq ($(GD_FOUND),1) + PM3CFLAGS += -DHAVE_GD +endif + ifeq ($(SWIG_LUA_FOUND),1) PM3CFLAGS += -DHAVE_LUA_SWIG endif @@ -510,6 +525,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 +630,6 @@ SRCS = mifare/aiddesfire.c \ cmdhftopaz.c \ cmdhftexkom.c \ cmdhfvas.c \ - cmdhfwaveshare.c \ cmdhfxerox.c \ cmdhw.c \ cmdlf.c \ @@ -738,6 +762,12 @@ SRCS += bucketsort.c \ lfdemod.c \ util_posix.c +ifeq ($(GD_FOUND),1) + # electronic shelf labels + SRCS += imgutils.c \ + cmdhfwaveshare.c +endif + # swig SWIGSRCS = diff --git a/client/deps/gd-static.patch b/client/deps/gd-static.patch new file mode 100644 index 000000000..c8447c845 --- /dev/null +++ b/client/deps/gd-static.patch @@ -0,0 +1,11 @@ +--- a/src/gd.h 2024-01-07 16:51:43.749223000 +0100 ++++ b/src/gd.h 2024-01-07 16:52:34.162291600 +0100 +@@ -45,7 +45,7 @@ + the gd sources in a project. */ + + /* http://gcc.gnu.org/wiki/Visibility */ +-#if defined(_WIN32) || defined(CYGWIN) || defined(_WIN32_WCE) ++#if 0 // Disable DLL annotations when building statically. Needed for embedding under MinGW. + # ifdef BGDWIN32 + # ifdef NONDLL + # define BGD_EXPORT_DATA_PROT diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 15a74d38c..efa334216 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -98,11 +98,12 @@ if (CMAKE_TOOLCHAIN_FILE) set(EMBED_READLINE ON) set(EMBED_BZIP2 ON) set(EMBED_LZ4 ON) + set(EMBED_GD ON) endif (CMAKE_TOOLCHAIN_FILE) -if (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4) +if (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4 OR EMBED_GD) include(ExternalProject) -endif (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4) +endif (EMBED_READLINE OR EMBED_BZIP2 OR EMBED_LZ4 OR EMBED_GD) if (NOT SKIPREADLINE EQUAL 1) if (APPLE) @@ -214,6 +215,27 @@ if (LZ4_INCLUDE_DIRS AND LZ4_LIBRARIES) set(LZ4_FOUND ON) endif (LZ4_INCLUDE_DIRS AND LZ4_LIBRARIES) +if (NOT SKIPGD EQUAL 1) + if (EMBED_GD) + cmake_policy(SET CMP0114 NEW) + set(GD_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/deps/gd) + # Specify SOURCE_DIR will cause some errors + ExternalProject_Add(gd + URL https://github.com/libgd/libgd/releases/download/gd-2.3.3/libgd-2.3.3.tar.gz + URL_HASH SHA256=dd3f1f0bb016edcc0b2d082e8229c822ad1d02223511997c80461481759b1ed2 + PREFIX deps/gd + CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DENABLE_CPP=0 -DCMAKE_INSTALL_PREFIX=. + PATCH_COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/deps/gd-static.patch | patch -p1 + ) + ExternalProject_Add_StepTargets(gd configure build install) + set(GD_INCLUDE_DIRS ${GD_BUILD_DIR}/src/gd-build/include) + set(GD_LIBRARIES ${GD_BUILD_DIR}/src/gd-build/lib/libgd.a) + set(GD_FOUND ON) + else (EMBED_GD) + pkg_search_module(GD QUIET gdlib) + endif (EMBED_GD) +endif (NOT SKIPGD EQUAL 1) + if (NOT SKIPWHEREAMISYSTEM EQUAL 1) find_path(WHEREAMI_INCLUDE_DIRS whereami.h) find_library(WHEREAMI_LIBRARIES whereami) @@ -333,7 +355,6 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfthinfilm.c ${PM3_ROOT}/client/src/cmdhftopaz.c ${PM3_ROOT}/client/src/cmdhfvas.c - ${PM3_ROOT}/client/src/cmdhfwaveshare.c ${PM3_ROOT}/client/src/cmdhfxerox.c ${PM3_ROOT}/client/src/cmdhw.c ${PM3_ROOT}/client/src/cmdlf.c @@ -496,6 +517,17 @@ if (LZ4_FOUND) set(ADDITIONAL_LNK ${LZ4_LIBRARIES} ${ADDITIONAL_LNK}) endif (LZ4_FOUND) +if (NOT SKIPGD EQUAL 1 AND GD_FOUND) + set(ADDITIONAL_DIRS ${GD_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) + set(ADDITIONAL_LNK ${GD_LIBRARIES} ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNKDIRS ${GD_LIBRARY_DIRS} ${ADDITIONAL_LNKDIRS}) + set(TARGET_SOURCES + ${PM3_ROOT}/client/src/imgutils.c + ${PM3_ROOT}/client/src/cmdhfwaveshare.c + ${TARGET_SOURCES}) + add_definitions("-DHAVE_GD") +endif (NOT SKIPGD EQUAL 1 AND GD_FOUND) + if (WHEREAMI_FOUND) set(ADDITIONAL_DIRS ${WHEREAMI_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) set(ADDITIONAL_LNK ${WHEREAMI_LIBRARIES} ${ADDITIONAL_LNK}) @@ -548,6 +580,18 @@ else (LZ4_FOUND) message(SEND_ERROR "LZ4 library: LZ4 not found") endif (LZ4_FOUND) +if (SKIPGD EQUAL 1) + message(STATUS "GD library: skipped") +elseif (GD_FOUND) + if (EMBED_GD) + message(STATUS "GD library: embedded") + else (EMBED_GD) + message(STATUS "GD library: system library found") + endif (EMBED_GD) +else (SKIPGD EQUAL 1) + message(STATUS "GD library: GD not found, disabled") +endif (SKIPGD EQUAL 1) + if (SKIPJANSSONSYSTEM EQUAL 1) message(STATUS "Jansson library: local library forced") else (SKIPJANSSONSYSTEM EQUAL 1) @@ -722,10 +766,10 @@ endif (NOT SKIPPTHREAD EQUAL 1) if (NOT SKIPPYTHON EQUAL 1) # OSX have a hard time compiling python3 dependency with older cmake. if (PYTHON3EMBED_FOUND OR PYTHON3_FOUND) - if (NOT CMAKE_VERSION VERSION_LESS 3.13) - target_link_directories(pm3rrg_rdv4 PRIVATE ${ADDITIONAL_LNKDIRS}) - elseif (APPLE) - message( SEND_ERROR "Your CMAKE version is too old for Apple platform, please update to a version >=3.13" ) - endif (NOT CMAKE_VERSION VERSION_LESS 3.13) + if (CMAKE_VERSION VERSION_LESS 3.13) + message( SEND_ERROR "Your CMAKE version is too old for Apple platform, please update to a version >=3.13" ) + endif (CMAKE_VERSION VERSION_LESS 3.13) endif (PYTHON3EMBED_FOUND OR PYTHON3_FOUND) endif (NOT SKIPPYTHON EQUAL 1) + +target_link_directories(pm3rrg_rdv4 PRIVATE ${ADDITIONAL_LNKDIRS}) diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index f3a45d92a..2de94680e 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -566,7 +566,9 @@ static command_t CommandTable[] = { {"thinfilm", CmdHFThinfilm, AlwaysAvailable, "{ Thinfilm RFIDs... }"}, {"topaz", CmdHFTopaz, AlwaysAvailable, "{ TOPAZ (NFC Type 1) RFIDs... }"}, {"vas", CmdHFVAS, AlwaysAvailable, "{ Apple Value Added Service }"}, +#ifdef HAVE_GD {"waveshare", CmdHFWaveshare, AlwaysAvailable, "{ Waveshare NFC ePaper... }"}, +#endif {"xerox", CmdHFXerox, AlwaysAvailable, "{ Fuji/Xerox cartridge RFIDs... }"}, {"-----------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"}, {"help", CmdHelp, AlwaysAvailable, "This help"}, diff --git a/client/src/cmdhfwaveshare.c b/client/src/cmdhfwaveshare.c index 8bb9eadc8..7dfef808c 100644 --- a/client/src/cmdhfwaveshare.c +++ b/client/src/cmdhfwaveshare.c @@ -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,13 +563,13 @@ 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++) { snprintf(desc + strlen(desc), sizeof(desc) - strlen(desc), - "hf waveshare loadbmp -f myfile -m %2u -> %s ( %u, %u )\n", + "hf waveshare load -f myfile -m %2u -> %s ( %u, %u )\n", i, models[i].desc, models[i].width, @@ -1018,8 +578,8 @@ static int CmdHF14AWSLoadBmp(const char *Cmd) { } CLIParserContext *ctx; - CLIParserInit(&ctx, "hf waveshare loadbmp", - "Load BMP file to Waveshare NFC ePaper.", + CLIParserInit(&ctx, "hf waveshare load", + "Load image file to Waveshare NFC ePaper", desc ); @@ -1029,24 +589,25 @@ static int CmdHF14AWSLoadBmp(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_int1("m", NULL, "", modeldesc), - arg_lit0("s", "save", "save dithered version in filename-[n].bmp, only for RGB BMP"), - arg_str1("f", "file", "", "specify filename[.bmp] to upload to tag"), + arg_str1("f", "file", "", "specify image to upload to tag"), + arg_str0("s", "save", "", "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) { @@ -1057,64 +618,85 @@ static int CmdHF14AWSLoadBmp(const char *Cmd) { PrintAndLogEx(WARNING, "Unknown model"); return PM3_EINVARG; } + if (!g_session.pm3_present && !outfilelen) { + PrintAndLogEx(WARNING, "Offline - can only perform image conversion"); + return PM3_ENOTTY; + } - 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) { + if (gdImageFile(pal_img, outfile)) { + PrintAndLogEx(INFO, "Save converted image to " _YELLOW_("%s"), outfile); + gdImageDestroy(pal_img); + return PM3_SUCCESS; + } else { + PrintAndLogEx(WARNING, "Could not save converted image", outfile); + gdImageDestroy(pal_img); + return PM3_EFILE; + } } - 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); + 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, AlwaysAvailable, "Load image file to Waveshare NFC ePaper"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/imgutils.c b/client/src/imgutils.c new file mode 100644 index 000000000..a58a5b29a --- /dev/null +++ b/client/src/imgutils.c @@ -0,0 +1,159 @@ + +#include +#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 = calloc(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. + * + * To reduce shifts and increase accuracy, each entry is stored with 16x times the error, + * and gets divided by that amount when it is read. + */ + struct ycbcr_t * forward = calloc(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 / 16; + pix.cb += row_err.cb / 16; + pix.cr += row_err.cr / 16; + + // 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 + forward[x + 2].y; + row_err.cb = best_err.cb * 7 + forward[x + 2].cb; + row_err.cr = best_err.cr * 7 + forward[x + 2].cr; + + // Add error to bottom left + forward[x + 0].y += best_err.y * 3; + forward[x + 0].cb += best_err.cb * 3; + forward[x + 0].cr += best_err.cr * 3; + + // Add error to bottom center + forward[x + 1].y += best_err.y * 5; + forward[x + 1].cb += best_err.cb * 5; + forward[x + 1].cr += best_err.cr * 5; + + // Set error to bottom right + forward[x + 2].y = best_err.y * 1; + forward[x + 2].cb = best_err.cb * 1; + forward[x + 2].cr = best_err.cr * 1; + } + } + + free(forward); + free(pal_ycbcr); + return res; +} diff --git a/client/src/imgutils.h b/client/src/imgutils.h new file mode 100644 index 000000000..1ffa07833 --- /dev/null +++ b/client/src/imgutils.h @@ -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 + +gdImagePtr img_palettize(gdImagePtr rgb, int * palette, int palette_size); + +#endif diff --git a/doc/md/Installation_Instructions/Linux-Installation-Instructions.md b/doc/md/Installation_Instructions/Linux-Installation-Instructions.md index 496e14be2..c7f10b26a 100644 --- a/doc/md/Installation_Instructions/Linux-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/Linux-Installation-Instructions.md @@ -72,6 +72,9 @@ you can skip the installation of `qtbase5-dev`. 👉 If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `libpython3-dev`. +👉 If you don't need support for NFC ePaper devices, +you can skip the installation of `libgd-dev`. + ### Failed to load module... ⚠️ If you get some (non blocking) error at runtime such as _Gtk-Message: Failed to load module "canberra-gtk-module"_ you may have to install `libcanberra-gtk-module`. @@ -82,7 +85,7 @@ you may have to install `libcanberra-gtk-module`. ```sh sudo pacman -Syu git base-devel readline bzip2 lz4 arm-none-eabi-gcc \ -arm-none-eabi-newlib qt5-base bluez python --needed +arm-none-eabi-newlib qt5-base bluez python gd --needed ``` ### If you don't need... @@ -95,6 +98,9 @@ you can skip the installation of `qt5-base`. 👉 If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `python`. +👉 If you don't need support for NFC ePaper devices, +you can skip the installation of `gd`. + ## On Fedora ^[Top](#top) @@ -102,7 +108,7 @@ you can skip the installation of `python`. ```sh sudo dnf install git make gcc gcc-c++ arm-none-eabi-gcc-cs arm-none-eabi-newlib \ readline-devel bzip2-devel lz4-devel qt5-qtbase-devel bluez-libs-devel \ -python3-devel libatomic openssl-devel +python3-devel libatomic openssl-devel gd-devel ``` ### If you don't need... @@ -115,6 +121,9 @@ you can skip the installation of `qt5-qtbase-devel`. 👉 If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `python3-devel`. +👉 If you don't need support for NFC ePaper devices, +you can skip the installation of `gd-devel`. + ## On openSUSE ^[Top](#top) @@ -122,7 +131,8 @@ you can skip the installation of `python3-devel`. ```sh sudo zypper install git patterns-devel-base-devel_basis gcc-c++ \ readline-devel libbz2-devel liblz4-devel cross-arm-none-gcc9 \ -cross-arm-none-newlib-devel python3-devel libqt5-qtbase-devel libopenssl-devel +cross-arm-none-newlib-devel python3-devel libqt5-qtbase-devel \ +libopenssl-devel gd-devel ``` Note that Bluez is not available on openSUSE so the native Bluetooth support won't be available in the client. @@ -134,6 +144,9 @@ you can skip the installation of `libqt5-qtbase-devel`. 👉 If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `python3-devel`. +👉 If you don't need support for NFC ePaper devices, +you can skip the installation of `gd-devel`. + # Clone the repository ^[Top](#top) diff --git a/doc/md/Installation_Instructions/Windows-Installation-Instructions.md b/doc/md/Installation_Instructions/Windows-Installation-Instructions.md index f14f772aa..d3056ee5c 100644 --- a/doc/md/Installation_Instructions/Windows-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/Windows-Installation-Instructions.md @@ -169,11 +169,12 @@ Install dependencies: ```sh sudo apt-get install --no-install-recommends git ca-certificates build-essential pkg-config \ libreadline-dev gcc-arm-none-eabi libnewlib-dev \ -libbz2-dev liblz4-dev libpython3-dev qtbase5-dev libssl-dev +libbz2-dev liblz4-dev libpython3-dev qtbase5-dev libssl-dev libgd-dev ``` _note_ If you don't need the graphical components of the Proxmark3 client, you can skip the installation of `qtbase5-dev`. If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `libpython3-dev`. +If you don't need support for NFC ePaper devices, you can skip the installation of `libgd-dev`. ## Clone the Iceman repository ^[Top](#top) diff --git a/doc/md/Installation_Instructions/Windows-WSL2-Installation-Instructions.md b/doc/md/Installation_Instructions/Windows-WSL2-Installation-Instructions.md index 0c720b62e..ca889aa00 100644 --- a/doc/md/Installation_Instructions/Windows-WSL2-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/Windows-WSL2-Installation-Instructions.md @@ -108,12 +108,14 @@ then, install proxmark dependencies: sudo apt-get install --no-install-recommends \ git ca-certificates build-essential pkg-config \ libreadline-dev gcc-arm-none-eabi libnewlib-dev \ - libbz2-dev liblz4-dev libpython3-dev qtbase5-dev libssl-dev + libbz2-dev liblz4-dev libpython3-dev qtbase5-dev \ + libssl-dev libgd-dev ``` _note_ If you don't need the graphical components of the Proxmark3 client, you can skip the installation of `qtbase5-dev`. If you don't need support for Python3 scripts in the Proxmark3 client, you can skip the installation of `libpython3-dev`. +If you don't need support for NFC ePaper devices, you can skip the installation of `libgd-dev`. ## X Server Installation ^[Top](#top) diff --git a/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md b/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md index c4622641a..6c1ac3eca 100644 --- a/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md @@ -161,7 +161,7 @@ These instructions will show how to setup the environment on OSX to the point wh 2. Install dependencies: ``` -brew install readline qt5 pkgconfig coreutils +brew install readline qt5 gd pkgconfig coreutils brew install RfidResearchGroup/proxmark3/arm-none-eabi-gcc ``` 3. (optional) Install makefile dependencies: diff --git a/doc/md/Installation_Instructions/macOS-MacPorts-Installation-Instructions.md b/doc/md/Installation_Instructions/macOS-MacPorts-Installation-Instructions.md index 2c36d1371..c86615085 100644 --- a/doc/md/Installation_Instructions/macOS-MacPorts-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/macOS-MacPorts-Installation-Instructions.md @@ -61,7 +61,7 @@ These instructions will show how to setup the environment on OSX to the point wh 2. Install dependencies: ```bash - sudo port install readline jansson lua52 python311 bzip2 lz4 openssl11 arm-none-eabi-gcc arm-none-eabi-binutils coreutils qt5 qt5-qtbase pkgconfig + sudo port install readline jansson lua52 python311 bzip2 lz4 openssl11 arm-none-eabi-gcc arm-none-eabi-binutils coreutils qt5 qt5-qtbase gd2 pkgconfig ``` 3. Clamp Python version for pkg-config diff --git a/tools/lena.bmp b/tools/lena.bmp new file mode 100644 index 000000000..d03cf8509 Binary files /dev/null and b/tools/lena.bmp differ diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index e56ce5d25..e1d574738 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -535,6 +535,7 @@ while true; do if ! CheckExecute "emv test" "$CLIENTBIN -c 'emv test'" "Test\(s\) \[ ok"; then break; fi if ! CheckExecute "hf cipurse test" "$CLIENTBIN -c 'hf cipurse test'" "Tests \[ ok"; then break; fi if ! CheckExecute "hf mfdes test" "$CLIENTBIN -c 'hf mfdes test'" "Tests \[ ok"; then break; fi + if ! CheckExecute "hf waveshare load" "$CLIENTBIN -c 'hf waveshare load -m 6 -f tools/lena.bmp -s dither.bmp' && echo '34ff55fe7257876acf30dae00eb0e439 dither.bmp' | md5sum -c" "dither.bmp: OK"; then break; fi fi echo -e "\n------------------------------------------------------------" echo -e "Tests [ ${C_GREEN}OK${C_NC} ] ${C_OK}\n"