From 37706febb12a38145741f683939668ab4f399e38 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 15:22:37 +0200 Subject: [PATCH 01/63] add missed for for last commit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 0f3bcaa..383a056 100644 --- a/index.html +++ b/index.html @@ -222,7 +222,7 @@
- +
From e543cb3f4a0aa9d3d3957457d3603c21b55d6e0a Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 15:22:42 +0200 Subject: [PATCH 02/63] update deps --- package-lock.json | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19c9cbe..226f538 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,10 +166,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -298,10 +299,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -376,10 +378,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1421,10 +1424,20 @@ } }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -1837,10 +1850,11 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } From 8e2b16453a143811a506941619c7486f3e543f53 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 15:23:18 +0200 Subject: [PATCH 03/63] update gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ebf1e49..b94a42d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules venv/ -.venv/ +.venv +__pycache__ From f55e0fc9634e17c89900d57859daff4430a06690 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 15:41:42 +0200 Subject: [PATCH 04/63] Fix error with width/height input using local this --- js/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/script.js b/js/script.js index 591491b..74042d6 100644 --- a/js/script.js +++ b/js/script.js @@ -779,7 +779,7 @@ function handleImageSelection(evt) { w.value = img.width; settings.screenWidth = img.width; w.oninput = () => { - canvas.width = this.value; + canvas.width = w.value; updateAllImages(); updateInteger('screenWidth'); }; @@ -793,7 +793,7 @@ function handleImageSelection(evt) { h.value = img.height; settings.screenHeight = img.height; h.oninput = () => { - canvas.height = this.value; + canvas.height = h.value; updateAllImages(); updateInteger('screenHeight'); }; From 47a7b2cfb2f4162145842e11e3ea6051ca3741e0 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:05:45 +0200 Subject: [PATCH 05/63] Change how we strip comments --- js/script.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/script.js b/js/script.js index 74042d6..2d23ebc 100644 --- a/js/script.js +++ b/js/script.js @@ -677,14 +677,15 @@ function handleTextInput(drawMode) { input = input.replace(/const\s+(unsigned\s+char|uint8_t)\s+[a-zA-Z0-9]+\s*\[\]\s*(PROGMEM\s*)?=\s*/g, ''); input = input.replace(/\};|\{/g, ''); - // Convert newlines to comma (helps to remove comments later) + // Remove comments (while newlines are still newlines, so the whole + // comment is dropped even if it contains a comma, e.g. "// 'name', 8x8px") + input = input.replace(/\/\/.*$/gm, ''); + // Convert newlines to comma input = input.replace(/\r\n|\r|\n/g, ','); // Convert multiple commas in a row into a single one input = input.replace(/,{2,}/g, ','); // Remove whitespace input = input.replace(/\s/g, ''); - // Remove comments - input = input.replace(/\/\/(.+?),/g, ''); // Remove "0x" input = input.replace(/0[xX]/g, ''); // Split into list From c7e9f431c9bd70e04d01d7b2c10139bfb8a41f0b Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:06:17 +0200 Subject: [PATCH 06/63] Add an initial version of our testing suite --- README.md | 2 + tests/README.md | 37 +++ tests/fixtures/test_pattern.png | Bin 0 -> 183 bytes tests/generate_test_image.py | 72 +++++ tests/golden/background_black.txt | 16 + tests/golden/background_transparent.txt | 16 + .../golden/background_white_larger_canvas.txt | 16 + tests/golden/bitswap.txt | 7 + tests/golden/center_both_larger_canvas.txt | 16 + tests/golden/default.txt | 7 + tests/golden/dithering_atkinson.txt | 7 + tests/golden/dithering_bayer.txt | 7 + tests/golden/dithering_floyd_steinberg.txt | 7 + tests/golden/dithering_threshold_high.txt | 7 + tests/golden/dithering_threshold_low.txt | 7 + tests/golden/draw_mode_horizontal565.txt | 49 +++ tests/golden/draw_mode_horizontal888.txt | 25 ++ tests/golden/draw_mode_horizontal_alpha.txt | 7 + tests/golden/draw_mode_vertical1bit.txt | 7 + tests/golden/flip_both.txt | 7 + tests/golden/flip_horizontal.txt | 7 + tests/golden/flip_vertical.txt | 7 + tests/golden/invert_colors.txt | 7 + tests/golden/kitchen_sink.txt | 7 + tests/golden/output_adafruit_gfx.txt | 19 ++ tests/golden/output_arduino.txt | 15 + tests/golden/output_arduino_single.txt | 9 + tests/golden/remove_zeroes_commas.txt | 2 + tests/golden/rotate_180.txt | 7 + tests/golden/rotate_270.txt | 7 + tests/golden/rotate_90.txt | 7 + tests/golden/scale_fit_larger_canvas.txt | 16 + .../golden/scale_stretch_h_larger_canvas.txt | 10 + tests/golden/scale_stretch_larger_canvas.txt | 16 + .../golden/scale_stretch_v_larger_canvas.txt | 11 + tests/requirements.txt | 2 + tests/test_conversions.py | 293 ++++++++++++++++++ 37 files changed, 761 insertions(+) create mode 100644 tests/README.md create mode 100644 tests/fixtures/test_pattern.png create mode 100644 tests/generate_test_image.py create mode 100644 tests/golden/background_black.txt create mode 100644 tests/golden/background_transparent.txt create mode 100644 tests/golden/background_white_larger_canvas.txt create mode 100644 tests/golden/bitswap.txt create mode 100644 tests/golden/center_both_larger_canvas.txt create mode 100644 tests/golden/default.txt create mode 100644 tests/golden/dithering_atkinson.txt create mode 100644 tests/golden/dithering_bayer.txt create mode 100644 tests/golden/dithering_floyd_steinberg.txt create mode 100644 tests/golden/dithering_threshold_high.txt create mode 100644 tests/golden/dithering_threshold_low.txt create mode 100644 tests/golden/draw_mode_horizontal565.txt create mode 100644 tests/golden/draw_mode_horizontal888.txt create mode 100644 tests/golden/draw_mode_horizontal_alpha.txt create mode 100644 tests/golden/draw_mode_vertical1bit.txt create mode 100644 tests/golden/flip_both.txt create mode 100644 tests/golden/flip_horizontal.txt create mode 100644 tests/golden/flip_vertical.txt create mode 100644 tests/golden/invert_colors.txt create mode 100644 tests/golden/kitchen_sink.txt create mode 100644 tests/golden/output_adafruit_gfx.txt create mode 100644 tests/golden/output_arduino.txt create mode 100644 tests/golden/output_arduino_single.txt create mode 100644 tests/golden/remove_zeroes_commas.txt create mode 100644 tests/golden/rotate_180.txt create mode 100644 tests/golden/rotate_270.txt create mode 100644 tests/golden/rotate_90.txt create mode 100644 tests/golden/scale_fit_larger_canvas.txt create mode 100644 tests/golden/scale_stretch_h_larger_canvas.txt create mode 100644 tests/golden/scale_stretch_larger_canvas.txt create mode 100644 tests/golden/scale_stretch_v_larger_canvas.txt create mode 100644 tests/requirements.txt create mode 100644 tests/test_conversions.py diff --git a/README.md b/README.md index e06c5e3..784f57b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ You can find a simple Arduino example sketch [over here](https://github.com/javl ### Screen types I wrote the code with my 128x64 pixel monochrome OLED display in mind, but it should work with most similar displays. You might need to change some export settings; those are explained in the tool. +### Running tests +A simple test suite lives in the `tests` directory. See [`tests/`](tests/README.md) for instructions. ### Credit Initial code by [javl](https://github.com/javl) with aditional code by (in alphabetical order): diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..3a6e398 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,37 @@ +# image2cpp test suite + +Test uses the real `index.html` file and scripts in a headless browser +using `Playwright`. Output is compared against known valid (`golden`) files. +This way we are testing the output of the actual tool, not a reimplementation. + +## Setup and running + +``` +python3 -m venv .venv +source .venv/bin/activate +pip install -r tests/requirements.txt +playwright install chromium +``` + +To run: +``` +python3 tests/test_conversions.py +``` + +Prints `ok`/`FAIL` per scenario and exits non-zero if anything doesn't +match. + +## Updating golden files and test image + +To generate the test image again (only needed if we changed the image): +``` +python3 tests/generate_test_image.py +``` + +After an intentional change to the conversion logic, regenerate the +golden output and optionally review the diff before committing: + +``` +python3 tests/test_conversions.py --update +git diff tests/golden +``` diff --git a/tests/fixtures/test_pattern.png b/tests/fixtures/test_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..87f9b7432da13cc8c0ac64bfafdfae52a8e3b013 GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Eu!2~2l3^S5}RGp`bV@SoEw^OWz7!*02g^zQ; z`O7~$cdl%>T~fN%#!3eUE(HZn22YE7{w}>1tL0AFGG>(SJa_f)&gZWz;tzg3aOvv- zk*6Fce@J(0yN0HctkoORuISuq3fy+GzUNf1 hVXDaeMf}Z2xEDPswVChp&l>0w22WQ%mvv4FO#qkuLe2mH literal 0 HcmV?d00001 diff --git a/tests/generate_test_image.py b/tests/generate_test_image.py new file mode 100644 index 0000000..2d8227a --- /dev/null +++ b/tests/generate_test_image.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +""" Generate a simple test-pattern image for use with the test suite. + This 32x24 image is used to test all the different conversion paths in + js/script.js and js/dithering.js. Different quadrants of the image test + different aspects of the conversion process: + + - top-left: checkerboard -> stresses the threshold/dithering modes + - top-right: horizontal grayscale gradient -> stresses dithering algorithms + - bottom-left: RGB color bars -> stresses 565/888 color output + - bottom-right: diagonal line on white -> asymmetric element to test flip/rotate/mirror + +A 1px red marker in the extreme top-left corner and a 1px blue marker in the +extreme bottom-right corner give an unambiguous orientation reference. +""" +from pathlib import Path + +from PIL import Image + +WIDTH, HEIGHT = 32, 24 +OUT_PATH = Path(__file__).parent / "fixtures" / "test_pattern.png" + + +def build_image(): + img = Image.new("RGB", (WIDTH, HEIGHT), "white") + px = img.load() + if px is None: + raise RuntimeError("Failed to create image object") + + half_w, half_h = WIDTH // 2, HEIGHT // 2 + + # Top-left: checkerboard + for y in range(half_h): + for x in range(half_w): + color = (0, 0, 0) if (x // 2 + y // 2) % 2 == 0 else (255, 255, 255) + px[x, y] = color + + # Top-right: horizontal grayscale gradient + for y in range(half_h): + for x in range(half_w): + v = int((x / max(half_w - 1, 1)) * 255) + px[half_w + x, y] = (v, v, v) + + # Bottom-left: RGB color bars (red, green, blue, white) + bars = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)] + bar_w = max(half_w // len(bars), 1) + for y in range(half_h): + for x in range(half_w): + bar = min(x // bar_w, len(bars) - 1) + px[x, half_h + y] = bars[bar] + + # Bottom-right: diagonal line on white background (asymmetric marker) + for y in range(half_h): + for x in range(half_w): + px[half_w + x, half_h + y] = (255, 255, 255) + for i in range(min(half_w, half_h)): + px[half_w + i, half_h + i] = (0, 0, 0) + + # Orientation markers + px[0, 0] = (255, 0, 0) # top-left corner: red + px[WIDTH - 1, HEIGHT - 1] = (0, 0, 255) # bottom-right corner: blue + + return img + + +def main(): + OUT_PATH.parent.mkdir(parents=True, exist_ok=True) + build_image().save(OUT_PATH) + print(f"wrote {OUT_PATH}") + + +if __name__ == "__main__": + main() diff --git a/tests/golden/background_black.txt b/tests/golden/background_black.txt new file mode 100644 index 0000000..928ffe2 --- /dev/null +++ b/tests/golden/background_black.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0x33, 0x33, 0x00, 0xff, 0x00, 0x00, 0x33, 0x33, 0x00, 0xff, 0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, +0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, 0x00, 0x00, 0x33, 0x33, 0x00, 0xff, 0x00, 0x00, 0x33, 0x33, +0x00, 0xff, 0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, 0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, 0x00, 0x00, +0x33, 0x33, 0x00, 0xff, 0x00, 0x00, 0x33, 0x33, 0x00, 0xff, 0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, +0x00, 0x00, 0xcc, 0xcc, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0x7f, 0xff, 0x00, 0x00, 0x0f, 0x0f, +0xbf, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xdf, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xef, 0xff, 0x00, 0x00, +0x0f, 0x0f, 0xf7, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xfb, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xfd, 0xff, +0x00, 0x00, 0x0f, 0x0f, 0xfe, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xff, 0x7f, 0x00, 0x00, 0x0f, 0x0f, +0xff, 0xbf, 0x00, 0x00, 0x0f, 0x0f, 0xff, 0xdf, 0x00, 0x00, 0x0f, 0x0f, 0xff, 0xee, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \ No newline at end of file diff --git a/tests/golden/background_transparent.txt b/tests/golden/background_transparent.txt new file mode 100644 index 0000000..c73451d --- /dev/null +++ b/tests/golden/background_transparent.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, +0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, +0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, +0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, +0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, +0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, +0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \ No newline at end of file diff --git a/tests/golden/background_white_larger_canvas.txt b/tests/golden/background_white_larger_canvas.txt new file mode 100644 index 0000000..703e81b --- /dev/null +++ b/tests/golden/background_white_larger_canvas.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, +0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, +0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x7f, 0xff, 0xff, 0xff, 0x0f, 0x0f, +0xbf, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xef, 0xff, 0xff, 0xff, +0x0f, 0x0f, 0xf7, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xfd, 0xff, +0xff, 0xff, 0x0f, 0x0f, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0x7f, 0xff, 0xff, 0x0f, 0x0f, +0xff, 0xbf, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0xdf, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0xee, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/bitswap.txt b/tests/golden/bitswap.txt new file mode 100644 index 0000000..3aeab3e --- /dev/null +++ b/tests/golden/bitswap.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0xf0, 0xf0, 0xfe, 0xff, 0xf0, 0xf0, 0xfd, 0xff, 0xf0, 0xf0, 0xfb, 0xff, 0xf0, 0xf0, 0xf7, 0xff, +0xf0, 0xf0, 0xef, 0xff, 0xf0, 0xf0, 0xdf, 0xff, 0xf0, 0xf0, 0xbf, 0xff, 0xf0, 0xf0, 0x7f, 0xff, +0xf0, 0xf0, 0xff, 0xfe, 0xf0, 0xf0, 0xff, 0xfd, 0xf0, 0xf0, 0xff, 0xfb, 0xf0, 0xf0, 0xff, 0x77 \ No newline at end of file diff --git a/tests/golden/center_both_larger_canvas.txt b/tests/golden/center_both_larger_canvas.txt new file mode 100644 index 0000000..db176b4 --- /dev/null +++ b/tests/golden/center_both_larger_canvas.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, +0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, +0x33, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, +0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0x33, 0x33, 0x00, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, +0xff, 0xff, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x7f, 0xff, 0xff, 0xff, 0x0f, +0x0f, 0xbf, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xef, 0xff, 0xff, +0xff, 0x0f, 0x0f, 0xf7, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xfd, +0xff, 0xff, 0xff, 0x0f, 0x0f, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0x7f, 0xff, 0xff, 0x0f, +0x0f, 0xff, 0xbf, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0xdf, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0xee, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/default.txt b/tests/golden/default.txt new file mode 100644 index 0000000..d2b1a12 --- /dev/null +++ b/tests/golden/default.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, +0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, +0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/dithering_atkinson.txt b/tests/golden/dithering_atkinson.txt new file mode 100644 index 0000000..bd8018f --- /dev/null +++ b/tests/golden/dithering_atkinson.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x01, 0xbf, 0x33, 0x33, 0x04, 0xff, 0xcc, 0xcc, 0x04, 0x9f, 0xcc, 0xcc, 0x03, 0x77, +0x33, 0x33, 0x09, 0x7f, 0x33, 0x33, 0x01, 0x9f, 0xcc, 0xcc, 0x06, 0x7f, 0xcc, 0xcc, 0x01, 0xb7, +0x33, 0x33, 0x09, 0xbf, 0x33, 0x33, 0x04, 0x6f, 0xcc, 0xcc, 0x05, 0xbf, 0xcc, 0xcc, 0x01, 0xb7, +0x0d, 0x0f, 0x7f, 0xff, 0x0d, 0x0f, 0xbf, 0xff, 0x4b, 0x0f, 0xdf, 0xff, 0x1a, 0x0f, 0xef, 0xff, +0x26, 0x0f, 0xf7, 0xff, 0x0d, 0x0f, 0xfb, 0xff, 0x49, 0x0f, 0xfd, 0xff, 0x1b, 0x0f, 0xfe, 0xff, +0x0e, 0x0f, 0xff, 0x7f, 0x65, 0x0f, 0xff, 0xbf, 0x0d, 0x0f, 0xff, 0xdf, 0x0b, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/dithering_bayer.txt b/tests/golden/dithering_bayer.txt new file mode 100644 index 0000000..8987b8a --- /dev/null +++ b/tests/golden/dithering_bayer.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x15, 0x57, 0x33, 0x33, 0x02, 0xbf, 0xcc, 0xcc, 0x15, 0x57, 0xcc, 0xcc, 0x00, 0xff, +0x33, 0x33, 0x15, 0x57, 0x33, 0x33, 0x02, 0xbf, 0xcc, 0xcc, 0x15, 0x57, 0xcc, 0xcc, 0x00, 0xff, +0x33, 0x33, 0x15, 0x57, 0x33, 0x33, 0x02, 0xbf, 0xcc, 0xcc, 0x15, 0x57, 0xcc, 0xcc, 0x00, 0xff, +0x55, 0x1f, 0x7f, 0xff, 0x2b, 0x0f, 0xbf, 0xff, 0x55, 0x0f, 0xdf, 0xff, 0x0a, 0x0f, 0xef, 0xff, +0x55, 0x1f, 0xf7, 0xff, 0x2b, 0x0f, 0xfb, 0xff, 0x55, 0x0f, 0xfd, 0xff, 0x0a, 0x0f, 0xfe, 0xff, +0x55, 0x1f, 0xff, 0x7f, 0x2b, 0x0f, 0xff, 0xbf, 0x55, 0x0f, 0xff, 0xdf, 0x0a, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/dithering_floyd_steinberg.txt b/tests/golden/dithering_floyd_steinberg.txt new file mode 100644 index 0000000..c86c4ad --- /dev/null +++ b/tests/golden/dithering_floyd_steinberg.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x02, 0xbf, 0x33, 0x33, 0x09, 0x57, 0xcc, 0xcc, 0x05, 0x7f, 0xcc, 0xcc, 0x11, 0x57, +0x33, 0x33, 0x05, 0x5f, 0x33, 0x33, 0x02, 0xb7, 0xcc, 0xcc, 0x14, 0xbf, 0xcc, 0xcc, 0x02, 0xd7, +0x33, 0x33, 0x09, 0x5d, 0x33, 0x33, 0x04, 0xb7, 0xcc, 0xcc, 0x12, 0xbf, 0xcc, 0xcc, 0x02, 0xaf, +0x0a, 0x0f, 0x7f, 0xff, 0x55, 0x0f, 0xbf, 0xff, 0x0b, 0x0f, 0xdf, 0xff, 0x2d, 0x0f, 0xef, 0xff, +0x45, 0x0f, 0xf7, 0xff, 0x15, 0x0f, 0xfb, 0xff, 0x4d, 0x0f, 0xfd, 0xff, 0x15, 0x0f, 0xfe, 0xff, +0x4a, 0x0f, 0xff, 0x7f, 0x0b, 0x0f, 0xff, 0xbf, 0x55, 0x0f, 0xff, 0xdf, 0x0a, 0x2f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/dithering_threshold_high.txt b/tests/golden/dithering_threshold_high.txt new file mode 100644 index 0000000..cabebd6 --- /dev/null +++ b/tests/golden/dithering_threshold_high.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x00, 0x0f, 0x33, 0x33, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, +0x33, 0x33, 0x00, 0x0f, 0x33, 0x33, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, +0x33, 0x33, 0x00, 0x0f, 0x33, 0x33, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, 0xcc, 0xcc, 0x00, 0x0f, +0x00, 0x0f, 0x7f, 0xff, 0x00, 0x0f, 0xbf, 0xff, 0x00, 0x0f, 0xdf, 0xff, 0x00, 0x0f, 0xef, 0xff, +0x00, 0x0f, 0xf7, 0xff, 0x00, 0x0f, 0xfb, 0xff, 0x00, 0x0f, 0xfd, 0xff, 0x00, 0x0f, 0xfe, 0xff, +0x00, 0x0f, 0xff, 0x7f, 0x00, 0x0f, 0xff, 0xbf, 0x00, 0x0f, 0xff, 0xdf, 0x00, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/dithering_threshold_low.txt b/tests/golden/dithering_threshold_low.txt new file mode 100644 index 0000000..d046e7c --- /dev/null +++ b/tests/golden/dithering_threshold_low.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xb3, 0x33, 0x0f, 0xff, 0x33, 0x33, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, +0x33, 0x33, 0x0f, 0xff, 0x33, 0x33, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, +0x33, 0x33, 0x0f, 0xff, 0x33, 0x33, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, 0xcc, 0xcc, 0x0f, 0xff, +0xff, 0x0f, 0x7f, 0xff, 0xff, 0x0f, 0xbf, 0xff, 0xff, 0x0f, 0xdf, 0xff, 0xff, 0x0f, 0xef, 0xff, +0xff, 0x0f, 0xf7, 0xff, 0xff, 0x0f, 0xfb, 0xff, 0xff, 0x0f, 0xfd, 0xff, 0xff, 0x0f, 0xfe, 0xff, +0xff, 0x0f, 0xff, 0x7f, 0xff, 0x0f, 0xff, 0xbf, 0xff, 0x0f, 0xff, 0xdf, 0xff, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/golden/draw_mode_horizontal565.txt b/tests/golden/draw_mode_horizontal565.txt new file mode 100644 index 0000000..56b0757 --- /dev/null +++ b/tests/golden/draw_mode_horizontal565.txt @@ -0,0 +1,49 @@ +// 'test_pattern', 32x24px +0xf800, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0x001f \ No newline at end of file diff --git a/tests/golden/draw_mode_horizontal888.txt b/tests/golden/draw_mode_horizontal888.txt new file mode 100644 index 0000000..e731596 --- /dev/null +++ b/tests/golden/draw_mode_horizontal888.txt @@ -0,0 +1,25 @@ +// 'test_pattern', 32x24px +0x00ff0000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x000000ff \ No newline at end of file diff --git a/tests/golden/draw_mode_horizontal_alpha.txt b/tests/golden/draw_mode_horizontal_alpha.txt new file mode 100644 index 0000000..fe08dcf --- /dev/null +++ b/tests/golden/draw_mode_horizontal_alpha.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/draw_mode_vertical1bit.txt b/tests/golden/draw_mode_vertical1bit.txt new file mode 100644 index 0000000..790404b --- /dev/null +++ b/tests/golden/draw_mode_vertical1bit.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0x0c, 0x0c, 0x03, 0x03, 0xfc, 0xfc, 0xf3, 0xf3, 0x0c, 0x0c, 0x03, 0x03, 0xfc, 0xfc, 0xf3, 0xf3, +0xe0, 0xd0, 0xb0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f, 0xff, 0xff, 0xff, 0x7f \ No newline at end of file diff --git a/tests/golden/flip_both.txt b/tests/golden/flip_both.txt new file mode 100644 index 0000000..c70700f --- /dev/null +++ b/tests/golden/flip_both.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x77, 0xff, 0xf0, 0xf0, 0xfb, 0xff, 0xf0, 0xf0, 0xfd, 0xff, 0xf0, 0xf0, 0xfe, 0xff, 0xf0, 0xf0, +0xff, 0x7f, 0xf0, 0xf0, 0xff, 0xbf, 0xf0, 0xf0, 0xff, 0xdf, 0xf0, 0xf0, 0xff, 0xef, 0xf0, 0xf0, +0xff, 0xf7, 0xf0, 0xf0, 0xff, 0xfb, 0xf0, 0xf0, 0xff, 0xfd, 0xf0, 0xf0, 0xff, 0xfe, 0xf0, 0xf0, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc \ No newline at end of file diff --git a/tests/golden/flip_horizontal.txt b/tests/golden/flip_horizontal.txt new file mode 100644 index 0000000..d6b33af --- /dev/null +++ b/tests/golden/flip_horizontal.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, +0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, +0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, +0xff, 0xfe, 0xf0, 0xf0, 0xff, 0xfd, 0xf0, 0xf0, 0xff, 0xfb, 0xf0, 0xf0, 0xff, 0xf7, 0xf0, 0xf0, +0xff, 0xef, 0xf0, 0xf0, 0xff, 0xdf, 0xf0, 0xf0, 0xff, 0xbf, 0xf0, 0xf0, 0xff, 0x7f, 0xf0, 0xf0, +0xfe, 0xff, 0xf0, 0xf0, 0xfd, 0xff, 0xf0, 0xf0, 0xfb, 0xff, 0xf0, 0xf0, 0x77, 0xff, 0xf0, 0xf0 \ No newline at end of file diff --git a/tests/golden/flip_vertical.txt b/tests/golden/flip_vertical.txt new file mode 100644 index 0000000..3c0eaf2 --- /dev/null +++ b/tests/golden/flip_vertical.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x0f, 0x0f, 0xff, 0xee, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0x7f, +0x0f, 0x0f, 0xfe, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xf7, 0xff, +0x0f, 0x0f, 0xef, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0x7f, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff \ No newline at end of file diff --git a/tests/golden/invert_colors.txt b/tests/golden/invert_colors.txt new file mode 100644 index 0000000..ba88886 --- /dev/null +++ b/tests/golden/invert_colors.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, +0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, +0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, +0xf0, 0xf0, 0x80, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0x20, 0x00, 0xf0, 0xf0, 0x10, 0x00, +0xf0, 0xf0, 0x08, 0x00, 0xf0, 0xf0, 0x04, 0x00, 0xf0, 0xf0, 0x02, 0x00, 0xf0, 0xf0, 0x01, 0x00, +0xf0, 0xf0, 0x00, 0x80, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x20, 0xf0, 0xf0, 0x00, 0x11 \ No newline at end of file diff --git a/tests/golden/kitchen_sink.txt b/tests/golden/kitchen_sink.txt new file mode 100644 index 0000000..7695d1d --- /dev/null +++ b/tests/golden/kitchen_sink.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +80000000804020100804020100000000 +00000000ff7fffff904fb04bb5ffaaff +0001000208010e010e030d0e8b4f2f1f +0c0c0303fcfcf3f31cac5323dc7cd3f3 +000000aa009a611ee15eabfdb7ffffff +cccc3333cccc3333cccc3333cccc3333 diff --git a/tests/golden/output_adafruit_gfx.txt b/tests/golden/output_adafruit_gfx.txt new file mode 100644 index 0000000..f8c637a --- /dev/null +++ b/tests/golden/output_adafruit_gfx.txt @@ -0,0 +1,19 @@ +const unsigned char epd_bitmapBitmap [] PROGMEM = { + // 'test_pattern', 32x24px + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; + +const GFXbitmapGlyph epd_bitmapGlyphs [] PROGMEM = { + { 0, 32, 24, 0, '0' }// 'test_pattern' +}; + +const GFXbitmapFont epd_bitmapFont PROGMEM = { + (uint8_t *)epd_bitmapBitmap, + (GFXbitmapGlyph *)epd_bitmapGlyphs, + 1 +}; diff --git a/tests/golden/output_arduino.txt b/tests/golden/output_arduino.txt new file mode 100644 index 0000000..ef93a3a --- /dev/null +++ b/tests/golden/output_arduino.txt @@ -0,0 +1,15 @@ +// 'test_pattern', 32x24px +const unsigned char epd_bitmaptest_pattern [] PROGMEM = { + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 112) +const int epd_bitmap_allArray_LEN = 1; +const unsigned char* epd_bitmap_allArray[1] = { + epd_bitmaptest_pattern +}; diff --git a/tests/golden/output_arduino_single.txt b/tests/golden/output_arduino_single.txt new file mode 100644 index 0000000..ebcc9f0 --- /dev/null +++ b/tests/golden/output_arduino_single.txt @@ -0,0 +1,9 @@ +const unsigned char epd_bitmap [] PROGMEM = { + // 'test_pattern, 32x24px + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; \ No newline at end of file diff --git a/tests/golden/remove_zeroes_commas.txt b/tests/golden/remove_zeroes_commas.txt new file mode 100644 index 0000000..3da87fe --- /dev/null +++ b/tests/golden/remove_zeroes_commas.txt @@ -0,0 +1,2 @@ +// 'test_pattern', 32x24px +333300ff333300ffcccc00ffcccc00ff333300ff333300ffcccc00ffcccc00ff333300ff333300ffcccc00ffcccc00ff0f0f7fff0f0fbfff0f0fdfff0f0fefff0f0ff7ff0f0ffbff0f0ffdff0f0ffeff0f0fff7f0f0fffbf0f0fffdf0f0fffee \ No newline at end of file diff --git a/tests/golden/rotate_180.txt b/tests/golden/rotate_180.txt new file mode 100644 index 0000000..c70700f --- /dev/null +++ b/tests/golden/rotate_180.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x77, 0xff, 0xf0, 0xf0, 0xfb, 0xff, 0xf0, 0xf0, 0xfd, 0xff, 0xf0, 0xf0, 0xfe, 0xff, 0xf0, 0xf0, +0xff, 0x7f, 0xf0, 0xf0, 0xff, 0xbf, 0xf0, 0xf0, 0xff, 0xdf, 0xf0, 0xf0, 0xff, 0xef, 0xf0, 0xf0, +0xff, 0xf7, 0xf0, 0xf0, 0xff, 0xfb, 0xf0, 0xf0, 0xff, 0xfd, 0xf0, 0xf0, 0xff, 0xfe, 0xf0, 0xf0, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc, +0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0x33, 0x33, 0xff, 0x00, 0xcc, 0xcc, 0xff, 0x00, 0xcc, 0xcc \ No newline at end of file diff --git a/tests/golden/rotate_270.txt b/tests/golden/rotate_270.txt new file mode 100644 index 0000000..784e6cc --- /dev/null +++ b/tests/golden/rotate_270.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, +0xff, 0xfd, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xf7, 0x00, 0x0f, 0xef, 0x00, 0x0f, 0xdf, 0x00, 0x0f, +0xbf, 0x00, 0x0f, 0x7f, 0x00, 0x0e, 0xff, 0x00, 0x0d, 0xff, 0x00, 0x0b, 0xff, 0x00, 0x07, 0xff, +0xcc, 0xcf, 0xff, 0xcc, 0xcf, 0xff, 0x33, 0x3f, 0xff, 0x33, 0x3f, 0xff, 0xcc, 0xc0, 0x00, 0xcc, +0xc0, 0x00, 0x33, 0x30, 0x00, 0x33, 0x30, 0x00, 0xcc, 0xcf, 0xff, 0xcc, 0xcf, 0xff, 0x33, 0x3f, +0xff, 0x33, 0x3f, 0xff, 0xcc, 0xc0, 0x00, 0xcc, 0xc0, 0x00, 0x33, 0x30, 0x00, 0x33, 0x30, 0x00 \ No newline at end of file diff --git a/tests/golden/rotate_90.txt b/tests/golden/rotate_90.txt new file mode 100644 index 0000000..648e5b0 --- /dev/null +++ b/tests/golden/rotate_90.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc, 0x00, 0x03, 0x33, 0x00, 0x03, 0x33, 0xff, 0xfc, 0xcc, 0xff, +0xfc, 0xcc, 0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, 0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc, 0x00, 0x03, +0x33, 0x00, 0x03, 0x33, 0xff, 0xfc, 0xcc, 0xff, 0xfc, 0xcc, 0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, +0xff, 0xe0, 0x00, 0xff, 0xd0, 0x00, 0xff, 0xb0, 0x00, 0xff, 0x70, 0x00, 0xfe, 0xf0, 0x00, 0xfd, +0xf0, 0x00, 0xfb, 0xf0, 0x00, 0xf7, 0xf0, 0x00, 0xef, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xbf, 0xff, +0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/scale_fit_larger_canvas.txt b/tests/golden/scale_fit_larger_canvas.txt new file mode 100644 index 0000000..c5af95e --- /dev/null +++ b/tests/golden/scale_fit_larger_canvas.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, +0x38, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, +0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, +0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x1f, 0xff, 0x03, 0xc0, 0x3f, 0x3f, +0xff, 0xff, 0x03, 0xf0, 0x3f, 0xbf, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xdf, 0xff, 0xff, 0x03, 0xf0, +0x3f, 0xef, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xf7, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfb, 0xff, 0xff, +0x03, 0xf0, 0x3f, 0xfd, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfe, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xff, +0x7f, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xbf, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xdf, 0xff, 0x03, 0xf0, +0x3f, 0xff, 0xef, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xf7, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xfb, 0xff, +0x03, 0xf0, 0x3f, 0xff, 0xfd, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xfe, 0xff, 0x03, 0xf0, 0x3f, 0xff, +0xff, 0x7f, 0x03, 0xf0, 0x3f, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/scale_stretch_h_larger_canvas.txt b/tests/golden/scale_stretch_h_larger_canvas.txt new file mode 100644 index 0000000..ee57f8f --- /dev/null +++ b/tests/golden/scale_stretch_h_larger_canvas.txt @@ -0,0 +1,10 @@ +// 'test_pattern', 48x24px +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, +0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0x03, 0xf0, 0x3f, 0x3f, 0xff, 0xff, 0x03, 0xf0, +0x3f, 0x9f, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xe7, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xf3, 0xff, 0xff, +0x03, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfe, 0x7f, 0xff, 0x03, 0xf0, 0x3f, 0xff, +0x9f, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xcf, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xf3, 0xff, 0x03, 0xf0, +0x3f, 0xff, 0xf9, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xfe, 0x7f, 0x03, 0xf0, 0x3f, 0xff, 0xff, 0x3e \ No newline at end of file diff --git a/tests/golden/scale_stretch_larger_canvas.txt b/tests/golden/scale_stretch_larger_canvas.txt new file mode 100644 index 0000000..a85719f --- /dev/null +++ b/tests/golden/scale_stretch_larger_canvas.txt @@ -0,0 +1,16 @@ +// 'test_pattern', 48x40px +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, +0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0x1c, 0x71, +0xc6, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, +0x1c, 0x71, 0xc7, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, +0x0f, 0xff, 0xe3, 0x8e, 0x38, 0x00, 0x3f, 0xff, 0x03, 0x80, 0x3f, 0x3f, 0xff, 0xff, 0x03, 0xf0, +0x3f, 0x3f, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0x9f, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xef, 0xff, 0xff, +0x03, 0xf0, 0x3f, 0xe7, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xf3, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfb, +0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0x03, 0xf0, 0x3f, 0xfe, 0xff, 0xff, 0x03, 0xf0, +0x3f, 0xff, 0x7f, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xbf, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0x9f, 0xff, +0x03, 0xf0, 0x3f, 0xff, 0xcf, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xf7, 0xff, 0x03, 0xf0, 0x3f, 0xff, +0xf3, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xf9, 0xff, 0x03, 0xf0, 0x3f, 0xff, 0xfd, 0xff, 0x03, 0xf0, +0x3f, 0xff, 0xfe, 0x7f, 0x03, 0xf0, 0x3f, 0xff, 0xff, 0x7e, 0x03, 0xf0, 0x3f, 0xff, 0xff, 0x3e \ No newline at end of file diff --git a/tests/golden/scale_stretch_v_larger_canvas.txt b/tests/golden/scale_stretch_v_larger_canvas.txt new file mode 100644 index 0000000..93d9734 --- /dev/null +++ b/tests/golden/scale_stretch_v_larger_canvas.txt @@ -0,0 +1,11 @@ +// 'test_pattern', 32x40px +0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, +0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, +0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x03, 0xff, +0x0c, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, +0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, 0x0f, 0x0f, 0xef, 0xff, 0x0f, 0x0f, 0xf7, 0xff, +0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfd, 0xff, +0x0f, 0x0f, 0xfe, 0xff, 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, +0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee, 0x0f, 0x0f, 0xff, 0xee \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..d662229 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,2 @@ +playwright +pillow diff --git a/tests/test_conversions.py b/tests/test_conversions.py new file mode 100644 index 0000000..b050203 --- /dev/null +++ b/tests/test_conversions.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +"""Test suite for image2cpp's conversion output. + +Controls the real index.html in a headless browser (Playwright), feeds it the +deterministic test-pattern image, exercises every conversion option through +the actual UI, and compares the generated code output against saved golden +files. Because it runs the real app (not a reimplementation of the +conversion logic), any change to js/script.js or js/dithering.js that +changes output is caught. + +Usage: + python tests/test_conversions.py # run tests, compare to golden + python tests/test_conversions.py --diff # print diff on failing scenarios + python tests/test_conversions.py --update # (re)generate golden files +""" +import argparse +import difflib +import re +import sys +from pathlib import Path + +from playwright.sync_api import sync_playwright + +ROOT = Path(__file__).parent.parent +INDEX_HTML = ROOT / "index.html" +TEST_IMAGE = Path(__file__).parent / "fixtures" / "test_pattern.png" +GOLDEN_DIR = Path(__file__).parent / "golden" + +NATIVE_WIDTH, NATIVE_HEIGHT = 32, 24 + +# Full set of controllable options and their app defaults. +BASE_SETTINGS = { + "drawMode": "horizontal1bit", + "outputFormat": "plain", + "backgroundColor": "white", + "invertColors": False, + "ditheringMode": 0, + "ditheringThreshold": 128, + "scale": 1, + "centerHorizontally": False, + "centerVertically": False, + "rotation": 0, + "flipHorizontally": False, + "flipVertically": False, + "bitswap": False, + "removeZeroesCommas": False, +} + +# (scenario name, overrides on top of BASE_SETTINGS, optional canvas resize) +SCENARIOS = [ + ("default", {}, None), + ("draw_mode_vertical1bit", {"drawMode": "vertical1bit"}, None), + ("draw_mode_horizontal565", {"drawMode": "horizontal565"}, None), + ("draw_mode_horizontal888", {"drawMode": "horizontal888"}, None), + ("draw_mode_horizontal_alpha", {"drawMode": "horizontalAlpha"}, None), + ("output_arduino", {"outputFormat": "arduino"}, None), + ("output_arduino_single", {"outputFormat": "arduino_single"}, None), + ("output_adafruit_gfx", {"outputFormat": "adafruit_gfx"}, None), + ("invert_colors", {"invertColors": True}, None), + ("flip_horizontal", {"flipHorizontally": True}, None), + ("flip_vertical", {"flipVertically": True}, None), + ("flip_both", {"flipHorizontally": True, "flipVertically": True}, None), + ("rotate_90", {"rotation": 90}, None), + ("rotate_180", {"rotation": 180}, None), + ("rotate_270", {"rotation": 270}, None), + ("dithering_bayer", {"ditheringMode": 1}, None), + ("dithering_floyd_steinberg", {"ditheringMode": 2}, None), + ("dithering_atkinson", {"ditheringMode": 3}, None), + ("dithering_threshold_low", {"ditheringThreshold": 64}, None), + ("dithering_threshold_high", {"ditheringThreshold": 200}, None), + ("bitswap", {"bitswap": True}, None), + ("remove_zeroes_commas", {"removeZeroesCommas": True}, None), + # scale=1 (original) on a canvas larger than the image so the + # background actually shows through in the margins. + ("background_white_larger_canvas", {"backgroundColor": "white"}, (48, 40)), + ("background_black", {"backgroundColor": "black"}, (48, 40)), + # RGB channels of a transparent background are zero-initialized, same as + # an explicit black background, so the two are indistinguishable under + # the 1-bit draw modes (which ignore alpha). Use the alpha draw mode + # here so transparency is actually used. + ( + "background_transparent", + {"backgroundColor": "transparent", "drawMode": "horizontalAlpha"}, + (48, 40), + ), + ("scale_fit_larger_canvas", {"scale": 2}, (48, 40)), + ("scale_stretch_larger_canvas", {"scale": 3}, (48, 40)), + ("scale_stretch_h_larger_canvas", {"scale": 4}, (48, NATIVE_HEIGHT)), + ("scale_stretch_v_larger_canvas", {"scale": 5}, (NATIVE_WIDTH, 40)), + ( + "center_both_larger_canvas", + {"centerHorizontally": True, "centerVertically": True}, + (48, 40), + ), + ( + "kitchen_sink", + { + "drawMode": "vertical1bit", + "invertColors": True, + "flipHorizontally": True, + "flipVertically": True, + "bitswap": True, + "removeZeroesCommas": True, + "ditheringMode": 2, + }, + None, + ), +] + + +def reset_canvas_size(page, width, height): + """Set canvas size through the real width/height text inputs.""" + page.fill("#screenWidth", str(width)) + page.fill("#screenHeight", str(height)) + + +def apply_settings(page, settings): + page.select_option("#drawMode", settings["drawMode"]) + page.select_option("#outputFormat", settings["outputFormat"]) + page.check(f"#backgroundColor{settings['backgroundColor'].capitalize()}") + set_checkbox(page, "#invertColors", settings["invertColors"]) + page.select_option("#ditheringMode", str(settings["ditheringMode"])) + page.fill("#ditheringThreshold", str(settings["ditheringThreshold"])) + page.select_option("#scale", str(settings["scale"])) + set_checkbox(page, "#centerHorizontally", settings["centerHorizontally"]) + set_checkbox(page, "#centerVertically", settings["centerVertically"]) + page.select_option("#rotation", str(settings["rotation"])) + set_checkbox(page, "#flipHorizontally", settings["flipHorizontally"]) + set_checkbox(page, "#flipVertically", settings["flipVertically"]) + set_checkbox(page, "#bitswap", settings["bitswap"]) + if settings["outputFormat"] == "plain": + set_checkbox(page, "#removeZeroesCommas", settings["removeZeroesCommas"]) + + +def set_checkbox(page, selector, checked): + if checked: + page.check(selector) + else: + page.uncheck(selector) + + +def run_scenario(page, name, overrides, resize): + settings = {**BASE_SETTINGS, **overrides} + reset_canvas_size(page, *(resize or (NATIVE_WIDTH, NATIVE_HEIGHT))) + apply_settings(page, settings) + # Force a redraw with the final settings state regardless of which + # controls' change events actually fired (some may be no-ops if the + # value didn't change from the previous scenario). + page.evaluate("updateAllImages()") + page.click("text=Generate code") + return page.input_value("#code-output") + + +def roundtrip_eligible(settings): + """Only plain-format, non-bitswapped 1-bit output is re-importable via the + "Paste byte array" box: other draw modes aren't supported by the parser, + and bitswap/removeZeroesCommas mangle the byte stream so it can't be read + back (bitswap reorders bits with no importer to undo it; removeZeroesCommas + drops the separators the parser splits on).""" + return ( + settings["drawMode"] in ("horizontal1bit", "vertical1bit") + and settings["outputFormat"] == "plain" + and not settings["bitswap"] + and not settings["removeZeroesCommas"] + ) + + +def run_roundtrip(page, output, draw_mode, width, height): + """Paste a generated byte array back in, import it, and re-export it, + same steps a user would take to sanity-check their array. Uses a fresh + page load, so the reimported canvas is the only pixel source, nothing + left over from a previous scenario.""" + page.goto(INDEX_HTML.as_uri()) + # window.onload defaults outputFormat to "arduino"; force it back to plain + # to match the format the scenario was exported in. + page.select_option("#outputFormat", "plain") + page.fill("#byte-input", output) + page.fill("#text-input-width", str(width)) + page.fill("#text-input-height", str(height)) + direction = "horizontal" if draw_mode == "horizontal1bit" else "vertical" + page.click(f"text=Read as {direction}") + # The imported canvas is only backed by a real once its data-URL + # finishes loading (async); switching draw mode before that swap + # completes triggers a redraw from the still-blank placeholder image and + # wipes the canvas. Wait for the swap so this matches how a person would + # naturally pause between clicking "Read as..." and changing the dropdown. + page.wait_for_function("images.first().img.src") + if direction == "vertical": + page.select_option("#drawMode", "vertical1bit") + page.click("text=Generate code") + return page.input_value("#code-output") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--update", action="store_true", help="(re)generate golden files instead of comparing" + ) + parser.add_argument( + "--diff", action="store_true", help="print a diff against golden output on failure" + ) + args = parser.parse_args() + + if not TEST_IMAGE.exists(): + print(f"Test image missing at {TEST_IMAGE}; run generate_test_image.py first") + sys.exit(1) + + GOLDEN_DIR.mkdir(parents=True, exist_ok=True) + + failures = [] + with sync_playwright() as p: + browser = p.chromium.launch() + page = browser.new_page() + page.goto(INDEX_HTML.as_uri()) + page.set_input_files("#file-input", str(TEST_IMAGE)) + # Wait for the app to finish loading the image and create its canvas. + page.wait_for_selector("#image-size-settings li") + + for name, overrides, resize in SCENARIOS: + settings = {**BASE_SETTINGS, **overrides} + output = run_scenario(page, name, overrides, resize) + golden_path = GOLDEN_DIR / f"{name}.txt" + + if args.update: + golden_path.write_text(output) + print(f"wrote {name}") + continue + + if not golden_path.exists(): + failures.append(f"{name}: no golden file (run with --update first)") + print(f"MISSING {name}") + continue + + expected = golden_path.read_text() + if output != expected: + failures.append(name) + print(f"FAIL {name}") + if args.diff: + diff = difflib.unified_diff( + expected.splitlines(keepends=True), + output.splitlines(keepends=True), + fromfile=f"{name} (golden)", + tofile=f"{name} (actual)", + ) + sys.stdout.writelines(diff) + continue + + print(f"ok {name}") + + if roundtrip_eligible(settings): + width, height = resize or (NATIVE_WIDTH, NATIVE_HEIGHT) + roundtripped = run_roundtrip(page, output, settings["drawMode"], width, height) + # The re-imported image has no filename to derive a glyph + # comment from, so plain output drops its leading "// name, + # WxHpx" comment line; strip it before comparing byte data. + comparable_output = re.sub(r"^//[^\n]*\n", "", output) + if roundtripped != comparable_output: + failures.append(f"{name}: round-trip mismatch") + print(f"FAIL {name} (round-trip)") + if args.diff: + diff = difflib.unified_diff( + comparable_output.splitlines(keepends=True), + roundtripped.splitlines(keepends=True), + fromfile=f"{name} (exported)", + tofile=f"{name} (re-imported+exported)", + ) + sys.stdout.writelines(diff) + else: + print(f"ok {name} (round-trip)") + + # The page was reloaded for the round-trip check; reload the + # test image so the next scenario has its canvas back. + page.goto(INDEX_HTML.as_uri()) + page.set_input_files("#file-input", str(TEST_IMAGE)) + page.wait_for_selector("#image-size-settings li") + + browser.close() + + if args.update: + print(f"\nWrote {len(SCENARIOS)} golden files to {GOLDEN_DIR}") + return + + if failures: + print(f"\n{len(failures)} of {len(SCENARIOS)} scenarios failed:") + for f in failures: + print(f" - {f}") + sys.exit(1) + + print(f"\nAll {len(SCENARIOS)} scenarios match golden output.") + + +if __name__ == "__main__": + main() From b5bc6c3774dcfeac87b7429ddd6b2ba6f1589ac3 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:13:15 +0200 Subject: [PATCH 07/63] restyle test --- css/style.css | 765 +++++++++++++++++++++++++++++++++++++++++++------- index.html | 94 +++++-- js/script.js | 3 +- restyle.md | 404 ++++++++++++++++++++++++++ 4 files changed, 1145 insertions(+), 121 deletions(-) create mode 100644 restyle.md diff --git a/css/style.css b/css/style.css index 96d4400..b76621f 100644 --- a/css/style.css +++ b/css/style.css @@ -1,63 +1,367 @@ +:root { + /* Colors (from restyle design tokens) */ + --primary: #006b58; + --primary-container: #1abc9c; + --on-primary: #ffffff; + --on-primary-container: #004538; + --secondary: #4e6073; + --on-secondary: #ffffff; + --on-secondary-container: #526478; + --background: #f7f9fb; + --surface: #f7f9fb; + --surface-container-lowest: #ffffff; + --surface-container-low: #f2f4f6; + --surface-container: #eceef0; + --surface-container-high: #e6e8ea; + --surface-container-highest: #e0e3e5; + --on-surface: #191c1e; + --on-surface-variant: #3c4a45; + --on-background: #191c1e; + --text-muted: #64748b; + --border-subtle: #e2e8f0; + --outline: #6c7a75; + --outline-variant: #bbcac3; + --editor-bg: #1e1e1e; + --error: #ba1a1a; + + /* Radii */ + --radius: 0.125rem; + --radius-lg: 0.25rem; + --radius-xl: 0.5rem; + --radius-full: 0.75rem; + + /* Fonts */ + --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + * { margin: 0; padding: 0; + box-sizing: border-box; } -body{ - font-family: arial; +body { + font-family: var(--font-body); + font-size: 16px; + line-height: 24px; + color: var(--on-background); + background-color: var(--background); + min-height: 100vh; +} + +/* ---- Top nav bar ---- */ + +.topnav { + position: sticky; + top: 0; + z-index: 50; + background: var(--surface-container-lowest); + border-bottom: 1px solid var(--border-subtle); +} + +.topnav-inner { + max-width: 1200px; + margin: auto; + height: 64px; + padding: 0 24px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.topnav-left { + display: flex; + align-items: center; + gap: 32px; +} + +.brand { + font-size: 20px; + font-weight: 700; + color: var(--primary); +} + +.topnav-links { + display: flex; + gap: 24px; +} + +.topnav-links a { + color: var(--secondary); + font-weight: 500; + font-size: 14px; +} + +.topnav-links a:hover { + color: var(--primary); + text-decoration: none; +} + +.topnav-right { + display: flex; + align-items: center; + gap: 12px; +} + +.icon-btn { + background: transparent; + color: var(--secondary); + border: none; + font-size: 18px; + line-height: 1; + padding: 6px; + margin: 0; + border-radius: var(--radius-lg); + cursor: pointer; +} + +.icon-btn:hover { + color: var(--primary); + background: var(--surface-container); + opacity: 1; +} + +.theme-icon-light { + display: none; +} + +.btn-reset { + margin: 0; +} + +/* ---- Layout: sidebar + main ---- */ + +.layout { + display: flex; + gap: 32px; + max-width: 1200px; + margin: auto; + padding: 32px 24px 64px; + align-items: flex-start; +} + +.sidebar { + position: sticky; + top: 96px; + flex: 0 0 220px; + width: 220px; + background: var(--surface-container-low); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: 24px 0; +} + +.sidebar-nav { + display: flex; + flex-direction: column; +} + +.sidebar-link { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 24px; + font-family: var(--font-mono); + font-size: 13px; + font-weight: 500; + color: var(--secondary); + border-right: 3px solid transparent; +} + +.sidebar-link:hover { + background: var(--surface-container-high); + text-decoration: none; +} + +.sidebar-link.active { + color: var(--primary); + font-weight: 700; + background: rgba(26, 188, 156, 0.1); + border-right-color: var(--primary); +} + +.sidebar-num { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 4px; + background: var(--surface-container-high); + color: var(--secondary); + font-size: 12px; + font-weight: 700; + flex-shrink: 0; +} + +.sidebar-link.active .sidebar-num { + background: var(--primary); + color: var(--on-primary); } .wrapper { display: flex; flex-direction: column; - margin: auto; - width: 900px; + gap: 24px; + flex: 1; + min-width: 0; } +/* ---- Step number rail ---- */ + +.section.step { + position: relative; + padding-left: 72px; + scroll-margin-top: 88px; +} + +.step-num { + position: absolute; + left: 16px; + top: 32px; + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + background: var(--surface-container-high); + color: var(--secondary); + border: 1px solid var(--border-subtle); + z-index: 2; +} + +.active .step-num { + background: var(--primary); + color: var(--on-primary); + border-color: var(--primary); +} + +.step::before { + content: ""; + position: absolute; + left: 35px; + top: 40px; + bottom: -24px; + width: 2px; + background: var(--border-subtle); + z-index: 1; +} + +#step-4.step::before { + display: none; +} + +/* ---- Typography ---- */ + +h1 { + font-size: 30px; + line-height: 38px; + letter-spacing: -0.02em; + font-weight: 600; + color: var(--primary); + margin-bottom: 8px; +} + +h2 { + font-size: 20px; + line-height: 28px; + font-weight: 600; + color: var(--on-surface); +} + +.sub-section-title { + font-family: var(--font-mono); + font-size: 13px; + line-height: 16px; + font-weight: 500; + color: var(--primary); + margin: 0 0 12px; +} + +p { + margin: 16px 0; + color: var(--on-surface-variant); +} + +a { + color: var(--primary); + text-decoration: none; + font-weight: 500; +} + +a:hover { + text-decoration: underline; +} + +code { + font-family: var(--font-mono); + font-size: 0.9em; + background: var(--surface-container); + padding: 1px 5px; + border-radius: var(--radius-lg); +} + +/* ---- Section cards ---- */ + .section { - margin: 10px 0; + background: var(--surface-container-lowest); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: 32px; } +/* First (intro) section: keep it flush/light */ +.section:first-child { + background: transparent; + border: none; + padding: 8px 0 0; +} + +.section h2 { + margin-bottom: 16px; +} + +/* Legacy divider no longer needed; cards separate content */ .bottom-divider { - border-bottom: 2px solid #000000; - padding-bottom: 20px; + border-bottom: none; + padding-bottom: 32px; } .sub-section { clear: both; - margin-bottom: 1px; -} - -.section, -.sub-section { width: 100%; } +.section { + width: 100%; +} + +/* ---- Multi-column (select image row) ---- */ + .column { float: left; } .column-center { - min-width: 160px; + min-width: 120px; text-align: center; + color: var(--text-muted); } .column-right { float: right; } -.sub-section-title { - margin: 0 0 10px; -} - -p { - margin: 20px 0; -} +/* ---- Table layout for settings ---- */ .table { display: table; - margin: 10px 0 0; + margin: 8px 0 0; width: 100%; + border-collapse: collapse; } .table-row { @@ -67,11 +371,13 @@ p { .table-cell { display: table-cell; - padding: 5px 0; + padding: 10px 0; + vertical-align: top; } .table-cell:first-child { width: 30%; + padding-right: 16px; } .table-cell:last-child { @@ -79,11 +385,16 @@ p { } .table-cell:first-child label { - font-weight: bold; + font-family: var(--font-mono); + font-size: 13px; + font-weight: 500; + color: var(--primary); } .table-cell:last-child label { margin-right: 10px; + color: var(--on-surface-variant); + font-size: 14px; } .nested-table { @@ -91,83 +402,99 @@ p { } .nested-table .table-cell { - color: #666; - font-size: .9em; + color: var(--text-muted); + font-size: 0.9em; width: 200px; } #format-caption-container div { - color: #505050; + color: var(--text-muted); display: none; - font-size: .9em; - line-height: 1.4em; + font-size: 14px; + line-height: 1.5em; padding: 10px 0 15px; width: 100%; } +/* ---- Notes / messages ---- */ + +.note { + color: var(--text-muted); + font-size: 14px; + line-height: 1.5em; + margin: 4px 0; +} + .upload-note { - max-width: 300px; + max-width: 320px; +} + +.msg { + font-size: 16px; + color: var(--text-muted); +} + +.error-msg { + color: var(--error); + display: none; +} + +/* ---- Form controls ---- */ + +input[type="text"], +input[type="number"], +textarea, +select { + font-family: var(--font-body); + font-size: 14px; + color: var(--on-surface-variant); + background: var(--surface-container); + border: 1px solid transparent; + border-radius: var(--radius-lg); + padding: 8px 10px; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +input[type="text"]:focus, +input[type="number"]:focus, +textarea:focus, +select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25); +} + +select { + cursor: pointer; +} + +input[type="checkbox"], +input[type="radio"] { + accent-color: var(--primary); + width: 16px; + height: 16px; + vertical-align: middle; + cursor: pointer; } .byte-input { min-height: 160px; min-width: 360px; -} - -.code-output { - height: 200px; width: 100%; + font-family: var(--font-mono); + font-size: 14px; + line-height: 22px; + resize: vertical; } -.note { - color: #666666; - font-size: .9em; - line-height: 1.4em; - margin: 3px 0; +.text-input-size { + margin: 8px 0; + color: var(--on-surface-variant); + font-size: 14px; } -.file-input-entry { - display: flex; - justify-content: space-between; - margin-bottom: 10px; -} - -button, -input[type="file"] { - background: #00CB99; - border-radius: 3px; - border: none; - color: #fff; - font-size: .9em; - font-weight: bold; - margin: 10px 0 0 0; - padding: 4px 8px; -} - -input[type="file"] { - font-size: 1.0em; - padding: 6px 20px; -} - -.generate-button { - margin: 40px 0 20px; -} - -.remove-button { - margin: 0 0 0 10px; - padding: 1px 4px; -} - -.file-info { - color: #505050; - font-size: .7em; - margin-left: 20px; - max-width: 300px; - white-space: pre; -} - -.size-input{ - width: 45px; +.size-input { + width: 72px; } .glyph-input { @@ -175,18 +502,122 @@ input[type="file"] { margin-left: 10px; } +.text-input { + font-family: var(--font-mono); +} + +/* ---- Buttons ---- */ + +button { + font-family: var(--font-mono); + font-size: 13px; + font-weight: 500; + background: var(--primary-container); + color: var(--on-primary-container); + border: none; + border-radius: var(--radius-lg); + padding: 8px 16px; + margin: 10px 8px 0 0; + cursor: pointer; + transition: opacity 0.15s ease, background-color 0.15s ease; +} + +button:hover { + opacity: 0.9; +} + +button:active { + opacity: 0.8; +} + +input[type="file"] { + background: var(--surface-container); + color: var(--on-surface-variant); + border: 2px dashed var(--outline-variant); + border-radius: var(--radius-lg); + font-size: 14px; + padding: 16px 20px; + margin: 10px 0 0; + cursor: pointer; + width: 100%; + max-width: 360px; +} + +input[type="file"]:hover { + background: var(--surface-container-high); +} + +input[type="file"]::file-selector-button { + font-family: var(--font-mono); + font-weight: 500; + background: var(--primary-container); + color: var(--on-primary-container); + border: none; + border-radius: var(--radius-lg); + padding: 6px 12px; + margin-right: 12px; + cursor: pointer; +} + +.generate-button { + background: var(--primary); + color: var(--on-primary); + margin: 32px 8px 20px 0; + padding: 10px 20px; +} + +#copy-button, +#download-button { + background: var(--secondary); + color: var(--on-secondary); + margin-top: 32px; +} + +.remove-button { + margin: 0 0 0 10px; + padding: 2px 8px; + background: var(--surface-container-high); + color: var(--on-surface-variant); +} + +/* ---- File info / size list ---- */ + +.file-input-entry { + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} + +.file-info { + color: var(--text-muted); + font-size: 12px; + margin-left: 20px; + max-width: 320px; + white-space: pre; +} + +.file-name { + display: block; + margin-left: 0; + margin-bottom: 8px; + font-weight: 500; + color: var(--on-surface-variant); +} + +/* Tighten gap between a row and the explanatory note row that follows */ +.note-row .table-cell { + padding-top: 0; +} + #image-size-settings { list-style-type: none; } #image-size-settings li { - margin: 4px 0; + margin: 6px 0; } -#images-canvas-container canvas { - border: 3px solid #88DAC5; - margin: 10px 15px; -} +/* ---- Preview canvases ---- */ #images-canvas-container { align-items: flex-start; @@ -194,33 +625,165 @@ input[type="file"] { flex-wrap: wrap; } +#images-canvas-container canvas { + border: 1px solid var(--outline-variant); + border-radius: var(--radius-lg); + background: var(--editor-bg); + margin: 10px 15px 10px 0; + padding: 4px; +} + +.inlineImg { + border-radius: var(--radius-lg); + margin-top: 8px; +} + +/* ---- Code output (dark editor look) ---- */ + +.code-output { + height: 400px; + width: 100%; + margin-top: 16px; + background: var(--editor-bg); + color: #dcdcaa; + border: 1px solid var(--on-surface-variant); + border-radius: var(--radius-xl); + font-family: var(--font-mono); + font-size: 14px; + line-height: 22px; + padding: 20px; + resize: vertical; +} + +.code-output:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25); +} + +/* ---- Hidden by default (toggled via JS) ---- */ + #arduino-identifier, #adafruit-gfx-settings, #all-same-size { display: none; } -.msg { - font-size: 1.2em; +/* ---- Sponsor block ---- */ + +.sp-block { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; } -.error-msg { - color: #ff0000; +.sp-block img { + vertical-align: middle; +} + +/* ---- Responsive ---- */ + +@media (max-width: 900px) { + .layout { + flex-direction: column; + } + + .sidebar { + position: static; + width: 100%; + flex: none; + } + + .sidebar-nav { + flex-direction: row; + flex-wrap: wrap; + } + + .sidebar-link { + border-right: none; + border-radius: var(--radius-lg); + } + + .sidebar-link.active { + border-right: none; + } +} + +@media (max-width: 640px) { + .topnav-links { + display: none; + } + + .section.step { + padding-left: 32px; + } + + .step-num { + position: static; + margin-bottom: 16px; + } + + .step::before { + display: none; + } + + .column, + .column-right { + float: none; + width: 100%; + } + + .byte-input { + min-width: 0; + } + + .table-cell:first-child, + .table-cell:last-child { + display: block; + width: 100%; + } + + .table-cell:first-child { + padding-bottom: 0; + } +} + +/* ---- Dark theme ---- */ + +:root[data-theme="dark"] { + --primary: #4eddbb; + --primary-container: #1abc9c; + --on-primary: #003a2e; + --on-primary-container: #e5fff7; + --secondary: #b5c8df; + --on-secondary: #17293b; + --on-secondary-container: #cfe2f9; + --background: #0f1416; + --surface: #0f1416; + --surface-container-lowest: #171d1f; + --surface-container-low: #1a2124; + --surface-container: #22282b; + --surface-container-high: #2a3033; + --surface-container-highest: #343a3d; + --on-surface: #e0e3e5; + --on-surface-variant: #c3d0c9; + --on-background: #e0e3e5; + --text-muted: #8a9a94; + --border-subtle: #2d3133; + --outline: #8a938f; + --outline-variant: #414a47; + --editor-bg: #0b0e0f; +} + +:root[data-theme="dark"] .theme-icon-dark { display: none; } -h1{ - padding: 10px; - color: white; - background-color: #00cb99; +:root[data-theme="dark"] .theme-icon-light { + display: inline; } -/*.sp-block { - border: 1px solid rgb(65, 65, 65); - padding: .5rem; - border-radius: 1rem; +:root[data-theme="dark"] .sidebar-link.active { + background: rgba(26, 188, 156, 0.18); } -.sp-btn { - display: block; - margin-top: .5rem; -}*/ diff --git a/index.html b/index.html index 0f3bcaa..9d24cff 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,43 @@ image2cpp + + + -
+
+
+
+ image2cpp + +
+
+ + +
+
+
+ +
+ + +

image2cpp

image2cpp is a simple tool to change images into byte arrays (or arrays back into an images) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
@@ -17,20 +48,16 @@

More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially.
- - GitHub Sponsor - - - Buy Me A Coffee - - + GitHub Sponsor + BMC

-
+
+
1
-

1. Select image

+

Select image

All processing is done locally in your browser; your images are not uploaded or stored anywhere online.

@@ -38,7 +65,7 @@

or

-

1. Paste byte array

+

Paste byte array


x @@ -53,8 +80,9 @@ -
-

2. Image Settings

+
+
2
+

Image Settings

@@ -131,7 +159,7 @@
-
+
Centering the image only works when using a canvas larger than the original image. @@ -165,16 +193,18 @@ -
-

3. Preview

+
+
3
+

Preview

No files selected
-
-

4. Output

+
+
4
+

Output

@@ -286,7 +316,35 @@
+
+ diff --git a/js/script.js b/js/script.js index bef3c87..18f75be 100644 --- a/js/script.js +++ b/js/script.js @@ -808,9 +808,8 @@ function handleImageSelection(evt) { }; const fn = document.createElement('span'); - fn.className = 'file-info'; + fn.className = 'file-info file-name'; fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; - fn.innerHTML += '
'; const rb = document.createElement('button'); rb.className = 'remove-button'; diff --git a/restyle.md b/restyle.md new file mode 100644 index 0000000..607bb59 --- /dev/null +++ b/restyle.md @@ -0,0 +1,404 @@ + + + +image2cpp | Image to Byte Array Converter + + + + + + + + + +
+
+
+image2cpp + +
+
+ + +
+
+
+
+ + + +
+
+

Image to C++ Converter

+

Generate monochrome byte arrays for OLED displays on Arduino or Raspberry Pi. All processing happens in your browser.

+
Support onfavoritecoffee
+ +
+ +
+
+
1
+
+
+
+ +
+cloud_upload +

Drag and drop images here or Browse

+

Supports JPG, PNG, BMP, GIF

+ +
+
+
+ + +
+
+
+
+ +
+
+
2
+
+

Image Configuration

+
+ +
+ +
+ +× + +
+
+ +
+ +
+ + + +
+
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +128 +
+
+ +
+ +
+ +
+ + +
+
+
+
+
+
+ +
+
+
3
+
+
+

Preview

+
+ + +
+
+
+
+Original +
+ +
+
+
+Converted (1-bit) +
+ + +
+
+
+
+
+ +
+
+
4
+
+
+
+

Output Settings

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + +
+
+
+
+
// Generated by image2cpp
+// 128x64px, myBitmap
+
+const unsigned char myBitmap [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; +
+
+info +

If the preview looks scrambled on your hardware, try switching the Draw Mode between Horizontal and Vertical. +

+
+
+
+
+
+
+
+
+ + + + + From 9cc028b5708da78af27e469367ce7436aa274374 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:14:14 +0200 Subject: [PATCH 08/63] update gitinore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e..8e8edfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +.venv +__pycache__ From 05cc9b4685cd9681a8a740a8ea5986a39379593a Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:14:30 +0200 Subject: [PATCH 09/63] update gitinore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8e8edfa..958952a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules -.venv +.venv/ +venv/ __pycache__ From ca532dac084ce73469f7da81dee091409ddabb14 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:19:35 +0200 Subject: [PATCH 10/63] change wording --- README.md | 2 +- index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e06c5e3..2764770 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ An online version of this tool is live at [http://javl.github.io/image2cpp/](http://javl.github.io/image2cpp/) -image2cpp is a simple tool to change images into byte arrays (or your array back into an image) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution. +image2cpp is a simple tool to convert images into byte arrays (or your array back into an image) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution. Alternatively you can also enter a byte array as input to turn it back into an image. This might be useful for debugging, or when you want to write the byte array yourself. I don't know. diff --git a/index.html b/index.html index 9d24cff..852aa8e 100644 --- a/index.html +++ b/index.html @@ -42,7 +42,7 @@

image2cpp

-

image2cpp is a simple tool to change images into byte arrays (or arrays back into an images) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
+

image2cpp is a simple tool to convert images into byte arrays (or arrays back into an images) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
It was originally made to work with the Adafruit OLED library (for which your can find an example sketch for Arduino here) but has been expanded by the community to be useful in all kind of (embedded) projects.

More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

From 88b5f09e385713016af47ca5356b74f71e74f464 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:19:51 +0200 Subject: [PATCH 11/63] align titles in 1st section --- css/style.css | 12 ++++++++++++ index.html | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index b76621f..ad8951c 100644 --- a/css/style.css +++ b/css/style.css @@ -128,6 +128,7 @@ body { .btn-reset { margin: 0; + color: #fff; } /* ---- Layout: sidebar + main ---- */ @@ -341,6 +342,11 @@ code { /* ---- Multi-column (select image row) ---- */ +.input-methods-row { + display: flex; + align-items: flex-start; +} + .column { float: left; } @@ -353,6 +359,7 @@ code { .column-right { float: right; + margin-left: auto; } /* ---- Table layout for settings ---- */ @@ -728,10 +735,15 @@ input[type="file"]::file-selector-button { display: none; } + .input-methods-row { + flex-direction: column; + } + .column, .column-right { float: none; width: 100%; + margin-left: 0; } .byte-input { diff --git a/index.html b/index.html index 852aa8e..5144e28 100644 --- a/index.html +++ b/index.html @@ -55,7 +55,7 @@
1
-
+

Select image

All processing is done locally in your browser; your images are not uploaded or stored anywhere online.
From 72ad8ebd6fde36ca54369a1978c917350a3e93d0 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 16:33:52 +0200 Subject: [PATCH 12/63] make page wider, other small ui tweaks --- css/style.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index ad8951c..d9dced5 100644 --- a/css/style.css +++ b/css/style.css @@ -91,6 +91,7 @@ body { color: var(--secondary); font-weight: 500; font-size: 14px; + text-decoration: underline; } .topnav-links a:hover { @@ -136,7 +137,7 @@ body { .layout { display: flex; gap: 32px; - max-width: 1200px; + max-width: 1400px; margin: auto; padding: 32px 24px 64px; align-items: flex-start; @@ -592,7 +593,7 @@ input[type="file"]::file-selector-button { .file-input-entry { display: flex; justify-content: space-between; - margin-bottom: 10px; + margin: 10px 0; } .file-info { @@ -635,7 +636,7 @@ input[type="file"]::file-selector-button { #images-canvas-container canvas { border: 1px solid var(--outline-variant); border-radius: var(--radius-lg); - background: var(--editor-bg); + background: var(--primary-container); margin: 10px 15px 10px 0; padding: 4px; } From 4a73101c86a31caa8c17b73427e56e5b1379c214 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:36:02 +0200 Subject: [PATCH 13/63] change some colors --- css/style.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index d9dced5..9437417 100644 --- a/css/style.css +++ b/css/style.css @@ -3,7 +3,8 @@ --primary: #006b58; --primary-container: #1abc9c; --on-primary: #ffffff; - --on-primary-container: #004538; + /* --on-primary-container: #004538; */ + --on-primary-container: #e5fff7; --secondary: #4e6073; --on-secondary: #ffffff; --on-secondary-container: #526478; @@ -21,7 +22,8 @@ --border-subtle: #e2e8f0; --outline: #6c7a75; --outline-variant: #bbcac3; - --editor-bg: #1e1e1e; + /* --editor-bg: #1e1e1e; */ + --editor-bg: #ffffff; --error: #ba1a1a; /* Radii */ @@ -129,7 +131,6 @@ body { .btn-reset { margin: 0; - color: #fff; } /* ---- Layout: sidebar + main ---- */ From 70153e6a8f867d703342b19e5406d12790be5d5d Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:37:40 +0200 Subject: [PATCH 14/63] make topnav as wide as contents --- css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index 9437417..c9647c9 100644 --- a/css/style.css +++ b/css/style.css @@ -63,7 +63,7 @@ body { } .topnav-inner { - max-width: 1200px; + max-width: 1400px; margin: auto; height: 64px; padding: 0 24px; From cf74b95ae9d83a3bc4352d8901d871e960c2fe03 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:41:09 +0200 Subject: [PATCH 15/63] add some more formatting options --- index.html | 25 +++++++++-------- js/script.js | 79 ++++++++++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/index.html b/index.html index c4d0343..5c2706c 100644 --- a/index.html +++ b/index.html @@ -287,22 +287,25 @@
+ Swap between MSB and LSB. Useful when working with the u8g2 library.
-
-
-
- Useful when working with the u8g2 library. -
-
- - -
+
- - +
+ + +
+
+ + +
+
+ + +
diff --git a/js/script.js b/js/script.js index f61f39a..d69915b 100644 --- a/js/script.js +++ b/js/script.js @@ -14,10 +14,12 @@ const settings = { backgroundColor: 'white', scale: 1, drawMode: 'horizontal', - removeZeroesCommas: false, + prefix: '0x', ditheringThreshold: 128, ditheringMode: 0, outputFormat: 'plain', + separator: ', ', + outputComments: true, invertColors: false, rotation: 0, }; @@ -64,16 +66,10 @@ const ConversionFunctions = { if (byteIndex < 0) { let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - if (!settings.removeZeroesCommas) { - stringFromBytes += `0x${byteSet}, `; - } else { - stringFromBytes += byteSet; - } + stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; outputIndex++; if (outputIndex >= 16) { - if (!settings.removeZeroesCommas) { - stringFromBytes += '\n'; - } + stringFromBytes += '\n'; outputIndex = 0; } number = 0; @@ -103,11 +99,7 @@ const ConversionFunctions = { } let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - if (!settings.removeZeroesCommas) { - stringFromBytes += `0x${byteSet.toString(16)}, `; - } else { - stringFromBytes += byteSet.toString(16); - } + stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; outputIndex++; if (outputIndex >= 16) { stringFromBytes += '\n'; @@ -139,11 +131,7 @@ const ConversionFunctions = { let byteSet = bitswap(rgb).toString(16); while (byteSet.length < 4) { byteSet = `0${byteSet}`; } - if (!settings.removeZeroesCommas) { - stringFromBytes += `0x${byteSet}, `; - } else { - stringFromBytes += byteSet; - } + stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; // add newlines every 16 bytes outputIndex++; if (outputIndex >= 16) { @@ -173,11 +161,7 @@ const ConversionFunctions = { let byteSet = bitswap(rgb).toString(16); while (byteSet.length < 8) { byteSet = `0${byteSet}`; } - if (!settings.removeZeroesCommas) { - stringFromBytes += `0x${byteSet}, `; - } else { - stringFromBytes += byteSet; - } + stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; // add newlines every 16 bytes outputIndex++; @@ -214,11 +198,7 @@ const ConversionFunctions = { if (byteIndex < 0) { let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - if (!settings.removeZeroesCommas) { - stringFromBytes += `0x${byteSet}, `; - } else { - stringFromBytes += byteSet; - } + stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; outputIndex++; if (outputIndex >= 16) { stringFromBytes += '\n'; @@ -478,6 +458,13 @@ function updateBoolean(fieldName) { updateAllImages(); } +// Easy way to update settings controlled by a text input +// eslint-disable-next-line no-unused-vars +function updateString(fieldName) { + settings[fieldName] = document.getElementById(fieldName).value; + updateAllImages(); +} + // Convert hex to binary function hexToBinary(s) { let i; @@ -912,7 +899,7 @@ function generateOutputString() { code = `\t${code.split('\n').join('\n\t')}\n`; // const variableCount = images.length() > 1 ? count++ : ''; - const comment = `// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; + const comment = settings.outputComments ? `// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n` : ''; bytesUsed += code.split('\n').length * 16; // 16 bytes per line. const varname = getIdentifier() + (image.glyph ? image.glyph.replace(/[^a-zA-Z0-9]/g, '_') : ''); @@ -922,7 +909,12 @@ function generateOutputString() { }); varQuickArray.sort(); - outputString += `\n// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = ${bytesUsed})\n`; + if (outputString.length > 0) { + outputString += '\n'; + } + if (settings.outputComments) { + outputString += `// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = ${bytesUsed})\n`; + } outputString += `const int ${getIdentifier()}_allArray_LEN = ${varQuickArray.length};\n`; outputString += `const ${getImageType()}* ${getIdentifier()}_allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; break; @@ -933,7 +925,7 @@ function generateOutputString() { images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; - comment = `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n`; + comment = settings.outputComments ? `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n` : ''; outputString += comment + code; }); @@ -952,7 +944,7 @@ function generateOutputString() { images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; - comment = `\t// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; + comment = settings.outputComments ? `\t// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n` : ''; outputString += comment + code; if (image.glyph.length === 1) { useGlyphs++; @@ -988,7 +980,7 @@ function generateOutputString() { if (image !== images.last()) { code += ','; } - code += `// '${image.glyph}'\n`; + code += settings.outputComments ? `// '${image.glyph}'\n` : '\n'; offset += image.canvas.width; }); code += '};\n'; @@ -1011,7 +1003,7 @@ function generateOutputString() { images.each((image) => { code = imageToString(image); let comment = ''; - if (image.glyph) { + if (image.glyph && settings.outputComments) { comment = (`// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`); } if (image.img !== images.first().img) { @@ -1073,7 +1065,6 @@ function updateOutputFormat(elm) { let caption = document.getElementById('format-caption-container'); const adafruitGfx = document.getElementById('adafruit-gfx-settings'); const arduino = document.getElementById('arduino-identifier'); - const removeZeroesCommasContainer = document.getElementById('remove-zeroes-commas-container'); document.getElementById('code-output').value = ''; for (let i = 0; i < caption.children.length; i++) { @@ -1082,15 +1073,13 @@ function updateOutputFormat(elm) { caption = document.querySelector(`div[data-caption='${elm.value}']`); if (caption) caption.style.display = 'block'; - if (elm.value !== 'plain') { - arduino.style.display = 'block'; - removeZeroesCommasContainer.style.display = 'none'; - settings.removeZeroesCommas = false; - document.getElementById('removeZeroesCommas').checked = false; - } else { - arduino.style.display = 'none'; - removeZeroesCommasContainer.style.display = 'table-row'; - } + arduino.style.display = elm.value !== 'plain' ? 'block' : 'none'; + + settings.prefix = '0x'; + document.getElementById('prefix').value = '0x'; + settings.separator = ', '; + document.getElementById('separator').value = ', '; + if (elm.value === 'adafruit_gfx') { adafruitGfx.style.display = 'block'; } else { From f2ef5e5f1c5894211522faca61c9d175b1116a8b Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:43:24 +0200 Subject: [PATCH 16/63] update layout and error checks --- README.md | 2 +- css/style.css | 35 +++++++++++++++++-- index.html | 95 ++++++++++++++++++++++++++++----------------------- js/script.js | 74 ++++++++++++++++++++++++++++++--------- 4 files changed, 143 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index eebda01..ba1a2e6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ An online version of this tool is live at [http://javl.github.io/image2cpp/](http://javl.github.io/image2cpp/) -image2cpp is a simple tool to convert images into byte arrays (or your array back into an image) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution. +image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution. Alternatively you can also enter a byte array as input to turn it back into an image. This might be useful for debugging, or when you want to write the byte array yourself. I don't know. diff --git a/css/style.css b/css/style.css index c9647c9..742d483 100644 --- a/css/style.css +++ b/css/style.css @@ -438,6 +438,11 @@ code { max-width: 320px; } +#continue-note { + display: none; + margin-top: 30px; +} + .msg { font-size: 16px; color: var(--text-muted); @@ -495,6 +500,12 @@ input[type="radio"] { line-height: 22px; resize: vertical; } +#text-input-error { + color: var(--error); + font-size: 14px; + margin-top: 8px; + display: none; +} .text-input-size { margin: 8px 0; @@ -506,6 +517,21 @@ input[type="radio"] { width: 72px; } +.sub-field { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; +} + +.sub-field:first-child { + margin-top: 0; +} + +.sub-field-label { + min-width: 160px; +} + .glyph-input { width: 80px; margin-left: 10px; @@ -542,7 +568,7 @@ button:active { input[type="file"] { background: var(--surface-container); color: var(--on-surface-variant); - border: 2px dashed var(--outline-variant); + /* border: 2px dashed var(--outline-variant); */ border-radius: var(--radius-lg); font-size: 14px; padding: 16px 20px; @@ -589,6 +615,9 @@ input[type="file"]::file-selector-button { color: var(--on-surface-variant); } +#no-images-error { + margin-bottom: 10px; +} /* ---- File info / size list ---- */ .file-input-entry { @@ -652,9 +681,9 @@ input[type="file"]::file-selector-button { .code-output { height: 400px; width: 100%; - margin-top: 16px; + /* margin-top: 16px; */ background: var(--editor-bg); - color: #dcdcaa; + color: #000000; border: 1px solid var(--on-surface-variant); border-radius: var(--radius-xl); font-family: var(--font-mono); diff --git a/index.html b/index.html index 5c2706c..f966083 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@
@@ -24,7 +24,7 @@ - +
@@ -42,8 +42,8 @@

image2cpp

-

image2cpp is a simple tool to convert images into byte arrays (or arrays back into an images) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
- It was originally made to work with the Adafruit OLED library (for which your can find an example sketch for Arduino here) but +

image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
+ It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but has been expanded by the community to be useful in all kind of (embedded) projects.

More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

@@ -58,23 +58,24 @@

Select image

-
All processing is done locally in your browser; your images are not uploaded or stored anywhere online.
+
You can import multiple images at once.
All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

-
-
-

or

+
+
Select more images or continue to step 2
-

Paste byte array

+

or paste byte array


x px
+
Set this to the size of your image.
Common sizes: 32x32, 64x48, 128x32, 128x64.
+
Byte array is invalid (or empty).
Read images appear at step 3 below
@@ -91,13 +92,13 @@
    Only images file type are allowed
    -
    No files selected
    +
    Select one or more images at step 1 first.
    -
    +
    @@ -105,6 +106,9 @@ +
    + Used for parts of the canvas that are not covered by your image. +
    @@ -132,7 +136,8 @@
    - 0 - 255; if the brightness of a pixel is above the given level the pixel becomes white, otherwise they become black. When using alpha, opaque and transparent are used instead.
    + 0 - 255; if the brightness of a pixel is above the given level the pixel becomes white, otherwise they become black. When using alpha, opaque and transparent are used instead. Default is 128. +
    @@ -175,7 +180,7 @@ - + @@ -208,15 +213,42 @@
    -
    +
    - + + + + + + + +
    + If your image looks all messed up on your display, like the image below, try using a different mode. +
    + + +
    +
    + +
    +
    +
    + + + + + + + +
    +
    + Outputs just the raw bytes of the image(s). +
    Adds some extra Arduino code around the output for easy copy-paste into this example. @@ -260,28 +292,6 @@
    -
    -
    -
    - - -
    - If your image looks all messed up on your display, like the image below, try using a different mode. -
    - - -
    -
    -
    @@ -314,8 +324,9 @@
    - + +
    Select one or more images at step 1 first.
    diff --git a/js/script.js b/js/script.js index d69915b..dba19f1 100644 --- a/js/script.js +++ b/js/script.js @@ -639,6 +639,21 @@ function listToImageVertical(list, canvas) { img.src = canvas.toDataURL('image/png'); } +// Set width/height preset for byte array text input +// eslint-disable-next-line no-unused-vars +function setTextInputSize(width, height) { + document.getElementById('text-input-width').value = width; + document.getElementById('text-input-height').value = height; +} + +// Show/hide the "no images" error and report whether images are available +function checkImagesAvailable() { + const error = document.getElementById('no-images-error'); + const hasImages = images.length() > 0; + error.style.display = hasImages ? 'none' : 'block'; + return hasImages; +} + // Handle inserting an image by pasting code // eslint-disable-next-line no-unused-vars function handleTextInput(drawMode) { @@ -678,6 +693,13 @@ function handleTextInput(drawMode) { // Split into list const list = input.split(','); + if (list.length === 1 && list[0] === '') { + // eslint-disable-next-line no-alert + const errorEl = document.getElementById('text-input-error'); + errorEl.style.display = 'block'; + return; + } + if (drawMode === 'horizontal') { listToImageHorizontal(list, canvas); } else { @@ -726,6 +748,12 @@ function handleImageSelection(evt) { }); } + if (files.length > 0) { + document.getElementById('continue-note').style.display = 'block'; + } else { + document.getElementById('continue-note').style.display = 'none'; + } + for (let i = 0; files[i]; i++) { // Only process image files. if (!files[i].type.match('image.*')) { @@ -751,7 +779,7 @@ function handleImageSelection(evt) { const fileInputColumnEntryRemoveButton = document.createElement('button'); fileInputColumnEntryRemoveButton.className = 'remove-button'; - fileInputColumnEntryRemoveButton.innerHTML = 'remove'; + fileInputColumnEntryRemoveButton.innerHTML = 'remove image'; const canvas = document.createElement('canvas'); @@ -799,15 +827,7 @@ function handleImageSelection(evt) { image.glyph = gi.value; }; - const fn = document.createElement('span'); - fn.className = 'file-info file-name'; - fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; - - const rb = document.createElement('button'); - rb.className = 'remove-button'; - rb.innerHTML = 'remove'; - - const fileInputColumn = document.getElementById('file-input-column'); + const fileInputColumn = document.getElementById('file-input-column-items'); const imageSizeSettings = document.getElementById('image-size-settings'); const canvasContainer = document.getElementById('images-canvas-container'); @@ -822,6 +842,7 @@ function handleImageSelection(evt) { document.getElementById('all-same-size').style.display = 'none'; } if (images.length() === 0) { + document.getElementById('file-input').value = ''; noFileSelected.forEach((el) => { // eslint-disable-next-line no-param-reassign el.style.display = 'block'; @@ -830,7 +851,18 @@ function handleImageSelection(evt) { updateAllImages(); }; + const rb = document.createElement('button'); + rb.className = 'remove-button'; + rb.innerHTML = 'remove image'; rb.onclick = removeButtonOnClick; + + const fn = document.createElement('span'); + fn.className = 'file-info file-name'; + fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; + + + fn.appendChild(rb); + fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); @@ -843,7 +875,6 @@ function handleImageSelection(evt) { imageEntry.appendChild(h); imageEntry.appendChild(gil); imageEntry.appendChild(gi); - imageEntry.appendChild(rb); imageSizeSettings.appendChild(imageEntry); @@ -856,6 +887,7 @@ function handleImageSelection(evt) { document.getElementById('all-same-size').style.display = 'block'; } placeImage(images.last()); + checkImagesAvailable(); }; img.src = file.target.result; }; @@ -883,6 +915,8 @@ function getIdentifier() { // Output the image string to the textfield // eslint-disable-next-line no-unused-vars function generateOutputString() { + if (!checkImagesAvailable()) return; + let outputString = ''; let code = ''; @@ -1018,17 +1052,24 @@ function generateOutputString() { } document.getElementById('code-output').value = outputString; - document.getElementById('copy-button').disabled = false; } // Copy the final output to the clipboard // eslint-disable-next-line no-unused-vars function copyOutput() { - navigator.clipboard.writeText(document.getElementById('code-output').value); + if (!checkImagesAvailable()) return; + + const output = document.getElementById('code-output'); + if (!output.value) { + generateOutputString(); + } + navigator.clipboard.writeText(output.value); } // eslint-disable-next-line no-unused-vars function downloadBinFile() { + if (!checkImagesAvailable()) return; + let raw = []; images.each((image) => { const data = imageToString(image) @@ -1102,12 +1143,11 @@ function updateRadio(fieldName) { } window.onload = () => { - document.getElementById('copy-button').disabled = true; - // Add events to the file input button const fileInput = document.getElementById('file-input'); fileInput.addEventListener('click', () => { this.value = null; }, false); fileInput.addEventListener('change', handleImageSelection, false); - document.getElementById('outputFormat').value = 'arduino'; - document.getElementById('outputFormat').onchange(); + const outputFormatArduino = document.getElementById('outputFormatArduino'); + outputFormatArduino.checked = true; + outputFormatArduino.onchange(); }; From f4f087db7634111153a86ec8b37e35596e7d76da Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:45:41 +0200 Subject: [PATCH 17/63] update intro text --- index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index f966083..441cab4 100644 --- a/index.html +++ b/index.html @@ -47,9 +47,10 @@ has been expanded by the community to be useful in all kind of (embedded) projects.

    More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

    - Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially.
    - GitHub Sponsor - BMC + Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially. +

    +

    + GitHub Sponsor BMC

    From 9d70bae4baefd489c14a1db1e728324b2819b424 Mon Sep 17 00:00:00 2001 From: javl Date: Fri, 14 Aug 2026 18:55:16 +0200 Subject: [PATCH 18/63] Update tests --- css/style.css | 18 ---------------- js/script.js | 1 - tests/golden/kitchen_sink.txt | 12 +++++------ tests/golden/remove_zeroes_commas.txt | 7 +++++- tests/test_conversions.py | 31 ++++++++++++++++++--------- 5 files changed, 33 insertions(+), 36 deletions(-) diff --git a/css/style.css b/css/style.css index 742d483..14f9a4f 100644 --- a/css/style.css +++ b/css/style.css @@ -3,34 +3,26 @@ --primary: #006b58; --primary-container: #1abc9c; --on-primary: #ffffff; - /* --on-primary-container: #004538; */ --on-primary-container: #e5fff7; --secondary: #4e6073; --on-secondary: #ffffff; - --on-secondary-container: #526478; --background: #f7f9fb; - --surface: #f7f9fb; --surface-container-lowest: #ffffff; --surface-container-low: #f2f4f6; --surface-container: #eceef0; --surface-container-high: #e6e8ea; - --surface-container-highest: #e0e3e5; --on-surface: #191c1e; --on-surface-variant: #3c4a45; --on-background: #191c1e; --text-muted: #64748b; --border-subtle: #e2e8f0; - --outline: #6c7a75; --outline-variant: #bbcac3; - /* --editor-bg: #1e1e1e; */ --editor-bg: #ffffff; --error: #ba1a1a; /* Radii */ - --radius: 0.125rem; --radius-lg: 0.25rem; --radius-xl: 0.5rem; - --radius-full: 0.75rem; /* Fonts */ --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; @@ -353,12 +345,6 @@ code { float: left; } -.column-center { - min-width: 120px; - text-align: center; - color: var(--text-muted); -} - .column-right { float: right; margin-left: auto; @@ -801,20 +787,16 @@ input[type="file"]::file-selector-button { --on-primary-container: #e5fff7; --secondary: #b5c8df; --on-secondary: #17293b; - --on-secondary-container: #cfe2f9; --background: #0f1416; - --surface: #0f1416; --surface-container-lowest: #171d1f; --surface-container-low: #1a2124; --surface-container: #22282b; --surface-container-high: #2a3033; - --surface-container-highest: #343a3d; --on-surface: #e0e3e5; --on-surface-variant: #c3d0c9; --on-background: #e0e3e5; --text-muted: #8a9a94; --border-subtle: #2d3133; - --outline: #8a938f; --outline-variant: #414a47; --editor-bg: #0b0e0f; } diff --git a/js/script.js b/js/script.js index dba19f1..dc1e87d 100644 --- a/js/script.js +++ b/js/script.js @@ -860,7 +860,6 @@ function handleImageSelection(evt) { fn.className = 'file-info file-name'; fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; - fn.appendChild(rb); fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; diff --git a/tests/golden/kitchen_sink.txt b/tests/golden/kitchen_sink.txt index 7695d1d..58a86a7 100644 --- a/tests/golden/kitchen_sink.txt +++ b/tests/golden/kitchen_sink.txt @@ -1,7 +1,7 @@ // 'test_pattern', 32x24px -80000000804020100804020100000000 -00000000ff7fffff904fb04bb5ffaaff -0001000208010e010e030d0e8b4f2f1f -0c0c0303fcfcf3f31cac5323dc7cd3f3 -000000aa009a611ee15eabfdb7ffffff -cccc3333cccc3333cccc3333cccc3333 +0x80, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0xff, 0xff, 0x90, 0x4f, 0xb0, 0x4b, 0xb5, 0xff, 0xaa, 0xff, +0x00, 0x01, 0x00, 0x02, 0x08, 0x01, 0x0e, 0x01, 0x0e, 0x03, 0x0d, 0x0e, 0x8b, 0x4f, 0x2f, 0x1f, +0x0c, 0x0c, 0x03, 0x03, 0xfc, 0xfc, 0xf3, 0xf3, 0x1c, 0xac, 0x53, 0x23, 0xdc, 0x7c, 0xd3, 0xf3, +0x00, 0x00, 0x00, 0xaa, 0x00, 0x9a, 0x61, 0x1e, 0xe1, 0x5e, 0xab, 0xfd, 0xb7, 0xff, 0xff, 0xff, +0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33 \ No newline at end of file diff --git a/tests/golden/remove_zeroes_commas.txt b/tests/golden/remove_zeroes_commas.txt index 3da87fe..53423dd 100644 --- a/tests/golden/remove_zeroes_commas.txt +++ b/tests/golden/remove_zeroes_commas.txt @@ -1,2 +1,7 @@ // 'test_pattern', 32x24px -333300ff333300ffcccc00ffcccc00ff333300ff333300ffcccc00ffcccc00ff333300ff333300ffcccc00ffcccc00ff0f0f7fff0f0fbfff0f0fdfff0f0fefff0f0ff7ff0f0ffbff0f0ffdff0f0ffeff0f0fff7f0f0fffbf0f0fffdf0f0fffee \ No newline at end of file +333300ff333300ffcccc00ffcccc00ff +333300ff333300ffcccc00ffcccc00ff +333300ff333300ffcccc00ffcccc00ff +0f0f7fff0f0fbfff0f0fdfff0f0fefff +0f0ff7ff0f0ffbff0f0ffdff0f0ffeff +0f0fff7f0f0fffbf0f0fffdf0f0fffee diff --git a/tests/test_conversions.py b/tests/test_conversions.py index b050203..bf9321b 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -43,7 +43,15 @@ BASE_SETTINGS = { "flipHorizontally": False, "flipVertically": False, "bitswap": False, - "removeZeroesCommas": False, + "prefix": "0x", + "separator": ", ", +} + +OUTPUT_FORMAT_IDS = { + "plain": "outputFormatPlain", + "arduino": "outputFormatArduino", + "arduino_single": "outputFormatArduinoSingle", + "adafruit_gfx": "outputFormatAdafruitGfx", } # (scenario name, overrides on top of BASE_SETTINGS, optional canvas resize) @@ -69,7 +77,7 @@ SCENARIOS = [ ("dithering_threshold_low", {"ditheringThreshold": 64}, None), ("dithering_threshold_high", {"ditheringThreshold": 200}, None), ("bitswap", {"bitswap": True}, None), - ("remove_zeroes_commas", {"removeZeroesCommas": True}, None), + ("remove_zeroes_commas", {"prefix": "", "separator": ""}, None), # scale=1 (original) on a canvas larger than the image so the # background actually shows through in the margins. ("background_white_larger_canvas", {"backgroundColor": "white"}, (48, 40)), @@ -116,7 +124,10 @@ def reset_canvas_size(page, width, height): def apply_settings(page, settings): page.select_option("#drawMode", settings["drawMode"]) - page.select_option("#outputFormat", settings["outputFormat"]) + # Selecting an output format resets prefix/separator to their defaults + # (updateOutputFormat() in script.js), so this must run before those two + # fields are filled in below. + page.check(f"#{OUTPUT_FORMAT_IDS[settings['outputFormat']]}") page.check(f"#backgroundColor{settings['backgroundColor'].capitalize()}") set_checkbox(page, "#invertColors", settings["invertColors"]) page.select_option("#ditheringMode", str(settings["ditheringMode"])) @@ -128,8 +139,8 @@ def apply_settings(page, settings): set_checkbox(page, "#flipHorizontally", settings["flipHorizontally"]) set_checkbox(page, "#flipVertically", settings["flipVertically"]) set_checkbox(page, "#bitswap", settings["bitswap"]) - if settings["outputFormat"] == "plain": - set_checkbox(page, "#removeZeroesCommas", settings["removeZeroesCommas"]) + page.fill("#prefix", settings["prefix"]) + page.fill("#separator", settings["separator"]) def set_checkbox(page, selector, checked): @@ -154,14 +165,14 @@ def run_scenario(page, name, overrides, resize): def roundtrip_eligible(settings): """Only plain-format, non-bitswapped 1-bit output is re-importable via the "Paste byte array" box: other draw modes aren't supported by the parser, - and bitswap/removeZeroesCommas mangle the byte stream so it can't be read - back (bitswap reorders bits with no importer to undo it; removeZeroesCommas - drops the separators the parser splits on).""" + bitswap mangles the byte stream with no importer to undo it, and the + parser splits bytes on commas, so a separator without one (e.g. the + empty string used to strip formatting) can't be read back.""" return ( settings["drawMode"] in ("horizontal1bit", "vertical1bit") and settings["outputFormat"] == "plain" and not settings["bitswap"] - and not settings["removeZeroesCommas"] + and "," in settings["separator"] ) @@ -173,7 +184,7 @@ def run_roundtrip(page, output, draw_mode, width, height): page.goto(INDEX_HTML.as_uri()) # window.onload defaults outputFormat to "arduino"; force it back to plain # to match the format the scenario was exported in. - page.select_option("#outputFormat", "plain") + page.check(f"#{OUTPUT_FORMAT_IDS['plain']}") page.fill("#byte-input", output) page.fill("#text-input-width", str(width)) page.fill("#text-input-height", str(height)) From 98514511fbcb4fb337221f13bbbabd0aa21aec8f Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 08:53:01 +0200 Subject: [PATCH 19/63] Clean up and simplify our hexToBinary method --- js/script.js | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/js/script.js b/js/script.js index dc1e87d..85d74fb 100644 --- a/js/script.js +++ b/js/script.js @@ -467,40 +467,13 @@ function updateString(fieldName) { // Convert hex to binary function hexToBinary(s) { - let i; let ret = ''; - // lookup table for easier conversion. "0" characters are padded for "1" to "7" - const lookupTable = { - 0: '0000', - 1: '0001', - 2: '0010', - 3: '0011', - 4: '0100', - 5: '0101', - 6: '0110', - 7: '0111', - 8: '1000', - 9: '1001', - a: '1010', - b: '1011', - c: '1100', - d: '1101', - e: '1110', - f: '1111', - A: '1010', - B: '1011', - C: '1100', - D: '1101', - E: '1110', - F: '1111', - }; - for (i = 0; i < s.length; i += 1) { - // eslint-disable-next-line no-prototype-builtins - if (lookupTable.hasOwnProperty(s[i])) { - ret += lookupTable[s[i]]; - } else { + for (let i = 0; i < s.length; i += 1) { + const nibble = parseInt(s[i], 16); + if (Number.isNaN(nibble)) { return { valid: false, s }; } + ret += nibble.toString(2).padStart(4, '0'); } return { valid: true, result: ret }; } From 43e3af1b81c4d3ef25afc6bdfcd1b91f84276f82 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 08:55:09 +0200 Subject: [PATCH 20/63] Reuse method for glyphcomment instead of duplicate lines per section --- js/script.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/js/script.js b/js/script.js index 85d74fb..0ce7740 100644 --- a/js/script.js +++ b/js/script.js @@ -884,6 +884,13 @@ function getIdentifier() { return (vn && vn.value.length) ? vn.value : identifier; } +// Builds the "// 'glyph', WxHpx" comment line placed before each image's +// byte array, or '' when comments are disabled. Indent is prefixed when given. +function glyphComment(image, indent = '') { + if (!settings.outputComments) return ''; + return `${indent}// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; +} + // Output the image string to the textfield // eslint-disable-next-line no-unused-vars function generateOutputString() { @@ -905,7 +912,7 @@ function generateOutputString() { code = `\t${code.split('\n').join('\n\t')}\n`; // const variableCount = images.length() > 1 ? count++ : ''; - const comment = settings.outputComments ? `// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n` : ''; + const comment = glyphComment(image); bytesUsed += code.split('\n').length * 16; // 16 bytes per line. const varname = getIdentifier() + (image.glyph ? image.glyph.replace(/[^a-zA-Z0-9]/g, '_') : ''); @@ -931,7 +938,7 @@ function generateOutputString() { images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; - comment = settings.outputComments ? `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n` : ''; + comment = glyphComment(image, '\t'); outputString += comment + code; }); @@ -950,7 +957,7 @@ function generateOutputString() { images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; - comment = settings.outputComments ? `\t// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n` : ''; + comment = glyphComment(image, '\t'); outputString += comment + code; if (image.glyph.length === 1) { useGlyphs++; @@ -1008,10 +1015,7 @@ function generateOutputString() { default: { // plain images.each((image) => { code = imageToString(image); - let comment = ''; - if (image.glyph && settings.outputComments) { - comment = (`// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`); - } + let comment = image.glyph ? glyphComment(image) : ''; if (image.img !== images.first().img) { comment = `\n${comment}`; } From 3da6cf9607f20a4a1c23e4c12a329ffb0163b381 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:17:42 +0200 Subject: [PATCH 21/63] Create a small writer method to reuse with all conversion types instead of handling formatting separate for each type --- js/script.js | 341 +++++++++++++++++++++------------------------------ 1 file changed, 139 insertions(+), 202 deletions(-) diff --git a/js/script.js b/js/script.js index 0ce7740..89e408a 100644 --- a/js/script.js +++ b/js/script.js @@ -36,54 +36,65 @@ function bitswap(b) { return b; } +// Accumulates formatted bytes (prefix/value/separator, wrapped every +// wrapWidth bytes) so our ConversionFunctions can use this formatting. +// wrapWidth defaults to 16 bytes per line; horizontal888 wraps +// once per image row (canvasWidth) instead. +function makeByteWriter(wrapWidth = 16) { + let out = ''; + let count = 0; + return { + push(value, hexWidth) { + const byteSet = bitswap(value).toString(16).padStart(hexWidth, '0'); + out += `${settings.prefix}${byteSet}${settings.separator}`; + count++; + if (count >= wrapWidth) { + out += '\n'; + count = 0; + } + }, + result() { return out; }, + }; +} + +// Shared by horizontal1bit/horizontalAlpha: pack 8 samples (one per pixel, +// MSB first) into a byte, resetting early at the end of a row or the image +// so a partial final byte is still zero-padded instead of dropped. +function packBitsHorizontal(data, canvasWidth, writer, sampleAt) { + let byteIndex = 7; + let number = 0; + for (let index = 0; index < data.length; index += 4) { + if (sampleAt(data, index) > settings.ditheringThreshold) { + number += 2 ** byteIndex; + } + byteIndex--; + + const isRowEnd = index !== 0 && (((index / 4) + 1) % canvasWidth) === 0; + const isImageEnd = index === data.length - 4; + if (isRowEnd || isImageEnd) { + byteIndex = -1; + } + + if (byteIndex < 0) { + writer.push(number, 2); + number = 0; + byteIndex = 7; + } + } +} + const ConversionFunctions = { // Output the image as a string for horizontally drawing displays horizontal1bit(data, canvasWidth) { - let stringFromBytes = ''; - let outputIndex = 0; - let byteIndex = 7; - let number = 0; - - // format is RGBA, so move 4 steps per pixel - for (let index = 0; index < data.length; index += 4) { - // Get the average of the RGB (we ignore A) - const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; - if (avg > settings.ditheringThreshold) { - number += 2 ** byteIndex; - } - byteIndex--; - - // if this was the last pixel of a row or the last pixel of the - // image, fill up the rest of our byte with zeros so it always contains 8 bits - if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { - // for(var i=byteIndex;i>-1;i--){ - // number += Math.pow(2, i); - // } - byteIndex = -1; - } - - // When we have the complete 8 bits, combine them into a hex value - if (byteIndex < 0) { - let byteSet = bitswap(number).toString(16); - if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; - outputIndex++; - if (outputIndex >= 16) { - stringFromBytes += '\n'; - outputIndex = 0; - } - number = 0; - byteIndex = 7; - } - } - return stringFromBytes; + const writer = makeByteWriter(); + const avgRgb = (d, i) => (d[i] + d[i + 1] + d[i + 2]) / 3; + packBitsHorizontal(data, canvasWidth, writer, avgRgb); + return writer.result(); }, // Output the image as a string for vertically drawing displays - // eslint-disable-next-line no-unused-vars - vertical1bit(data, canvasWidth) { - let stringFromBytes = ''; - let outputIndex = 0; + vertical1bit(data) { + const writer = makeByteWriter(); for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) { for (let x = 0; x < settings.screenWidth; x++) { let byteIndex = 7; @@ -97,118 +108,49 @@ const ConversionFunctions = { } byteIndex--; } - let byteSet = bitswap(number).toString(16); - if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; - outputIndex++; - if (outputIndex >= 16) { - stringFromBytes += '\n'; - outputIndex = 0; - } + writer.push(number, 2); } } - return stringFromBytes; + return writer.result(); }, // Output the image as a string for 565 displays (horizontally) // eslint-disable-next-line no-unused-vars horizontal565(data, canvasWidth) { - let stringFromBytes = ''; - let outputIndex = 0; - + const writer = makeByteWriter(); // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { - // Get the RGB values const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; - // calculate the 565 color value + // Calculate the 565 color value // eslint-disable-next-line no-bitwise const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3); - // Split up the color value in two bytes - // const firstByte = (rgb >> 8) & 0xff; - // const secondByte = rgb & 0xff; - - let byteSet = bitswap(rgb).toString(16); - while (byteSet.length < 4) { byteSet = `0${byteSet}`; } - stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; - // add newlines every 16 bytes - outputIndex++; - if (outputIndex >= 16) { - stringFromBytes += '\n'; - outputIndex = 0; - } + writer.push(rgb, 4); } - return stringFromBytes; + return writer.result(); }, - // Output the image as a string for rgb888 displays (horizontally) + // Output the image as a string for rgb888 displays (horizontally), one + // image row per output line horizontal888(data, canvasWidth) { - let stringFromBytes = ''; - let outputIndex = 0; - + const writer = makeByteWriter(canvasWidth); // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { - // Get the RGB values + // Split into RGB and pack into a single 24-bit value (MSB first) const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; - // calculate the 565 color value // eslint-disable-next-line no-bitwise const rgb = (r << 16) | (g << 8) | (b); - // Split up the color value in two bytes - // const firstByte = (rgb >> 8) & 0xff; - // const secondByte = rgb & 0xff; - - let byteSet = bitswap(rgb).toString(16); - while (byteSet.length < 8) { byteSet = `0${byteSet}`; } - stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; - - // add newlines every 16 bytes - outputIndex++; - if (outputIndex >= canvasWidth) { - stringFromBytes += '\n'; - outputIndex = 0; - } + writer.push(rgb, 8); } - return stringFromBytes; + return writer.result(); }, // Output the alpha mask as a string for horizontally drawing displays horizontalAlpha(data, canvasWidth) { - let stringFromBytes = ''; - let outputIndex = 0; - let byteIndex = 7; - let number = 0; - - // format is RGBA, so move 4 steps per pixel - for (let index = 0; index < data.length; index += 4) { - // Get alpha part of the image data - const alpha = data[index + 3]; - if (alpha > settings.ditheringThreshold) { - number += 2 ** byteIndex; - } - byteIndex--; - - // if this was the last pixel of a row or the last pixel of the - // image, fill up the rest of our byte with zeros so it always contains 8 bits - if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { - byteIndex = -1; - } - - // When we have the complete 8 bits, combine them into a hex value - if (byteIndex < 0) { - let byteSet = bitswap(number).toString(16); - if (byteSet.length === 1) { byteSet = `0${byteSet}`; } - stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`; - outputIndex++; - if (outputIndex >= 16) { - stringFromBytes += '\n'; - outputIndex = 0; - } - number = 0; - byteIndex = 7; - } - } - return stringFromBytes; + const writer = makeByteWriter(); + packBitsHorizontal(data, canvasWidth, writer, (d, i) => d[i + 3]); + return writer.result(); }, }; settings.conversionFunction = ConversionFunctions.horizontal1bit; @@ -489,10 +431,50 @@ function getImageType() { return 'unsigned char'; } +// Use the horizontally oriented list to draw the image +// Validates and decodes a comma-split list of pasted hex byte strings into +// one contiguous bit string (MSB first, each entry padded to a full byte). +// Returns { valid: true, bits } or { valid: false, s } naming the bad entry. +function hexListToBits(list) { + let bits = ''; + for (let i = 0; i < list.length; i++) { + const binString = hexToBinary(list[i]); + if (!binString.valid) { + return binString; + } + bits += binString.result.length === 4 ? `${binString.result}0000` : binString.result; + } + return { valid: true, bits }; +} + +function reportInvalidByteArray(s) { + // eslint-disable-next-line no-alert + alert('Something went wrong converting the string. Make sure there are no comments in your input.'); + // eslint-disable-next-line no-console + console.error('invalid hexToBinary: ', s); +} + +// Save the canvas contents inside the (first) image object so it can be +// reused when scaling/inverting/etc. +function commitCanvasToFirstImage(canvas) { + const img = new Image(); + img.onload = () => { + images.first().img = img; + }; + img.src = canvas.toDataURL('image/png'); +} + // Use the horizontally oriented list to draw the image function listToImageHorizontal(list, canvas) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); + + const decoded = hexListToBits(list); + if (!decoded.valid) { + reportInvalidByteArray(decoded.s); + return; + } + const imgData = ctx.createImageData(canvas.width, canvas.height); let index = 0; @@ -501,51 +483,24 @@ function listToImageHorizontal(list, canvas) { let widthCounter = 0; // Move the list into the imageData object - for (let i = 0; i < list.length; i++) { - let binString = hexToBinary(list[i]); - if (!binString.valid) { - // eslint-disable-next-line no-alert - alert('Something went wrong converting the string. Make sure there are no comments in your input?'); - // eslint-disable-next-line no-console - console.error('invalid hexToBinary: ', binString.s); - return; + for (let k = 0; k < decoded.bits.length; k++, widthCounter++) { + // if we've counted enough bits, reset counter for next line + if (widthCounter >= widthRoundedUp) { + widthCounter = 0; } - binString = binString.result; - if (binString.length === 4) { - binString += '0000'; - } - - // Check if pixel is white or black - for (let k = 0; k < binString.length; k++, widthCounter++) { - // if we've counted enough bits, reset counter for next line - if (widthCounter >= widthRoundedUp) { - widthCounter = 0; - } - // skip 'artifact' pixels due to rounding up to a byte - if (widthCounter < canvas.width) { - let color = 0; - if (binString.charAt(k) === '1') { - color = 255; - } - imgData.data[index] = color; - imgData.data[index + 1] = color; - imgData.data[index + 2] = color; - imgData.data[index + 3] = 255; - - index += 4; - } + // skip 'artifact' pixels due to rounding up to a byte + if (widthCounter < canvas.width) { + const color = decoded.bits.charAt(k) === '1' ? 255 : 0; + imgData.data[index] = color; + imgData.data[index + 1] = color; + imgData.data[index + 2] = color; + imgData.data[index + 3] = 255; + index += 4; } } - // Draw the image onto the canvas, then save the canvas contents - // inside the img object. This way we can reuse the img object when - // we want to scale / invert, etc. ctx.putImageData(imgData, 0, 0); - const img = new Image(); - img.onload = () => { - images.first().img = img; - }; - img.src = canvas.toDataURL('image/png'); + commitCanvasToFirstImage(canvas); } // Quick and effective way to draw single pixels onto the canvas @@ -566,50 +521,32 @@ function listToImageVertical(list, canvas) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); + const decoded = hexListToBits(list); + if (!decoded.valid) { + reportInvalidByteArray(decoded.s); + return; + } + let page = 0; let x = 0; let y = 7; // Move the list into the imageData object - for (let i = 0; i < list.length; i++) { - let binString = hexToBinary(list[i]); - if (!binString.valid) { - // eslint-disable-next-line no-alert - alert('Something went wrong converting the string. Did you forget to remove any comments from the input?'); - // eslint-disable-next-line no-console - console.error('invalid hexToBinary: ', binString.s); - return; - } - binString = binString.result; - if (binString.length === 4) { - binString += '0000'; - } - - // Check if pixel is white or black - for (let k = 0; k < binString.length; k++) { - let color = 0; - if (binString.charAt(k) === '1') { - color = 255; - } - drawPixel(ctx, x, (page * 8) + y, color); - y--; - if (y < 0) { - y = 7; - x++; - if (x >= settings.screenWidth) { - x = 0; - page++; - } + for (let k = 0; k < decoded.bits.length; k++) { + const color = decoded.bits.charAt(k) === '1' ? 255 : 0; + drawPixel(ctx, x, (page * 8) + y, color); + y--; + if (y < 0) { + y = 7; + x++; + if (x >= settings.screenWidth) { + x = 0; + page++; } } } - // Save the canvas contents inside the img object. This way we can - // reuse the img object when we want to scale / invert, etc. - const img = new Image(); - img.onload = () => { - images.first().img = img; - }; - img.src = canvas.toDataURL('image/png'); + + commitCanvasToFirstImage(canvas); } // Set width/height preset for byte array text input From d12ede5b6fbfea60171750e39f6d8e56a016610c Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:37:42 +0200 Subject: [PATCH 22/63] fix golden file --- tests/golden/output_arduino_single.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/golden/output_arduino_single.txt b/tests/golden/output_arduino_single.txt index ebcc9f0..44ca337 100644 --- a/tests/golden/output_arduino_single.txt +++ b/tests/golden/output_arduino_single.txt @@ -1,5 +1,5 @@ const unsigned char epd_bitmap [] PROGMEM = { - // 'test_pattern, 32x24px + // 'test_pattern', 32x24px 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, From 966ad6bee3ab9fba76f81df021dfc89aa5ed830d Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:38:06 +0200 Subject: [PATCH 23/63] layout tweaks --- css/style.css | 9 ++++++++- index.html | 12 +++++++----- js/script.js | 8 ++++++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/css/style.css b/css/style.css index 14f9a4f..b1523db 100644 --- a/css/style.css +++ b/css/style.css @@ -420,6 +420,10 @@ code { margin: 4px 0; } +#glyph-name-note { + display: none; +} + .upload-note { max-width: 320px; } @@ -519,7 +523,7 @@ input[type="radio"] { } .glyph-input { - width: 80px; + width: 300px; margin-left: 10px; } @@ -639,6 +643,9 @@ input[type="file"]::file-selector-button { #image-size-settings li { margin: 6px 0; + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: 8px 12px; } /* ---- Preview canvases ---- */ diff --git a/index.html b/index.html index 441cab4..0822a2b 100644 --- a/index.html +++ b/index.html @@ -95,6 +95,9 @@
    Only images file type are allowed
    Select one or more images at step 1 first.
    +
    + Glyph name is used to identify the image in the code. +
    @@ -203,7 +206,7 @@
    3

    Preview

    -
    No files selected
    +
    No files selected
    @@ -221,7 +224,6 @@ - @@ -271,13 +273,13 @@
    - +
    - +
    @@ -285,7 +287,7 @@
    - +
    diff --git a/js/script.js b/js/script.js index 89e408a..de86ee4 100644 --- a/js/script.js +++ b/js/script.js @@ -559,8 +559,10 @@ function setTextInputSize(width, height) { // Show/hide the "no images" error and report whether images are available function checkImagesAvailable() { const error = document.getElementById('no-images-error'); + const glyphNameNote = document.getElementById('glyph-name-note'); const hasImages = images.length() > 0; error.style.display = hasImages ? 'none' : 'block'; + glyphNameNote.style.display = hasImages ? 'block' : 'none'; return hasImages; } @@ -725,13 +727,14 @@ function handleImageSelection(evt) { }; const gil = document.createElement('span'); - gil.innerHTML = 'glyph'; + gil.innerHTML = 'glyph name'; gil.className = 'file-info'; const gi = document.createElement('input'); gi.type = 'text'; gi.name = 'glyph'; gi.className = 'glyph-input'; + gi.value = file.name.split('.')[0]; gi.onchange = () => { const image = images.get(img); image.glyph = gi.value; @@ -759,6 +762,7 @@ function handleImageSelection(evt) { }); } updateAllImages(); + checkImagesAvailable(); }; const rb = document.createElement('button'); @@ -852,7 +856,7 @@ function generateOutputString() { const comment = glyphComment(image); bytesUsed += code.split('\n').length * 16; // 16 bytes per line. - const varname = getIdentifier() + (image.glyph ? image.glyph.replace(/[^a-zA-Z0-9]/g, '_') : ''); + const varname = getIdentifier() + (image.glyph ? `_${image.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); varQuickArray.push(varname); code = `${comment}const ${getImageType()} ${varname} [] PROGMEM = {\n${code}};\n`; outputString += code; From 1330a07d65eb335b49da50243313dcecb79c45e4 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:53:00 +0200 Subject: [PATCH 24/63] Add image preset of 16x16 --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 0822a2b..bcbe87b 100644 --- a/index.html +++ b/index.html @@ -71,7 +71,7 @@ x px -
    Set this to the size of your image.
    Common sizes: 32x32, 64x48, 128x32, 128x64.
    +
    Set this to the size of your image.
    Common sizes: 16x16, 32x32, 64x48, 128x32, 128x64.
    From ab3d396d3f089eb6b91dfc78293d1d800b36af78 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:53:16 +0200 Subject: [PATCH 25/63] Do not allow images sizes below 1 x 1 px --- index.html | 4 ++-- js/script.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index bcbe87b..6753b39 100644 --- a/index.html +++ b/index.html @@ -68,8 +68,8 @@

    or paste byte array


    - x - px + x + px
    Set this to the size of your image.
    Common sizes: 16x16, 32x32, 64x48, 128x32, 128x64.
    diff --git a/js/script.js b/js/script.js index de86ee4..e0847f2 100644 --- a/js/script.js +++ b/js/script.js @@ -702,7 +702,7 @@ function handleImageSelection(evt) { w.type = 'number'; w.name = 'width'; w.id = 'screenWidth'; - w.min = 0; + w.min = 1; w.className = 'size-input'; w.value = img.width; settings.screenWidth = img.width; @@ -716,7 +716,7 @@ function handleImageSelection(evt) { h.type = 'number'; h.name = 'height'; h.id = 'screenHeight'; - h.min = 0; + h.min = 1; h.className = 'size-input'; h.value = img.height; settings.screenHeight = img.height; From 3f7de405bbaa165761d6a172b6dd12d46126ca25 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 09:53:25 +0200 Subject: [PATCH 26/63] Move options layout --- index.html | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 6753b39..e686b13 100644 --- a/index.html +++ b/index.html @@ -295,15 +295,6 @@
    -
    -
    -
    - - - Swap between MSB and LSB. Useful when working with the u8g2 library. -
    -
    -
    @@ -311,6 +302,12 @@
    +
    + + + MSB -> LSB. Useful when working with the u8g2 library. +
    +
    From eaaf1da3aa785b4147bf57c8781c4a3dbb1c1034 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 10:09:07 +0200 Subject: [PATCH 27/63] fix golden file --- tests/golden/output_arduino.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/golden/output_arduino.txt b/tests/golden/output_arduino.txt index ef93a3a..c1407a3 100644 --- a/tests/golden/output_arduino.txt +++ b/tests/golden/output_arduino.txt @@ -1,15 +1,15 @@ // 'test_pattern', 32x24px const unsigned char epd_bitmaptest_pattern [] PROGMEM = { - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, - 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee }; // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 112) const int epd_bitmap_allArray_LEN = 1; const unsigned char* epd_bitmap_allArray[1] = { - epd_bitmaptest_pattern + epd_bitmap_test_pattern }; From cc1fc7ad43dc0ac7e74607ba72b414f7f0b15e40 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 10:31:51 +0200 Subject: [PATCH 28/63] Fix error with rotating vertical mode images. Add more tests for catching these. --- js/script.js | 10 ++--- tests/golden/bitswap_vertical.txt | 7 +++ tests/golden/output_arduino.txt | 12 ++--- tests/golden/rotate_180_vertical.txt | 7 +++ tests/golden/rotate_270_flip_horizontal.txt | 7 +++ tests/golden/rotate_270_vertical.txt | 7 +++ tests/golden/rotate_90_flip_both.txt | 7 +++ tests/golden/rotate_90_horizontal565.txt | 49 +++++++++++++++++++++ tests/golden/rotate_90_horizontal888.txt | 33 ++++++++++++++ tests/golden/rotate_90_horizontal_alpha.txt | 7 +++ tests/golden/rotate_90_resized.txt | 10 +++++ tests/golden/rotate_90_vertical.txt | 7 +++ tests/test_conversions.py | 14 ++++++ 13 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 tests/golden/bitswap_vertical.txt create mode 100644 tests/golden/rotate_180_vertical.txt create mode 100644 tests/golden/rotate_270_flip_horizontal.txt create mode 100644 tests/golden/rotate_270_vertical.txt create mode 100644 tests/golden/rotate_90_flip_both.txt create mode 100644 tests/golden/rotate_90_horizontal565.txt create mode 100644 tests/golden/rotate_90_horizontal888.txt create mode 100644 tests/golden/rotate_90_horizontal_alpha.txt create mode 100644 tests/golden/rotate_90_resized.txt create mode 100644 tests/golden/rotate_90_vertical.txt diff --git a/js/script.js b/js/script.js index e0847f2..1c92b7d 100644 --- a/js/script.js +++ b/js/script.js @@ -93,15 +93,15 @@ const ConversionFunctions = { }, // Output the image as a string for vertically drawing displays - vertical1bit(data) { + vertical1bit(data, canvasWidth, canvasHeight) { const writer = makeByteWriter(); - for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) { - for (let x = 0; x < settings.screenWidth; x++) { + for (let p = 0; p < Math.ceil(canvasHeight / 8); p++) { + for (let x = 0; x < canvasWidth; x++) { let byteIndex = 7; let number = 0; for (let y = 7; y >= 0; y--) { - const index = ((p * 8) + y) * (settings.screenWidth * 4) + x * 4; + const index = ((p * 8) + y) * (canvasWidth * 4) + x * 4; const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; if (avg > settings.ditheringThreshold) { number += 2 ** byteIndex; @@ -816,7 +816,7 @@ function imageToString(image) { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const { data } = imageData; - return settings.conversionFunction(data, canvas.width); + return settings.conversionFunction(data, canvas.width, canvas.height); } // Get the custom arduino output variable name, if any diff --git a/tests/golden/bitswap_vertical.txt b/tests/golden/bitswap_vertical.txt new file mode 100644 index 0000000..8a81e1e --- /dev/null +++ b/tests/golden/bitswap_vertical.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0x30, 0x30, 0xc0, 0xc0, 0x3f, 0x3f, 0xcf, 0xcf, 0x30, 0x30, 0xc0, 0xc0, 0x3f, 0x3f, 0xcf, 0xcf, +0x07, 0x0b, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xfe \ No newline at end of file diff --git a/tests/golden/output_arduino.txt b/tests/golden/output_arduino.txt index c1407a3..306f313 100644 --- a/tests/golden/output_arduino.txt +++ b/tests/golden/output_arduino.txt @@ -1,10 +1,10 @@ // 'test_pattern', 32x24px -const unsigned char epd_bitmaptest_pattern [] PROGMEM = { - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, - 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, - 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, +const unsigned char epd_bitmap_test_pattern [] PROGMEM = { + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee }; diff --git a/tests/golden/rotate_180_vertical.txt b/tests/golden/rotate_180_vertical.txt new file mode 100644 index 0000000..18bc55e --- /dev/null +++ b/tests/golden/rotate_180_vertical.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 32x24px +0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0d, 0x0b, 0x07, +0xcf, 0xcf, 0x3f, 0x3f, 0xc0, 0xc0, 0x30, 0x30, 0xcf, 0xcf, 0x3f, 0x3f, 0xc0, 0xc0, 0x30, 0x30, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33 \ No newline at end of file diff --git a/tests/golden/rotate_270_flip_horizontal.txt b/tests/golden/rotate_270_flip_horizontal.txt new file mode 100644 index 0000000..c303612 --- /dev/null +++ b/tests/golden/rotate_270_flip_horizontal.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xbf, +0xff, 0xff, 0xdf, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf7, 0xf0, 0x00, 0xfb, 0xf0, 0x00, 0xfd, 0xf0, +0x00, 0xfe, 0xf0, 0x00, 0xff, 0x70, 0x00, 0xff, 0xb0, 0x00, 0xff, 0xd0, 0x00, 0xff, 0xe0, 0x00, +0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, 0xff, 0xfc, 0xcc, 0xff, 0xfc, 0xcc, 0x00, 0x03, 0x33, 0x00, +0x03, 0x33, 0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc, 0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, 0xff, 0xfc, +0xcc, 0xff, 0xfc, 0xcc, 0x00, 0x03, 0x33, 0x00, 0x03, 0x33, 0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc \ No newline at end of file diff --git a/tests/golden/rotate_270_vertical.txt b/tests/golden/rotate_270_vertical.txt new file mode 100644 index 0000000..71701c7 --- /dev/null +++ b/tests/golden/rotate_270_vertical.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x7f, 0xbf, 0xdf, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, +0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x0f, 0x0f, 0x0f, 0x0f, +0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, +0x33, 0x33, 0xcc, 0xcc, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f \ No newline at end of file diff --git a/tests/golden/rotate_90_flip_both.txt b/tests/golden/rotate_90_flip_both.txt new file mode 100644 index 0000000..784e6cc --- /dev/null +++ b/tests/golden/rotate_90_flip_both.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, +0xff, 0xfd, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xf7, 0x00, 0x0f, 0xef, 0x00, 0x0f, 0xdf, 0x00, 0x0f, +0xbf, 0x00, 0x0f, 0x7f, 0x00, 0x0e, 0xff, 0x00, 0x0d, 0xff, 0x00, 0x0b, 0xff, 0x00, 0x07, 0xff, +0xcc, 0xcf, 0xff, 0xcc, 0xcf, 0xff, 0x33, 0x3f, 0xff, 0x33, 0x3f, 0xff, 0xcc, 0xc0, 0x00, 0xcc, +0xc0, 0x00, 0x33, 0x30, 0x00, 0x33, 0x30, 0x00, 0xcc, 0xcf, 0xff, 0xcc, 0xcf, 0xff, 0x33, 0x3f, +0xff, 0x33, 0x3f, 0xff, 0xcc, 0xc0, 0x00, 0xcc, 0xc0, 0x00, 0x33, 0x30, 0x00, 0x33, 0x30, 0x00 \ No newline at end of file diff --git a/tests/golden/rotate_90_horizontal565.txt b/tests/golden/rotate_90_horizontal565.txt new file mode 100644 index 0000000..30012c7 --- /dev/null +++ b/tests/golden/rotate_90_horizontal565.txt @@ -0,0 +1,49 @@ +// 'test_pattern', 24x32px +0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xffff, 0xffff, 0x0000, 0x0000, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, +0xf800, 0xf800, 0xf800, 0xf800, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, 0xf800, +0xf800, 0xf800, 0xf800, 0xf800, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0xffff, 0xffff, 0x0000, 0x0000, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, +0x07e0, 0x07e0, 0x07e0, 0x07e0, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x07e0, +0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0x0000, 0x0000, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, +0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, +0x001f, 0x001f, 0x001f, 0x001f, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0x0000, 0xffff, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, 0x1082, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x2104, 0x2104, 0x2104, 0x2104, +0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0x0000, 0xffff, 0xffff, 0xffff, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x4228, 0x4228, 0x4228, 0x4228, +0x4228, 0x4228, 0x4228, 0x4228, 0x4228, 0x4228, 0x4228, 0x4228, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, 0x52aa, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x632c, 0x632c, 0x632c, 0x632c, +0x632c, 0x632c, 0x632c, 0x632c, 0x632c, 0x632c, 0x632c, 0x632c, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, 0x73ae, +0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x8c51, 0x8c51, 0x8c51, 0x8c51, +0x8c51, 0x8c51, 0x8c51, 0x8c51, 0x8c51, 0x8c51, 0x8c51, 0x8c51, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, +0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xad55, 0xad55, 0xad55, 0xad55, +0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce79, 0xce79, 0xce79, 0xce79, +0xce79, 0xce79, 0xce79, 0xce79, 0xce79, 0xce79, 0xce79, 0xce79, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xef7d, 0xef7d, 0xef7d, +0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, +0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff \ No newline at end of file diff --git a/tests/golden/rotate_90_horizontal888.txt b/tests/golden/rotate_90_horizontal888.txt new file mode 100644 index 0000000..4c33e5f --- /dev/null +++ b/tests/golden/rotate_90_horizontal888.txt @@ -0,0 +1,33 @@ +// 'test_pattern', 24x32px +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ff0000, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, 0x00111111, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, 0x00222222, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, 0x00333333, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, 0x00444444, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, 0x00666666, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, 0x00777777, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, 0x00888888, +0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, 0x00999999, +0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, 0x00aaaaaa, +0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, 0x00bbbbbb, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, 0x00cccccc, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, 0x00dddddd, +0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, 0x00eeeeee, +0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff \ No newline at end of file diff --git a/tests/golden/rotate_90_horizontal_alpha.txt b/tests/golden/rotate_90_horizontal_alpha.txt new file mode 100644 index 0000000..b169223 --- /dev/null +++ b/tests/golden/rotate_90_horizontal_alpha.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/rotate_90_resized.txt b/tests/golden/rotate_90_resized.txt new file mode 100644 index 0000000..51cb5c0 --- /dev/null +++ b/tests/golden/rotate_90_resized.txt @@ -0,0 +1,10 @@ +// 'test_pattern', 24x48px +0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc, 0x00, 0x03, 0x33, 0x00, 0x03, 0x33, 0xff, 0xfc, 0xcc, 0xff, +0xfc, 0xcc, 0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, 0x00, 0x0c, 0xcc, 0x00, 0x0c, 0xcc, 0x00, 0x03, +0x33, 0x00, 0x03, 0x33, 0xff, 0xfc, 0xcc, 0xff, 0xfc, 0xcc, 0xff, 0xf3, 0x33, 0xff, 0xf3, 0x33, +0xff, 0xe0, 0x00, 0xff, 0xd0, 0x00, 0xff, 0xb0, 0x00, 0xff, 0x70, 0x00, 0xfe, 0xf0, 0x00, 0xfd, +0xf0, 0x00, 0xfb, 0xf0, 0x00, 0xf7, 0xf0, 0x00, 0xef, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xbf, 0xff, +0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/golden/rotate_90_vertical.txt b/tests/golden/rotate_90_vertical.txt new file mode 100644 index 0000000..e3f2e88 --- /dev/null +++ b/tests/golden/rotate_90_vertical.txt @@ -0,0 +1,7 @@ +// 'test_pattern', 24x32px +0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x33, 0xcc, 0xcc, +0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, +0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, +0xff, 0xff, 0xff, 0xff, 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xfb, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ No newline at end of file diff --git a/tests/test_conversions.py b/tests/test_conversions.py index bf9321b..6bfb06e 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -71,6 +71,20 @@ SCENARIOS = [ ("rotate_90", {"rotation": 90}, None), ("rotate_180", {"rotation": 180}, None), ("rotate_270", {"rotation": 270}, None), + ("rotate_90_vertical", {"drawMode": "vertical1bit", "rotation": 90}, None), + ("rotate_270_vertical", {"drawMode": "vertical1bit", "rotation": 270}, None), + ("rotate_180_vertical", {"drawMode": "vertical1bit", "rotation": 180}, None), + ("rotate_90_horizontal565", {"drawMode": "horizontal565", "rotation": 90}, None), + ("rotate_90_horizontal888", {"drawMode": "horizontal888", "rotation": 90}, None), + ("rotate_90_horizontal_alpha", {"drawMode": "horizontalAlpha", "rotation": 90}, None), + ( + "rotate_90_flip_both", + {"rotation": 90, "flipHorizontally": True, "flipVertically": True}, + None, + ), + ("rotate_270_flip_horizontal", {"rotation": 270, "flipHorizontally": True}, None), + ("rotate_90_resized", {"rotation": 90}, (48, 24)), + ("bitswap_vertical", {"drawMode": "vertical1bit", "bitswap": True}, None), ("dithering_bayer", {"ditheringMode": 1}, None), ("dithering_floyd_steinberg", {"ditheringMode": 2}, None), ("dithering_atkinson", {"ditheringMode": 3}, None), From c42d39cb68bcdb48588abea686e3711d3ef89c13 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 10:38:12 +0200 Subject: [PATCH 29/63] Fix issue with bin file not using the proper byte values. Also added tests to catch these in the future. --- js/script.js | 49 +++++++++++++++++++++++++++++++-------- tests/test_conversions.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/js/script.js b/js/script.js index 1c92b7d..bf1249e 100644 --- a/js/script.js +++ b/js/script.js @@ -155,6 +155,18 @@ const ConversionFunctions = { }; settings.conversionFunction = ConversionFunctions.horizontal1bit; +// Bytes per output value for each conversion function, matching the +// hexWidth passed to writer.push() in that function (hexWidth / 2). +// Needed by downloadBinFile() to reconstruct the true byte stream instead +// of truncating multi-byte values (565/888) down to a single byte. +const CONVERSION_BYTES_PER_VALUE = new Map([ + [ConversionFunctions.horizontal1bit, 1], + [ConversionFunctions.vertical1bit, 1], + [ConversionFunctions.horizontal565, 2], + [ConversionFunctions.horizontal888, 4], + [ConversionFunctions.horizontalAlpha, 1], +]); + // An images collection with helper methods function Images() { const collection = []; @@ -983,20 +995,37 @@ function copyOutput() { navigator.clipboard.writeText(output.value); } +// Rebuilds the true byte stream for the current images/conversion mode by +// re-splitting each formatted output value (which may be 1, 2 or 4 bytes +// wide, depending on drawMode) back into its individual bytes, most- +// significant first. Exposed standalone so it can be exercised directly in +// tests without going through a real browser file download. +function computeBinData() { + const bytesPerValue = CONVERSION_BYTES_PER_VALUE.get(settings.conversionFunction) || 1; + const raw = []; + images.each((image) => { + const values = imageToString(image) + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + .map((token) => parseInt(token, 16)); + values.forEach((value) => { + // Split back into individual bytes, most-significant first, matching + // the left-to-right digit order produced by toString(16).padStart(). + for (let shift = (bytesPerValue - 1) * 8; shift >= 0; shift -= 8) { + // eslint-disable-next-line no-bitwise + raw.push((value >> shift) & 0xff); + } + }); + }); + return new Uint8Array(raw); +} + // eslint-disable-next-line no-unused-vars function downloadBinFile() { if (!checkImagesAvailable()) return; - let raw = []; - images.each((image) => { - const data = imageToString(image) - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - .map((byte) => parseInt(byte, 16)); - raw = raw.concat(data); - }); - const data = new Uint8Array(raw); + const data = computeBinData(); const a = document.createElement('a'); a.style = 'display: none'; document.body.appendChild(a); diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 6bfb06e..59d856d 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -129,6 +129,45 @@ SCENARIOS = [ ), ] +# Bytes per output value per drawMode, mirroring CONVERSION_BYTES_PER_VALUE in +# js/script.js. Used to verify downloadBinFile()'s byte stream (via the +# computeBinData() helper) matches the hex values shown in the text output, +# instead of the multi-byte values (565/888) getting truncated to one byte. +BYTES_PER_VALUE = { + "horizontal1bit": 1, + "vertical1bit": 1, + "horizontal565": 2, + "horizontal888": 4, + "horizontalAlpha": 1, +} + +# Scenario names to also verify against computeBinData(); restricted to ones +# using the default plain/comma-separated output (outputFormat and separator +# untouched), since that's the format downloadBinFile's parser expects. +BIN_CHECK_NAMES = { + "default", + "draw_mode_vertical1bit", + "draw_mode_horizontal565", + "draw_mode_horizontal888", + "draw_mode_horizontal_alpha", + "rotate_90_horizontal565", + "rotate_90_horizontal888", + "rotate_90_horizontal_alpha", +} + + +def expected_bin_bytes(output, draw_mode): + """Reconstruct the byte stream a correct downloadBinFile() should produce + from the plain-format hex text output, expanding each value back to its + full byte width (MSB first) instead of assuming one byte per value.""" + bytes_per_value = BYTES_PER_VALUE[draw_mode] + values = (int(tok, 16) for tok in re.findall(r"0x[0-9a-fA-F]+", output)) + result = [] + for value in values: + for shift in range((bytes_per_value - 1) * 8, -1, -8): + result.append((value >> shift) & 0xFF) + return result + def reset_canvas_size(page, width, height): """Set canvas size through the real width/height text inputs.""" @@ -272,6 +311,15 @@ def main(): print(f"ok {name}") + if name in BIN_CHECK_NAMES: + expected_bytes = expected_bin_bytes(output, settings["drawMode"]) + actual_bytes = page.evaluate("Array.from(computeBinData())") + if actual_bytes != expected_bytes: + failures.append(f"{name}: bin data mismatch") + print(f"FAIL {name} (bin)") + else: + print(f"ok {name} (bin)") + if roundtrip_eligible(settings): width, height = resize or (NATIVE_WIDTH, NATIVE_HEIGHT) roundtripped = run_roundtrip(page, output, settings["drawMode"], width, height) From 8fd88558346cbea8810e3ddc159e82332af57114 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 10:55:04 +0200 Subject: [PATCH 30/63] Adds toggle for using ESP32 formatting instead --- index.html | 7 +++ js/script.js | 32 ++++++++--- tests/golden/output_adafruit_gfx_esp32.txt | 19 +++++++ tests/golden/output_arduino_esp32.txt | 15 ++++++ tests/golden/output_arduino_esp32_565.txt | 57 ++++++++++++++++++++ tests/golden/output_arduino_single_esp32.txt | 9 ++++ tests/test_conversions.py | 17 ++++++ 7 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 tests/golden/output_adafruit_gfx_esp32.txt create mode 100644 tests/golden/output_arduino_esp32.txt create mode 100644 tests/golden/output_arduino_esp32_565.txt create mode 100644 tests/golden/output_arduino_single_esp32.txt diff --git a/index.html b/index.html index e686b13..7e28015 100644 --- a/index.html +++ b/index.html @@ -290,6 +290,13 @@
    +
    +
    +
    + + Uses uint8_t instead of unsigned char and drops PROGMEM, which is a no-op on ESP32. +
    +
    diff --git a/js/script.js b/js/script.js index bf1249e..c35e454 100644 --- a/js/script.js +++ b/js/script.js @@ -22,6 +22,7 @@ const settings = { outputComments: true, invertColors: false, rotation: 0, + esp32Format: false, }; function bitswap(b) { @@ -432,6 +433,13 @@ function hexToBinary(s) { return { valid: true, result: ret }; } +// The plain byte type, independent of drawMode (used by output formats, +// like adafruit_gfx, that are always byte-packed regardless of the current +// conversion function). +function getByteType() { + return settings.esp32Format ? 'uint8_t' : 'unsigned char'; +} + // get the type (in arduino code) of the output image // this is a bit of a hack, it's better to make this a property of the conversion function (should probably turn it into objects) function getImageType() { @@ -440,7 +448,14 @@ function getImageType() { } if (settings.conversionFunction === ConversionFunctions.horizontal888) { return 'unsigned long'; } - return 'unsigned char'; + return getByteType(); +} + +// PROGMEM is an AVR-ism: it's a no-op on ESP32 (flash is memory-mapped +// there), so the ESP32 output toggle drops it instead of emitting a keyword +// that does nothing. +function progmemKeyword() { + return settings.esp32Format ? '' : ' PROGMEM'; } // Use the horizontally oriented list to draw the image @@ -870,7 +885,7 @@ function generateOutputString() { const varname = getIdentifier() + (image.glyph ? `_${image.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); varQuickArray.push(varname); - code = `${comment}const ${getImageType()} ${varname} [] PROGMEM = {\n${code}};\n`; + code = `${comment}const ${getImageType()} ${varname} []${progmemKeyword()} = {\n${code}};\n`; outputString += code; }); @@ -879,7 +894,8 @@ function generateOutputString() { outputString += '\n'; } if (settings.outputComments) { - outputString += `// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = ${bytesUsed})\n`; + const storageNote = settings.esp32Format ? '' : ' in PROGMEM'; + outputString += `// Array of all bitmaps for convenience. (Total bytes used to store images${storageNote} = ${bytesUsed})\n`; } outputString += `const int ${getIdentifier()}_allArray_LEN = ${varQuickArray.length};\n`; outputString += `const ${getImageType()}* ${getIdentifier()}_allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; @@ -899,7 +915,7 @@ function generateOutputString() { outputString = `const ${getImageType()} ${ getIdentifier() - } [] PROGMEM = {` + } []${progmemKeyword()} = {` + `\n${outputString}\n};`; break; } @@ -918,14 +934,14 @@ function generateOutputString() { }); outputString = outputString.replace(/,\s*$/, ''); - outputString = `const unsigned char ${ + outputString = `const ${getByteType()} ${ getIdentifier() }Bitmap` - + ' [] PROGMEM = {' + + ` []${progmemKeyword()} = {` + `\n${outputString}\n};\n\n` + `const GFXbitmapGlyph ${ getIdentifier() - }Glyphs [] PROGMEM = {\n`; + }Glyphs []${progmemKeyword()} = {\n`; let firstAschiiChar = document.getElementById('first-ascii-char').value; const xAdvance = parseInt(document.getElementById('x-advance').value); @@ -955,7 +971,7 @@ function generateOutputString() { // GFXbitmapFont outputString += `\nconst GFXbitmapFont ${ getIdentifier() - }Font PROGMEM = {\n` + }Font${progmemKeyword()} = {\n` + `\t(uint8_t *)${ getIdentifier()}Bitmap,\n` + `\t(GFXbitmapGlyph *)${ diff --git a/tests/golden/output_adafruit_gfx_esp32.txt b/tests/golden/output_adafruit_gfx_esp32.txt new file mode 100644 index 0000000..dc7d45a --- /dev/null +++ b/tests/golden/output_adafruit_gfx_esp32.txt @@ -0,0 +1,19 @@ +const uint8_t epd_bitmapBitmap [] = { + // 'test_pattern', 32x24px + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; + +const GFXbitmapGlyph epd_bitmapGlyphs [] = { + { 0, 32, 24, 0, '0' }// 'test_pattern' +}; + +const GFXbitmapFont epd_bitmapFont = { + (uint8_t *)epd_bitmapBitmap, + (GFXbitmapGlyph *)epd_bitmapGlyphs, + 1 +}; diff --git a/tests/golden/output_arduino_esp32.txt b/tests/golden/output_arduino_esp32.txt new file mode 100644 index 0000000..450b6d2 --- /dev/null +++ b/tests/golden/output_arduino_esp32.txt @@ -0,0 +1,15 @@ +// 'test_pattern', 32x24px +const uint8_t epd_bitmap_test_pattern [] = { + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images = 112) +const int epd_bitmap_allArray_LEN = 1; +const uint8_t* epd_bitmap_allArray[1] = { + epd_bitmap_test_pattern +}; diff --git a/tests/golden/output_arduino_esp32_565.txt b/tests/golden/output_arduino_esp32_565.txt new file mode 100644 index 0000000..e41ec4f --- /dev/null +++ b/tests/golden/output_arduino_esp32_565.txt @@ -0,0 +1,57 @@ +// 'test_pattern', 32x24px +const uint16_t epd_bitmap_test_pattern [] = { + 0xf800, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, + 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0x001f +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images = 784) +const int epd_bitmap_allArray_LEN = 1; +const uint16_t* epd_bitmap_allArray[1] = { + epd_bitmap_test_pattern +}; diff --git a/tests/golden/output_arduino_single_esp32.txt b/tests/golden/output_arduino_single_esp32.txt new file mode 100644 index 0000000..400c530 --- /dev/null +++ b/tests/golden/output_arduino_single_esp32.txt @@ -0,0 +1,9 @@ +const uint8_t epd_bitmap [] = { + // 'test_pattern', 32x24px + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, + 0x0f, 0x0f, 0x7f, 0xff, 0x0f, 0x0f, 0xbf, 0xff, 0x0f, 0x0f, 0xdf, 0xff, 0x0f, 0x0f, 0xef, 0xff, + 0x0f, 0x0f, 0xf7, 0xff, 0x0f, 0x0f, 0xfb, 0xff, 0x0f, 0x0f, 0xfd, 0xff, 0x0f, 0x0f, 0xfe, 0xff, + 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee +}; \ No newline at end of file diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 59d856d..2a4cffe 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -45,6 +45,7 @@ BASE_SETTINGS = { "bitswap": False, "prefix": "0x", "separator": ", ", + "esp32Format": False, } OUTPUT_FORMAT_IDS = { @@ -64,6 +65,18 @@ SCENARIOS = [ ("output_arduino", {"outputFormat": "arduino"}, None), ("output_arduino_single", {"outputFormat": "arduino_single"}, None), ("output_adafruit_gfx", {"outputFormat": "adafruit_gfx"}, None), + ("output_arduino_esp32", {"outputFormat": "arduino", "esp32Format": True}, None), + ( + "output_arduino_single_esp32", + {"outputFormat": "arduino_single", "esp32Format": True}, + None, + ), + ("output_adafruit_gfx_esp32", {"outputFormat": "adafruit_gfx", "esp32Format": True}, None), + ( + "output_arduino_esp32_565", + {"outputFormat": "arduino", "drawMode": "horizontal565", "esp32Format": True}, + None, + ), ("invert_colors", {"invertColors": True}, None), ("flip_horizontal", {"flipHorizontally": True}, None), ("flip_vertical", {"flipVertically": True}, None), @@ -194,6 +207,10 @@ def apply_settings(page, settings): set_checkbox(page, "#bitswap", settings["bitswap"]) page.fill("#prefix", settings["prefix"]) page.fill("#separator", settings["separator"]) + # esp32Format checkbox only exists in the DOM (visible) for non-plain + # output formats; plain output has no type/PROGMEM declarations to affect. + if settings["outputFormat"] != "plain": + set_checkbox(page, "#esp32Format", settings["esp32Format"]) def set_checkbox(page, selector, checked): From 84536618254effc0ad2fd913282879300ef29750 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 11:14:46 +0200 Subject: [PATCH 31/63] Remove identifier placeholder, fixing #62 --- index.html | 2 +- js/script.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 7e28015..cc26f51 100644 --- a/index.html +++ b/index.html @@ -287,7 +287,7 @@
    - +
    diff --git a/js/script.js b/js/script.js index c35e454..1cc2c67 100644 --- a/js/script.js +++ b/js/script.js @@ -198,8 +198,6 @@ function Images() { const images = new Images(); // Filetypes accepted by the file picker // const fileTypes = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg']; -// Variable name, when "arduino code" is required -const identifier = 'myBitmap'; // Invert the colors of the canvas function invert(canvas, ctx) { @@ -849,7 +847,7 @@ function imageToString(image) { // Get the custom arduino output variable name, if any function getIdentifier() { const vn = document.getElementById('identifier'); - return (vn && vn.value.length) ? vn.value : identifier; + return (vn && vn.value.length) ? vn.value : ''; } // Builds the "// 'glyph', WxHpx" comment line placed before each image's @@ -883,7 +881,7 @@ function generateOutputString() { const comment = glyphComment(image); bytesUsed += code.split('\n').length * 16; // 16 bytes per line. - const varname = getIdentifier() + (image.glyph ? `_${image.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); + const varname = getIdentifier() + (image.glyph ? `${image.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); varQuickArray.push(varname); code = `${comment}const ${getImageType()} ${varname} []${progmemKeyword()} = {\n${code}};\n`; outputString += code; From a30874d8f1ea57bb5783c106af85c478dfd66786 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 11:16:24 +0200 Subject: [PATCH 32/63] Fix issue with size of first image getting applied to all images. Fixes issue #61 --- js/script.js | 46 ++++++++++++++++++++++----------------- tests/test_conversions.py | 34 +++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/js/script.js b/js/script.js index 1cc2c67..d5de5b5 100644 --- a/js/script.js +++ b/js/script.js @@ -171,7 +171,11 @@ const CONVERSION_BYTES_PER_VALUE = new Map([ // An images collection with helper methods function Images() { const collection = []; - this.push = (img, canvas, glyph) => { collection.push({ img, canvas, glyph }); }; + this.push = (img, canvas, glyph, width, height) => { + collection.push({ + img, canvas, glyph, width, height, + }); + }; this.remove = (image) => { const i = collection.indexOf(image); if (i !== -1) collection.splice(i, 1); @@ -217,9 +221,11 @@ function placeImage(_image) { const { canvas } = _image; const ctx = canvas.getContext('2d'); - // reset canvas size - canvas.width = Number.isFinite(settings.screenWidth) && settings.screenWidth > 0 ? settings.screenWidth : 1; - canvas.height = Number.isFinite(settings.screenHeight) && settings.screenHeight > 0 ? settings.screenHeight : 1; + // reset canvas size (falls back to the canvas' current size for images, + // like a re-imported byte array, that aren't tracked in the image list + // with their own width/height) + canvas.width = Number.isFinite(_image.width) && _image.width > 0 ? _image.width : canvas.width; + canvas.height = Number.isFinite(_image.height) && _image.height > 0 ? _image.height : canvas.height; // eslint-disable-next-line no-param-reassign _image.ctx = ctx; ctx.save(); @@ -358,8 +364,8 @@ function placeImage(_image) { ctx.fillRect(0, 0, canvas.width, canvas.height); if (settings.rotation === 90) { - canvas.width = settings.screenHeight; - canvas.height = settings.screenWidth; + canvas.width = clone.height; + canvas.height = clone.width; ctx.setTransform(1, 0, 0, 1, canvas.width, 0); ctx.rotate(Math.PI / 2); ctx.drawImage(clone, 0, 0); @@ -368,8 +374,8 @@ function placeImage(_image) { ctx.rotate(Math.PI); ctx.drawImage(clone, 0, 0); } else if (settings.rotation === 270) { - canvas.width = settings.screenHeight; - canvas.height = settings.screenWidth; + canvas.width = clone.height; + canvas.height = clone.width; ctx.setTransform(1, 0, 0, 1, 0, canvas.height); ctx.rotate(Math.PI * 1.5); ctx.drawImage(clone, 0, 0); @@ -399,6 +405,7 @@ function updateAllImages() { } // Easy way to update settings controlled by a textfield +// eslint-disable-next-line no-unused-vars function updateInteger(fieldName) { settings[fieldName] = parseInt(document.getElementById(fieldName).value); updateAllImages(); @@ -730,11 +737,10 @@ function handleImageSelection(evt) { w.min = 1; w.className = 'size-input'; w.value = img.width; - settings.screenWidth = img.width; w.oninput = () => { - canvas.width = w.value; - updateAllImages(); - updateInteger('screenWidth'); + const image = images.get(img); + image.width = parseInt(w.value, 10); + placeImage(image); }; const h = document.createElement('input'); @@ -744,11 +750,10 @@ function handleImageSelection(evt) { h.min = 1; h.className = 'size-input'; h.value = img.height; - settings.screenHeight = img.height; h.oninput = () => { - canvas.height = h.value; - updateAllImages(); - updateInteger('screenHeight'); + const image = images.get(img); + image.height = parseInt(h.value, 10); + placeImage(image); }; const gil = document.createElement('span'); @@ -759,7 +764,8 @@ function handleImageSelection(evt) { gi.type = 'text'; gi.name = 'glyph'; gi.className = 'glyph-input'; - gi.value = file.name.split('.')[0]; + const [fileName] = file.name.split('.'); + gi.value = fileName; gi.onchange = () => { const image = images.get(img); image.glyph = gi.value; @@ -820,7 +826,7 @@ function handleImageSelection(evt) { canvas.height = img.height; canvasContainer.appendChild(canvas); - images.push(img, canvas, file.name.split('.')[0]); + images.push(img, canvas, file.name.split('.')[0], img.width, img.height); if (images.length() > 1) { document.getElementById('all-same-size').style.display = 'block'; } @@ -895,8 +901,8 @@ function generateOutputString() { const storageNote = settings.esp32Format ? '' : ' in PROGMEM'; outputString += `// Array of all bitmaps for convenience. (Total bytes used to store images${storageNote} = ${bytesUsed})\n`; } - outputString += `const int ${getIdentifier()}_allArray_LEN = ${varQuickArray.length};\n`; - outputString += `const ${getImageType()}* ${getIdentifier()}_allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; + outputString += `const int ${getIdentifier()}allArray_LEN = ${varQuickArray.length};\n`; + outputString += `const ${getImageType()}* ${getIdentifier()}allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; break; } diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 2a4cffe..a26d268 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -272,6 +272,32 @@ def run_roundtrip(page, output, draw_mode, width, height): return page.input_value("#code-output") +def run_multi_image_independent_sizes(page): + """Each image in a multi-image upload keeps its own canvas size; setting + one image's width/height must not resize any other image's canvas. + Regression test for a bug where every canvas was forced to the size of + the first uploaded image.""" + page.goto(INDEX_HTML.as_uri()) + page.set_input_files("#file-input", [str(TEST_IMAGE), str(TEST_IMAGE)]) + page.wait_for_function("document.querySelectorAll('#image-size-settings li').length === 2") + + page.fill("#image-size-settings li:nth-child(1) input[name='width']", "50") + page.fill("#image-size-settings li:nth-child(1) input[name='height']", "60") + page.fill("#image-size-settings li:nth-child(2) input[name='width']", "20") + page.fill("#image-size-settings li:nth-child(2) input[name='height']", "10") + + sizes = page.evaluate( + "[images.getByIndex(0).canvas.width, images.getByIndex(0).canvas.height," + " images.getByIndex(1).canvas.width, images.getByIndex(1).canvas.height]" + ) + expected = [50, 60, 20, 10] + if sizes != expected: + print("FAIL multi_image_independent_sizes") + return [f"multi_image_independent_sizes: expected canvas sizes {expected}, got {sizes}"] + print("ok multi_image_independent_sizes") + return [] + + def main(): parser = argparse.ArgumentParser() parser.add_argument( @@ -364,19 +390,23 @@ def main(): page.set_input_files("#file-input", str(TEST_IMAGE)) page.wait_for_selector("#image-size-settings li") + if not args.update: + failures.extend(run_multi_image_independent_sizes(page)) + browser.close() if args.update: print(f"\nWrote {len(SCENARIOS)} golden files to {GOLDEN_DIR}") return + total = len(SCENARIOS) + 1 if failures: - print(f"\n{len(failures)} of {len(SCENARIOS)} scenarios failed:") + print(f"\n{len(failures)} of {total} scenarios failed:") for f in failures: print(f" - {f}") sys.exit(1) - print(f"\nAll {len(SCENARIOS)} scenarios match golden output.") + print(f"\nAll {total} scenarios match golden output.") if __name__ == "__main__": From 606d128403a98ed8a0a656ae243f84990fff8a7f Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 11:28:19 +0200 Subject: [PATCH 33/63] Add option to set the number of bytes per output line. Closes issue #38 --- index.html | 5 +++++ js/script.js | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index cc26f51..c72b2fc 100644 --- a/index.html +++ b/index.html @@ -323,6 +323,11 @@
    +
    + + + Set to 0 to wrap using the image's width instead, e.g. an 8px wide image adds a newline every 8th byte. +
    diff --git a/js/script.js b/js/script.js index d5de5b5..3831f4e 100644 --- a/js/script.js +++ b/js/script.js @@ -23,6 +23,7 @@ const settings = { invertColors: false, rotation: 0, esp32Format: false, + bytesPerRow: 16, }; function bitswap(b) { @@ -37,11 +38,17 @@ function bitswap(b) { return b; } +// settings.bytesPerRow of 0 means "wrap using the image's width instead", +// so a 8px-wide image gets a newline every 8 bytes. +function resolveWrapWidth(canvasWidth) { + return settings.bytesPerRow > 0 ? settings.bytesPerRow : canvasWidth; +} + // Accumulates formatted bytes (prefix/value/separator, wrapped every // wrapWidth bytes) so our ConversionFunctions can use this formatting. -// wrapWidth defaults to 16 bytes per line; horizontal888 wraps -// once per image row (canvasWidth) instead. -function makeByteWriter(wrapWidth = 16) { +// horizontal888 always wraps once per image row (canvasWidth) regardless +// of settings.bytesPerRow. +function makeByteWriter(wrapWidth) { let out = ''; let count = 0; return { @@ -87,7 +94,7 @@ function packBitsHorizontal(data, canvasWidth, writer, sampleAt) { const ConversionFunctions = { // Output the image as a string for horizontally drawing displays horizontal1bit(data, canvasWidth) { - const writer = makeByteWriter(); + const writer = makeByteWriter(resolveWrapWidth(canvasWidth)); const avgRgb = (d, i) => (d[i] + d[i + 1] + d[i + 2]) / 3; packBitsHorizontal(data, canvasWidth, writer, avgRgb); return writer.result(); @@ -95,7 +102,7 @@ const ConversionFunctions = { // Output the image as a string for vertically drawing displays vertical1bit(data, canvasWidth, canvasHeight) { - const writer = makeByteWriter(); + const writer = makeByteWriter(resolveWrapWidth(canvasWidth)); for (let p = 0; p < Math.ceil(canvasHeight / 8); p++) { for (let x = 0; x < canvasWidth; x++) { let byteIndex = 7; @@ -118,7 +125,7 @@ const ConversionFunctions = { // Output the image as a string for 565 displays (horizontally) // eslint-disable-next-line no-unused-vars horizontal565(data, canvasWidth) { - const writer = makeByteWriter(); + const writer = makeByteWriter(resolveWrapWidth(canvasWidth)); // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { const r = data[index]; @@ -149,7 +156,7 @@ const ConversionFunctions = { }, // Output the alpha mask as a string for horizontally drawing displays horizontalAlpha(data, canvasWidth) { - const writer = makeByteWriter(); + const writer = makeByteWriter(resolveWrapWidth(canvasWidth)); packBitsHorizontal(data, canvasWidth, writer, (d, i) => d[i + 3]); return writer.result(); }, @@ -885,7 +892,7 @@ function generateOutputString() { code = `\t${code.split('\n').join('\n\t')}\n`; // const variableCount = images.length() > 1 ? count++ : ''; const comment = glyphComment(image); - bytesUsed += code.split('\n').length * 16; // 16 bytes per line. + bytesUsed += code.split('\n').length * resolveWrapWidth(image.canvas.width); const varname = getIdentifier() + (image.glyph ? `${image.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); varQuickArray.push(varname); From eb8a9c2636042cdba519ac04b8ed90f02ff45155 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 12:22:47 +0200 Subject: [PATCH 34/63] Add option to show an asci version of the image behind the bytearray --- index.html | 5 ++ js/script.js | 61 ++++++++++++++++--- tests/golden/ascii_preview.txt | 25 ++++++++ tests/golden/ascii_preview_arduino.txt | 33 ++++++++++ .../ascii_preview_bytes_per_row_ignored.txt | 25 ++++++++ tests/golden/ascii_preview_horizontal565.txt | 25 ++++++++ tests/golden/ascii_preview_horizontal888.txt | 25 ++++++++ tests/golden/output_adafruit_gfx.txt | 10 +-- tests/golden/output_adafruit_gfx_esp32.txt | 10 +-- tests/golden/output_arduino_single.txt | 2 +- tests/golden/output_arduino_single_esp32.txt | 2 +- tests/test_conversions.py | 33 +++++++++- 12 files changed, 236 insertions(+), 20 deletions(-) create mode 100644 tests/golden/ascii_preview.txt create mode 100644 tests/golden/ascii_preview_arduino.txt create mode 100644 tests/golden/ascii_preview_bytes_per_row_ignored.txt create mode 100644 tests/golden/ascii_preview_horizontal565.txt create mode 100644 tests/golden/ascii_preview_horizontal888.txt diff --git a/index.html b/index.html index c72b2fc..ba10b37 100644 --- a/index.html +++ b/index.html @@ -328,6 +328,11 @@ Set to 0 to wrap using the image's width instead, e.g. an 8px wide image adds a newline every 8th byte. +
    + + + Adds a dot/hash ascii-art comment after each row of bytes. Forces one image row per output line, regardless of "Bytes per row". +
    diff --git a/js/script.js b/js/script.js index 3831f4e..de90217 100644 --- a/js/script.js +++ b/js/script.js @@ -24,6 +24,7 @@ const settings = { rotation: 0, esp32Format: false, bytesPerRow: 16, + showAsciiPreview: false, }; function bitswap(b) { @@ -47,7 +48,8 @@ function resolveWrapWidth(canvasWidth) { // Accumulates formatted bytes (prefix/value/separator, wrapped every // wrapWidth bytes) so our ConversionFunctions can use this formatting. // horizontal888 always wraps once per image row (canvasWidth) regardless -// of settings.bytesPerRow. +// of settings.bytesPerRow. When settings.showAsciiPreview is on, wrapWidth +// is ignored in favor of explicit endRow() calls (see makeAsciiRowTracker). function makeByteWriter(wrapWidth) { let out = ''; let count = 0; @@ -56,23 +58,52 @@ function makeByteWriter(wrapWidth) { const byteSet = bitswap(value).toString(16).padStart(hexWidth, '0'); out += `${settings.prefix}${byteSet}${settings.separator}`; count++; - if (count >= wrapWidth) { + if (!settings.showAsciiPreview && count >= wrapWidth) { out += '\n'; count = 0; } }, + // Ends the current line early with a dot/hash ascii-art comment for the + // row of pixels just written. Only called when settings.showAsciiPreview + // is on, so it always wins over wrapWidth-based wrapping. + endRow(asciiRow) { + out += `// ${asciiRow.split('').join(' ')}\n`; + count = 0; + }, result() { return out; }, }; } +// Builds a per-image-row ascii-art tracker (. for off, # for on) used +// when settings.showAsciiPreview is on. Call once per pixel with the same +// `index` used to read that pixel's channel data; flushes to the writer as +// a comment at the end of each image row, mirroring the row-boundary math +// packBitsHorizontal uses to flush partial bytes. +function makeAsciiRowTracker(canvasWidth, dataLength) { + let row = ''; + return (index, isOn, writer) => { + if (!settings.showAsciiPreview) return; + row += isOn ? '#' : '.'; + const pixelIndex = index / 4; + const isRowEnd = pixelIndex !== 0 && ((pixelIndex + 1) % canvasWidth) === 0; + const isImageEnd = index === dataLength - 4; + if (isRowEnd || isImageEnd) { + writer.endRow(row); + row = ''; + } + }; +} + // Shared by horizontal1bit/horizontalAlpha: pack 8 samples (one per pixel, // MSB first) into a byte, resetting early at the end of a row or the image // so a partial final byte is still zero-padded instead of dropped. function packBitsHorizontal(data, canvasWidth, writer, sampleAt) { let byteIndex = 7; let number = 0; + const trackAscii = makeAsciiRowTracker(canvasWidth, data.length); for (let index = 0; index < data.length; index += 4) { - if (sampleAt(data, index) > settings.ditheringThreshold) { + const isOn = sampleAt(data, index) > settings.ditheringThreshold; + if (isOn) { number += 2 ** byteIndex; } byteIndex--; @@ -88,6 +119,8 @@ function packBitsHorizontal(data, canvasWidth, writer, sampleAt) { number = 0; byteIndex = 7; } + + trackAscii(index, isOn, writer); } } @@ -126,6 +159,7 @@ const ConversionFunctions = { // eslint-disable-next-line no-unused-vars horizontal565(data, canvasWidth) { const writer = makeByteWriter(resolveWrapWidth(canvasWidth)); + const trackAscii = makeAsciiRowTracker(canvasWidth, data.length); // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { const r = data[index]; @@ -135,6 +169,7 @@ const ConversionFunctions = { // eslint-disable-next-line no-bitwise const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3); writer.push(rgb, 4); + trackAscii(index, (r + g + b) / 3 > settings.ditheringThreshold, writer); } return writer.result(); }, @@ -142,6 +177,7 @@ const ConversionFunctions = { // image row per output line horizontal888(data, canvasWidth) { const writer = makeByteWriter(canvasWidth); + const trackAscii = makeAsciiRowTracker(canvasWidth, data.length); // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { // Split into RGB and pack into a single 24-bit value (MSB first) @@ -151,6 +187,7 @@ const ConversionFunctions = { // eslint-disable-next-line no-bitwise const rgb = (r << 16) | (g << 8) | (b); writer.push(rgb, 8); + trackAscii(index, (r + g + b) / 3 > settings.ditheringThreshold, writer); } return writer.result(); }, @@ -870,6 +907,16 @@ function glyphComment(image, indent = '') { return `${indent}// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; } +// Removes the trailing separator comma left after the last byte value. +// When settings.showAsciiPreview is on, the string always ends with a +// dot/hash ascii-art comment line instead (see makeAsciiRowTracker), so the +// comma sits just before that comment rather than at the true end. +function stripTrailingComma(str) { + return settings.showAsciiPreview + ? str.replace(/,(\s*\/\/[^\n]*)\n?\s*$/, '$1') + : str.replace(/,\s*$/, ''); +} + // Output the image string to the textfield // eslint-disable-next-line no-unused-vars function generateOutputString() { @@ -887,7 +934,7 @@ function generateOutputString() { code = imageToString(image); // Trim whitespace from end and remove trailing comma - code = code.replace(/,\s*$/, ''); + code = stripTrailingComma(code); code = `\t${code.split('\n').join('\n\t')}\n`; // const variableCount = images.length() > 1 ? count++ : ''; @@ -922,7 +969,7 @@ function generateOutputString() { outputString += comment + code; }); - outputString = outputString.replace(/,\s*$/, ''); + outputString = stripTrailingComma(outputString); outputString = `const ${getImageType()} ${ getIdentifier() @@ -944,7 +991,7 @@ function generateOutputString() { } }); - outputString = outputString.replace(/,\s*$/, ''); + outputString = stripTrailingComma(outputString); outputString = `const ${getByteType()} ${ getIdentifier() }Bitmap` @@ -1003,7 +1050,7 @@ function generateOutputString() { outputString += code; }); // Trim whitespace from end and remove trailing comma - outputString = outputString.replace(/,\s*$/g, ''); + outputString = stripTrailingComma(outputString); } } diff --git a/tests/golden/ascii_preview.txt b/tests/golden/ascii_preview.txt new file mode 100644 index 0000000..00f0683 --- /dev/null +++ b/tests/golden/ascii_preview.txt @@ -0,0 +1,25 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x0f, 0x0f, 0x7f, 0xff, // . . . . # # # # . . . . # # # # . # # # # # # # # # # # # # # # +0x0f, 0x0f, 0xbf, 0xff, // . . . . # # # # . . . . # # # # # . # # # # # # # # # # # # # # +0x0f, 0x0f, 0xdf, 0xff, // . . . . # # # # . . . . # # # # # # . # # # # # # # # # # # # # +0x0f, 0x0f, 0xef, 0xff, // . . . . # # # # . . . . # # # # # # # . # # # # # # # # # # # # +0x0f, 0x0f, 0xf7, 0xff, // . . . . # # # # . . . . # # # # # # # # . # # # # # # # # # # # +0x0f, 0x0f, 0xfb, 0xff, // . . . . # # # # . . . . # # # # # # # # # . # # # # # # # # # # +0x0f, 0x0f, 0xfd, 0xff, // . . . . # # # # . . . . # # # # # # # # # # . # # # # # # # # # +0x0f, 0x0f, 0xfe, 0xff, // . . . . # # # # . . . . # # # # # # # # # # # . # # # # # # # # +0x0f, 0x0f, 0xff, 0x7f, // . . . . # # # # . . . . # # # # # # # # # # # # . # # # # # # # +0x0f, 0x0f, 0xff, 0xbf, // . . . . # # # # . . . . # # # # # # # # # # # # # . # # # # # # +0x0f, 0x0f, 0xff, 0xdf, // . . . . # # # # . . . . # # # # # # # # # # # # # # . # # # # # +0x0f, 0x0f, 0xff, 0xee // . . . . # # # # . . . . # # # # # # # # # # # # # # # . # # # . \ No newline at end of file diff --git a/tests/golden/ascii_preview_arduino.txt b/tests/golden/ascii_preview_arduino.txt new file mode 100644 index 0000000..8be6e07 --- /dev/null +++ b/tests/golden/ascii_preview_arduino.txt @@ -0,0 +1,33 @@ +// 'test_pattern', 32x24px +const unsigned char epd_bitmap_test_pattern [] PROGMEM = { + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # + 0x0f, 0x0f, 0x7f, 0xff, // . . . . # # # # . . . . # # # # . # # # # # # # # # # # # # # # + 0x0f, 0x0f, 0xbf, 0xff, // . . . . # # # # . . . . # # # # # . # # # # # # # # # # # # # # + 0x0f, 0x0f, 0xdf, 0xff, // . . . . # # # # . . . . # # # # # # . # # # # # # # # # # # # # + 0x0f, 0x0f, 0xef, 0xff, // . . . . # # # # . . . . # # # # # # # . # # # # # # # # # # # # + 0x0f, 0x0f, 0xf7, 0xff, // . . . . # # # # . . . . # # # # # # # # . # # # # # # # # # # # + 0x0f, 0x0f, 0xfb, 0xff, // . . . . # # # # . . . . # # # # # # # # # . # # # # # # # # # # + 0x0f, 0x0f, 0xfd, 0xff, // . . . . # # # # . . . . # # # # # # # # # # . # # # # # # # # # + 0x0f, 0x0f, 0xfe, 0xff, // . . . . # # # # . . . . # # # # # # # # # # # . # # # # # # # # + 0x0f, 0x0f, 0xff, 0x7f, // . . . . # # # # . . . . # # # # # # # # # # # # . # # # # # # # + 0x0f, 0x0f, 0xff, 0xbf, // . . . . # # # # . . . . # # # # # # # # # # # # # . # # # # # # + 0x0f, 0x0f, 0xff, 0xdf, // . . . . # # # # . . . . # # # # # # # # # # # # # # . # # # # # + 0x0f, 0x0f, 0xff, 0xee // . . . . # # # # . . . . # # # # # # # # # # # # # # # . # # # . +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 400) +const int epd_bitmap_allArray_LEN = 1; +const unsigned char* epd_bitmap_allArray[1] = { + epd_bitmap_test_pattern +}; diff --git a/tests/golden/ascii_preview_bytes_per_row_ignored.txt b/tests/golden/ascii_preview_bytes_per_row_ignored.txt new file mode 100644 index 0000000..00f0683 --- /dev/null +++ b/tests/golden/ascii_preview_bytes_per_row_ignored.txt @@ -0,0 +1,25 @@ +// 'test_pattern', 32x24px +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x33, 0x33, 0x00, 0xff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xcc, 0xcc, 0x00, 0xff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x0f, 0x0f, 0x7f, 0xff, // . . . . # # # # . . . . # # # # . # # # # # # # # # # # # # # # +0x0f, 0x0f, 0xbf, 0xff, // . . . . # # # # . . . . # # # # # . # # # # # # # # # # # # # # +0x0f, 0x0f, 0xdf, 0xff, // . . . . # # # # . . . . # # # # # # . # # # # # # # # # # # # # +0x0f, 0x0f, 0xef, 0xff, // . . . . # # # # . . . . # # # # # # # . # # # # # # # # # # # # +0x0f, 0x0f, 0xf7, 0xff, // . . . . # # # # . . . . # # # # # # # # . # # # # # # # # # # # +0x0f, 0x0f, 0xfb, 0xff, // . . . . # # # # . . . . # # # # # # # # # . # # # # # # # # # # +0x0f, 0x0f, 0xfd, 0xff, // . . . . # # # # . . . . # # # # # # # # # # . # # # # # # # # # +0x0f, 0x0f, 0xfe, 0xff, // . . . . # # # # . . . . # # # # # # # # # # # . # # # # # # # # +0x0f, 0x0f, 0xff, 0x7f, // . . . . # # # # . . . . # # # # # # # # # # # # . # # # # # # # +0x0f, 0x0f, 0xff, 0xbf, // . . . . # # # # . . . . # # # # # # # # # # # # # . # # # # # # +0x0f, 0x0f, 0xff, 0xdf, // . . . . # # # # . . . . # # # # # # # # # # # # # # . # # # # # +0x0f, 0x0f, 0xff, 0xee // . . . . # # # # . . . . # # # # # # # # # # # # # # # . # # # . \ No newline at end of file diff --git a/tests/golden/ascii_preview_horizontal565.txt b/tests/golden/ascii_preview_horizontal565.txt new file mode 100644 index 0000000..291b2d2 --- /dev/null +++ b/tests/golden/ascii_preview_horizontal565.txt @@ -0,0 +1,25 @@ +// 'test_pattern', 32x24px +0xf800, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x1082, 0x2104, 0x3186, 0x4228, 0x52aa, 0x632c, 0x73ae, 0x8c51, 0x9cd3, 0xad55, 0xbdd7, 0xce79, 0xdefb, 0xef7d, 0xffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # . # # # # # # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # . # # # # # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # . # # # # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # . # # # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # . # # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # . # # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # # . # # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # # # . # # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # # # # . # # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # # # # # . # # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // . . . . . . . . . . . . # # # # # # # # # # # # # # . # # # # # +0xf800, 0xf800, 0xf800, 0xf800, 0x07e0, 0x07e0, 0x07e0, 0x07e0, 0x001f, 0x001f, 0x001f, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0x001f // . . . . . . . . . . . . # # # # # # # # # # # # # # # . # # # . \ No newline at end of file diff --git a/tests/golden/ascii_preview_horizontal888.txt b/tests/golden/ascii_preview_horizontal888.txt new file mode 100644 index 0000000..8d1f4e4 --- /dev/null +++ b/tests/golden/ascii_preview_horizontal888.txt @@ -0,0 +1,25 @@ +// 'test_pattern', 32x24px +0x00ff0000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // . . # # . . # # . . # # . . # # . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000, 0x00000000, 0x00111111, 0x00222222, 0x00333333, 0x00444444, 0x00555555, 0x00666666, 0x00777777, 0x00888888, 0x00999999, 0x00aaaaaa, 0x00bbbbbb, 0x00cccccc, 0x00dddddd, 0x00eeeeee, 0x00ffffff, // # # . . # # . . # # . . # # . . . . . . . . . . # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # . # # # # # # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # . # # # # # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # . # # # # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # . # # # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # . # # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # . # # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # # . # # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # # # . # # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # # # # . # # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # # # # # . # # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, // . . . . . . . . . . . . # # # # # # # # # # # # # # . # # # # # +0x00ff0000, 0x00ff0000, 0x00ff0000, 0x00ff0000, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x000000ff // . . . . . . . . . . . . # # # # # # # # # # # # # # # . # # # . \ No newline at end of file diff --git a/tests/golden/output_adafruit_gfx.txt b/tests/golden/output_adafruit_gfx.txt index f8c637a..68216c2 100644 --- a/tests/golden/output_adafruit_gfx.txt +++ b/tests/golden/output_adafruit_gfx.txt @@ -1,4 +1,4 @@ -const unsigned char epd_bitmapBitmap [] PROGMEM = { +const unsigned char epd_bitmap_Bitmap [] PROGMEM = { // 'test_pattern', 32x24px 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, @@ -8,12 +8,12 @@ const unsigned char epd_bitmapBitmap [] PROGMEM = { 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee }; -const GFXbitmapGlyph epd_bitmapGlyphs [] PROGMEM = { +const GFXbitmapGlyph epd_bitmap_Glyphs [] PROGMEM = { { 0, 32, 24, 0, '0' }// 'test_pattern' }; -const GFXbitmapFont epd_bitmapFont PROGMEM = { - (uint8_t *)epd_bitmapBitmap, - (GFXbitmapGlyph *)epd_bitmapGlyphs, +const GFXbitmapFont epd_bitmap_Font PROGMEM = { + (uint8_t *)epd_bitmap_Bitmap, + (GFXbitmapGlyph *)epd_bitmap_Glyphs, 1 }; diff --git a/tests/golden/output_adafruit_gfx_esp32.txt b/tests/golden/output_adafruit_gfx_esp32.txt index dc7d45a..21ed39f 100644 --- a/tests/golden/output_adafruit_gfx_esp32.txt +++ b/tests/golden/output_adafruit_gfx_esp32.txt @@ -1,4 +1,4 @@ -const uint8_t epd_bitmapBitmap [] = { +const uint8_t epd_bitmap_Bitmap [] = { // 'test_pattern', 32x24px 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, @@ -8,12 +8,12 @@ const uint8_t epd_bitmapBitmap [] = { 0x0f, 0x0f, 0xff, 0x7f, 0x0f, 0x0f, 0xff, 0xbf, 0x0f, 0x0f, 0xff, 0xdf, 0x0f, 0x0f, 0xff, 0xee }; -const GFXbitmapGlyph epd_bitmapGlyphs [] = { +const GFXbitmapGlyph epd_bitmap_Glyphs [] = { { 0, 32, 24, 0, '0' }// 'test_pattern' }; -const GFXbitmapFont epd_bitmapFont = { - (uint8_t *)epd_bitmapBitmap, - (GFXbitmapGlyph *)epd_bitmapGlyphs, +const GFXbitmapFont epd_bitmap_Font = { + (uint8_t *)epd_bitmap_Bitmap, + (GFXbitmapGlyph *)epd_bitmap_Glyphs, 1 }; diff --git a/tests/golden/output_arduino_single.txt b/tests/golden/output_arduino_single.txt index 44ca337..633ec37 100644 --- a/tests/golden/output_arduino_single.txt +++ b/tests/golden/output_arduino_single.txt @@ -1,4 +1,4 @@ -const unsigned char epd_bitmap [] PROGMEM = { +const unsigned char epd_bitmap_ [] PROGMEM = { // 'test_pattern', 32x24px 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, diff --git a/tests/golden/output_arduino_single_esp32.txt b/tests/golden/output_arduino_single_esp32.txt index 400c530..55e7fc0 100644 --- a/tests/golden/output_arduino_single_esp32.txt +++ b/tests/golden/output_arduino_single_esp32.txt @@ -1,4 +1,4 @@ -const uint8_t epd_bitmap [] = { +const uint8_t epd_bitmap_ [] = { // 'test_pattern', 32x24px 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0x33, 0x33, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, 0xcc, 0xcc, 0x00, 0xff, diff --git a/tests/test_conversions.py b/tests/test_conversions.py index a26d268..7f9ac36 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -46,6 +46,8 @@ BASE_SETTINGS = { "prefix": "0x", "separator": ", ", "esp32Format": False, + "bytesPerRow": 16, + "showAsciiPreview": False, } OUTPUT_FORMAT_IDS = { @@ -104,6 +106,27 @@ SCENARIOS = [ ("dithering_threshold_low", {"ditheringThreshold": 64}, None), ("dithering_threshold_high", {"ditheringThreshold": 200}, None), ("bitswap", {"bitswap": True}, None), + ("ascii_preview", {"showAsciiPreview": True}, None), + ( + "ascii_preview_bytes_per_row_ignored", + {"showAsciiPreview": True, "bytesPerRow": 3}, + None, + ), + ( + "ascii_preview_horizontal565", + {"showAsciiPreview": True, "drawMode": "horizontal565"}, + None, + ), + ( + "ascii_preview_horizontal888", + {"showAsciiPreview": True, "drawMode": "horizontal888"}, + None, + ), + ( + "ascii_preview_arduino", + {"showAsciiPreview": True, "outputFormat": "arduino"}, + None, + ), ("remove_zeroes_commas", {"prefix": "", "separator": ""}, None), # scale=1 (original) on a canvas larger than the image so the # background actually shows through in the margins. @@ -207,6 +230,8 @@ def apply_settings(page, settings): set_checkbox(page, "#bitswap", settings["bitswap"]) page.fill("#prefix", settings["prefix"]) page.fill("#separator", settings["separator"]) + page.fill("#bytesPerRow", str(settings["bytesPerRow"])) + set_checkbox(page, "#showAsciiPreview", settings["showAsciiPreview"]) # esp32Format checkbox only exists in the DOM (visible) for non-plain # output formats; plain output has no type/PROGMEM declarations to affect. if settings["outputFormat"] != "plain": @@ -237,12 +262,18 @@ def roundtrip_eligible(settings): "Paste byte array" box: other draw modes aren't supported by the parser, bitswap mangles the byte stream with no importer to undo it, and the parser splits bytes on commas, so a separator without one (e.g. the - empty string used to strip formatting) can't be read back.""" + empty string used to strip formatting) can't be read back. showAsciiPreview + and a non-default bytesPerRow are excluded too: run_roundtrip reloads a + fresh page and never restores those two, so the re-exported text would use + default formatting and not textually match the original even though the + underlying pixel data round-trips fine.""" return ( settings["drawMode"] in ("horizontal1bit", "vertical1bit") and settings["outputFormat"] == "plain" and not settings["bitswap"] and "," in settings["separator"] + and not settings["showAsciiPreview"] + and settings["bytesPerRow"] == BASE_SETTINGS["bytesPerRow"] ) From 0738e806e1d062025c97791d7001c550915704f5 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 13:56:33 +0200 Subject: [PATCH 35/63] Fix issue with reading from byte array not populating images list properly --- js/script.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/js/script.js b/js/script.js index de90217..77778ca 100644 --- a/js/script.js +++ b/js/script.js @@ -548,7 +548,7 @@ function listToImageHorizontal(list, canvas) { const decoded = hexListToBits(list); if (!decoded.valid) { reportInvalidByteArray(decoded.s); - return; + return false; } const imgData = ctx.createImageData(canvas.width, canvas.height); @@ -577,6 +577,7 @@ function listToImageHorizontal(list, canvas) { ctx.putImageData(imgData, 0, 0); commitCanvasToFirstImage(canvas); + return true; } // Quick and effective way to draw single pixels onto the canvas @@ -600,7 +601,7 @@ function listToImageVertical(list, canvas) { const decoded = hexListToBits(list); if (!decoded.valid) { reportInvalidByteArray(decoded.s); - return; + return false; } let page = 0; @@ -623,6 +624,7 @@ function listToImageVertical(list, canvas) { } commitCanvasToFirstImage(canvas); + return true; } // Set width/height preset for byte array text input @@ -688,10 +690,17 @@ function handleTextInput(drawMode) { return; } - if (drawMode === 'horizontal') { - listToImageHorizontal(list, canvas); - } else { - listToImageVertical(list, canvas); + const success = drawMode === 'horizontal' + ? listToImageHorizontal(list, canvas) + : listToImageVertical(list, canvas); + + if (success) { + document.getElementById('text-input-error').style.display = 'none'; + document.querySelectorAll('.no-file-selected').forEach((el) => { + // eslint-disable-next-line no-param-reassign + el.style.display = 'none'; + }); + checkImagesAvailable(); } } From 00a9ec249982fbd9f2c5170a6ae00d422afcdcb7 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 14:16:09 +0200 Subject: [PATCH 36/63] Small UI tweaks --- css/style.css | 52 ++++++++++++++++++++++++++++++++++++++++++++++----- index.html | 23 ++++++++++------------- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/css/style.css b/css/style.css index b1523db..2d9ebbd 100644 --- a/css/style.css +++ b/css/style.css @@ -19,6 +19,7 @@ --outline-variant: #bbcac3; --editor-bg: #ffffff; --error: #ba1a1a; + --focus-ring: rgba(0, 107, 88, 0.25); /* Radii */ --radius-lg: 0.25rem; @@ -144,7 +145,7 @@ body { background: var(--surface-container-low); border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); - padding: 24px 0; + /* padding: 24px 0; */ } .sidebar-nav { @@ -211,6 +212,10 @@ body { scroll-margin-top: 88px; } +.section.first-step { + +} + .step-num { position: absolute; left: 16px; @@ -245,7 +250,7 @@ body { z-index: 1; } -#step-4.step::before { +#step-0.step::before, #step-4.step::before { display: none; } @@ -396,6 +401,42 @@ code { margin: 0; } +/* ---- Settings grid (step 2) ---- */ + +.settings-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 28px 32px; +} + +.settings-grid.table { + margin: 8px 0 0; +} + +.settings-grid .table-row { + display: flex; + flex-direction: column; + gap: 8px; +} + +.settings-grid .table-row.full-row { + grid-column: 1 / -1; +} + +.settings-grid .table-cell { + display: block; + padding: 0; +} + +.settings-grid .table-cell:first-child { + width: auto; + padding-right: 0; +} + +.settings-grid .table-cell:last-child { + width: auto; +} + .nested-table .table-cell { color: var(--text-muted); font-size: 0.9em; @@ -465,7 +506,7 @@ textarea:focus, select:focus { outline: none; border-color: var(--primary); - box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25); + box-shadow: 0 0 0 2px var(--focus-ring); } select { @@ -676,7 +717,7 @@ input[type="file"]::file-selector-button { width: 100%; /* margin-top: 16px; */ background: var(--editor-bg); - color: #000000; + color: var(--on-surface); border: 1px solid var(--on-surface-variant); border-radius: var(--radius-xl); font-family: var(--font-mono); @@ -689,7 +730,7 @@ input[type="file"]::file-selector-button { .code-output:focus { outline: none; border-color: var(--primary); - box-shadow: 0 0 0 2px rgba(0, 107, 88, 0.25); + box-shadow: 0 0 0 2px var(--focus-ring); } /* ---- Hidden by default (toggled via JS) ---- */ @@ -806,6 +847,7 @@ input[type="file"]::file-selector-button { --border-subtle: #2d3133; --outline-variant: #414a47; --editor-bg: #0b0e0f; + --focus-ring: rgba(78, 221, 187, 0.35); } :root[data-theme="dark"] .theme-icon-dark { diff --git a/index.html b/index.html index ba10b37..8d966c5 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,7 @@
    -
    +

    image2cpp

    image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
    It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but @@ -65,13 +66,13 @@

    Select more images or continue to step 2
    -

    or paste byte array

    -
    +

    or paste existing byte array

    +
    x px
    -
    Set this to the size of your image.
    Common sizes: 16x16, 32x32, 64x48, 128x32, 128x64.
    +
    Set this to the size of your image.
    Common sizes: 16x16 32x32 64x48 128x32 128x64.
    @@ -86,9 +87,9 @@
    2

    Image Settings

    -
    +
    -
    +
      @@ -165,13 +166,9 @@ -
      -
      - -
      -
      -
      - Centering the image only works when using a canvas larger than the original image. +
      + Centering the image only works when using a canvas larger than the original image. +
      From d709ea3af5e1c41cde0fbf2a4dedb9cadc918136 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 14:24:01 +0200 Subject: [PATCH 37/63] Show regular error on issue with imported byte array (instead of alert) --- js/script.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/script.js b/js/script.js index 77778ca..d1412bd 100644 --- a/js/script.js +++ b/js/script.js @@ -524,8 +524,9 @@ function hexListToBits(list) { } function reportInvalidByteArray(s) { - // eslint-disable-next-line no-alert - alert('Something went wrong converting the string. Make sure there are no comments in your input.'); + const errorEl = document.getElementById('text-input-error'); + errorEl.textContent = 'Something went wrong converting the string. Make sure there are no comments in your input.'; + errorEl.style.display = 'block'; // eslint-disable-next-line no-console console.error('invalid hexToBinary: ', s); } @@ -684,8 +685,8 @@ function handleTextInput(drawMode) { const list = input.split(','); if (list.length === 1 && list[0] === '') { - // eslint-disable-next-line no-alert const errorEl = document.getElementById('text-input-error'); + errorEl.textContent = 'Byte array is empty or invalid. Make sure there are no comments in your input.'; errorEl.style.display = 'block'; return; } From 12de8c4700b077d77db0b2dbedb71ffd96768ac5 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 14:24:26 +0200 Subject: [PATCH 38/63] update layout --- css/style.css | 7 ++++++- index.html | 2 +- restyle.md | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index 2d9ebbd..154b655 100644 --- a/css/style.css +++ b/css/style.css @@ -343,7 +343,12 @@ code { .input-methods-row { display: flex; - align-items: flex-start; + align-items: space-between; + gap: 10px; +} + +.input-methods-row .column { + width: 50%; } .column { diff --git a/index.html b/index.html index 8d966c5..5d0e710 100644 --- a/index.html +++ b/index.html @@ -63,7 +63,7 @@
      You can import multiple images at once.
      All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

      -
      Select more images or continue to step 2
      +
      Add more images or continue to step 2

      or paste existing byte array

      diff --git a/restyle.md b/restyle.md index 607bb59..7721107 100644 --- a/restyle.md +++ b/restyle.md @@ -175,7 +175,7 @@
      - +
      From b49c116221bf85632b40b0097399577d20ad507f Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 14:53:24 +0200 Subject: [PATCH 39/63] Show the imported byte array image below the input field --- css/style.css | 18 ++++++++++++++++-- index.html | 3 ++- js/script.js | 11 +++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index 154b655..bedc250 100644 --- a/css/style.css +++ b/css/style.css @@ -543,6 +543,20 @@ input[type="radio"] { display: none; } +.text-input-preview-canvas { + display: none; + border: 1px solid var(--outline-variant); + border-radius: var(--radius-lg); + /* background: var(--primary-container); */ + margin: 10px 0; + /* padding: 4px; */ + max-width: 100%; +} + +#read-images-note { + display: none; +} + .text-input-size { margin: 8px 0; color: var(--on-surface-variant); @@ -705,9 +719,9 @@ input[type="file"]::file-selector-button { #images-canvas-container canvas { border: 1px solid var(--outline-variant); border-radius: var(--radius-lg); - background: var(--primary-container); + /* background: var(--primary-container); */ margin: 10px 15px 10px 0; - padding: 4px; + /* padding: 4px; */ } .inlineImg { diff --git a/index.html b/index.html index 5d0e710..b15c797 100644 --- a/index.html +++ b/index.html @@ -77,8 +77,9 @@
      +
      Byte array is invalid (or empty).
      -
      Read images appear at step 3 below
      +
      If your image looks glitchy try changing the dimensions above and press the read button again.
      Read images also appear at step 3 below for further processing.
      diff --git a/js/script.js b/js/script.js index d1412bd..cb0c5ab 100644 --- a/js/script.js +++ b/js/script.js @@ -702,6 +702,17 @@ function handleTextInput(drawMode) { el.style.display = 'none'; }); checkImagesAvailable(); + + // Mirror the result onto a small preview canvas right below the + // buttons, so the pasted array is visible without scrolling to step 3. + const previewCanvas = document.getElementById('text-input-preview-canvas'); + previewCanvas.width = canvas.width; + previewCanvas.height = canvas.height; + previewCanvas.getContext('2d').drawImage(canvas, 0, 0); + previewCanvas.style.display = 'block'; + + const readImagesNoteEl = document.getElementById('read-images-note'); + readImagesNoteEl.style.display = 'block'; } } From 4b90b66b4ef2dc534f87f7b4602c48cfd98ae5c4 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 14:55:55 +0200 Subject: [PATCH 40/63] ui update --- css/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index bedc250..435ab2c 100644 --- a/css/style.css +++ b/css/style.css @@ -344,7 +344,7 @@ code { .input-methods-row { display: flex; align-items: space-between; - gap: 10px; + gap: 20px; } .input-methods-row .column { @@ -353,11 +353,13 @@ code { .column { float: left; + border-right: 1px solid var(--border-subtle); } .column-right { float: right; margin-left: auto; + border-right: none; } /* ---- Table layout for settings ---- */ From 872ed764f5759cd59a8f01c8730a614d7ee01936 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 15:03:23 +0200 Subject: [PATCH 41/63] Add previews to uploaded images --- css/style.css | 32 +++++++++++++++++++++++++++++--- js/script.js | 28 ++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/css/style.css b/css/style.css index 435ab2c..8db299c 100644 --- a/css/style.css +++ b/css/style.css @@ -663,8 +663,8 @@ input[type="file"]::file-selector-button { .remove-button { margin: 0 0 0 10px; padding: 2px 8px; - background: var(--surface-container-high); - color: var(--on-surface-variant); + /* background: var(--surface-container-high); */ + /* color: var(--on-surface-variant); */ } #no-images-error { @@ -674,10 +674,16 @@ input[type="file"]::file-selector-button { .file-input-entry { display: flex; - justify-content: space-between; + align-items: center; + gap: 8px; margin: 10px 0; } +.file-input-entry span { + flex: 1; + min-width: 0; +} + .file-info { color: var(--text-muted); font-size: 12px; @@ -704,12 +710,32 @@ input[type="file"]::file-selector-button { } #image-size-settings li { + display: flex; + align-items: flex-start; + gap: 12px; margin: 6px 0; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 8px 12px; } +.image-size-thumb { + width: auto; + height: auto; + max-width: 100px; + max-height: 100px; + border: 1px solid var(--outline-variant); + border-radius: var(--radius-md, 4px); + background: var(--surface-variant, #fff); + flex-shrink: 0; + vertical-align: middle; +} + +.image-size-details { + flex: 1; + min-width: 0; +} + /* ---- Preview canvases ---- */ #images-canvas-container { diff --git a/js/script.js b/js/script.js index cb0c5ab..3580026 100644 --- a/js/script.js +++ b/js/script.js @@ -783,6 +783,11 @@ function handleImageSelection(evt) { const fileInputColumnEntry = document.createElement('div'); fileInputColumnEntry.className = 'file-input-entry'; + const fileInputColumnEntryThumb = document.createElement('img'); + fileInputColumnEntryThumb.className = 'image-size-thumb'; + fileInputColumnEntryThumb.src = img.src; + fileInputColumnEntryThumb.alt = file.name; + const fileInputColumnEntryLabel = document.createElement('span'); fileInputColumnEntryLabel.textContent = file.name; @@ -795,6 +800,11 @@ function handleImageSelection(evt) { const imageEntry = document.createElement('li'); imageEntry.setAttribute('data-img', file.name); + const thumb = document.createElement('img'); + thumb.className = 'image-size-thumb'; + thumb.src = img.src; + thumb.alt = file.name; + const w = document.createElement('input'); w.type = 'number'; w.name = 'width'; @@ -874,16 +884,22 @@ function handleImageSelection(evt) { fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; + fileInputColumnEntry.appendChild(fileInputColumnEntryThumb); fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); fileInputColumn.appendChild(fileInputColumnEntry); - imageEntry.appendChild(fn); - imageEntry.appendChild(w); - imageEntry.appendChild(document.createTextNode(' x ')); - imageEntry.appendChild(h); - imageEntry.appendChild(gil); - imageEntry.appendChild(gi); + const imageEntryDetails = document.createElement('div'); + imageEntryDetails.className = 'image-size-details'; + imageEntryDetails.appendChild(fn); + imageEntryDetails.appendChild(w); + imageEntryDetails.appendChild(document.createTextNode(' x ')); + imageEntryDetails.appendChild(h); + imageEntryDetails.appendChild(gil); + imageEntryDetails.appendChild(gi); + + imageEntry.appendChild(thumb); + imageEntry.appendChild(imageEntryDetails); imageSizeSettings.appendChild(imageEntry); From ce4d3ecf9872bf3170753ba639431c8e52368627 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 15:08:15 +0200 Subject: [PATCH 42/63] use floyd-steinberg by default --- index.html | 2 +- js/script.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index b15c797..15955b2 100644 --- a/index.html +++ b/index.html @@ -129,9 +129,9 @@
      diff --git a/js/script.js b/js/script.js index 3580026..01b4fea 100644 --- a/js/script.js +++ b/js/script.js @@ -16,7 +16,7 @@ const settings = { drawMode: 'horizontal', prefix: '0x', ditheringThreshold: 128, - ditheringMode: 0, + ditheringMode: 2, outputFormat: 'plain', separator: ', ', outputComments: true, From 24f6d0d48c47ff09926e028a1366ea98348aa1d3 Mon Sep 17 00:00:00 2001 From: javl Date: Sat, 15 Aug 2026 15:09:44 +0200 Subject: [PATCH 43/63] update layout --- css/style.css | 10 +++++++--- index.html | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/css/style.css b/css/style.css index 8db299c..4650c63 100644 --- a/css/style.css +++ b/css/style.css @@ -347,13 +347,17 @@ code { gap: 20px; } -.input-methods-row .column { +.input-methods-row .column-left { + width: 50%; +} +.input-methods-row .column-right { width: 50%; } -.column { +.column-left { float: left; border-right: 1px solid var(--border-subtle); + padding-right: 12px; } .column-right { @@ -851,7 +855,7 @@ input[type="file"]::file-selector-button { flex-direction: column; } - .column, + .column-left, .column-right { float: none; width: 100%; diff --git a/index.html b/index.html index 15955b2..9bdcf06 100644 --- a/index.html +++ b/index.html @@ -58,14 +58,14 @@
      1
      -
      +

      Select image

      You can import multiple images at once.
      All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

      Add more images or continue to step 2
      -
      +

      or paste existing byte array


      From 720faac3f9cf4a6390f1694cebeaf4767061d524 Mon Sep 17 00:00:00 2001 From: javl Date: Mon, 17 Aug 2026 20:28:51 +0200 Subject: [PATCH 44/63] don't draw preview canvas for text input unless there is something to show --- js/script.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/js/script.js b/js/script.js index 01b4fea..4a2ddb5 100644 --- a/js/script.js +++ b/js/script.js @@ -648,22 +648,6 @@ function checkImagesAvailable() { // Handle inserting an image by pasting code // eslint-disable-next-line no-unused-vars function handleTextInput(drawMode) { - const canvasContainer = document.getElementById('images-canvas-container'); - const canvas = document.createElement('canvas'); - - canvas.width = parseInt(document.getElementById('text-input-width').value); - canvas.height = parseInt(document.getElementById('text-input-height').value); - settings.screenWidth = canvas.width; - settings.screenHeight = canvas.height; - - if (canvasContainer.children.length) { - canvasContainer.removeChild(canvasContainer.firstChild); - } - canvasContainer.appendChild(canvas); - - const image = new Image(); - images.setByIndex(0, { img: image, canvas, ctx: canvas.getContext('2d') }); - let input = document.getElementById('byte-input').value; // Remove Arduino code @@ -691,6 +675,22 @@ function handleTextInput(drawMode) { return; } + const canvasContainer = document.getElementById('images-canvas-container'); + const canvas = document.createElement('canvas'); + + canvas.width = parseInt(document.getElementById('text-input-width').value); + canvas.height = parseInt(document.getElementById('text-input-height').value); + settings.screenWidth = canvas.width; + settings.screenHeight = canvas.height; + + if (canvasContainer.children.length) { + canvasContainer.removeChild(canvasContainer.firstChild); + } + canvasContainer.appendChild(canvas); + + const image = new Image(); + images.setByIndex(0, { img: image, canvas, ctx: canvas.getContext('2d') }); + const success = drawMode === 'horizontal' ? listToImageHorizontal(list, canvas) : listToImageVertical(list, canvas); From 591b0644c1e45062a4b8cb2e35b5f4c0e1505f44 Mon Sep 17 00:00:00 2001 From: javl Date: Mon, 17 Aug 2026 20:30:58 +0200 Subject: [PATCH 45/63] fix layout issue on 2nd section --- css/style.css | 200 ++++++++++++++++++++++++++++++++++---------------- index.html | 18 ++++- 2 files changed, 153 insertions(+), 65 deletions(-) diff --git a/css/style.css b/css/style.css index 4650c63..68741ff 100644 --- a/css/style.css +++ b/css/style.css @@ -25,6 +25,17 @@ --radius-lg: 0.25rem; --radius-xl: 0.5rem; + /* Spacing scale (use these for margin/padding/gap instead of magic numbers) */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 32px; + --space-8: 48px; + --space-9: 64px; + /* Fonts */ --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; @@ -59,7 +70,7 @@ body { max-width: 1400px; margin: auto; height: 64px; - padding: 0 24px; + padding: 0 var(--space-6); display: flex; align-items: center; justify-content: space-between; @@ -68,7 +79,7 @@ body { .topnav-left { display: flex; align-items: center; - gap: 32px; + gap: var(--space-7); } .brand { @@ -79,7 +90,7 @@ body { .topnav-links { display: flex; - gap: 24px; + gap: var(--space-6); } .topnav-links a { @@ -97,7 +108,7 @@ body { .topnav-right { display: flex; align-items: center; - gap: 12px; + gap: var(--space-3); } .icon-btn { @@ -130,10 +141,10 @@ body { .layout { display: flex; - gap: 32px; + gap: var(--space-7); max-width: 1400px; margin: auto; - padding: 32px 24px 64px; + padding: var(--space-7) var(--space-6) var(--space-9); align-items: flex-start; } @@ -156,8 +167,8 @@ body { .sidebar-link { display: flex; align-items: center; - gap: 12px; - padding: 12px 24px; + gap: var(--space-3); + padding: var(--space-3) var(--space-6); font-family: var(--font-mono); font-size: 13px; font-weight: 500; @@ -199,7 +210,7 @@ body { .wrapper { display: flex; flex-direction: column; - gap: 24px; + gap: var(--space-6); flex: 1; min-width: 0; } @@ -212,9 +223,6 @@ body { scroll-margin-top: 88px; } -.section.first-step { - -} .step-num { position: absolute; @@ -254,6 +262,12 @@ body { display: none; } +/* step-3 (Preview) lives as a pinned side panel next to Settings, not + inline in the vertical step flow, so it doesn't carry a connecting rail */ +#step-3.step::before { + display: none; +} + /* ---- Typography ---- */ h1 { @@ -262,7 +276,7 @@ h1 { letter-spacing: -0.02em; font-weight: 600; color: var(--primary); - margin-bottom: 8px; + margin-bottom: var(--space-2); } h2 { @@ -278,11 +292,11 @@ h2 { line-height: 16px; font-weight: 500; color: var(--primary); - margin: 0 0 12px; + margin: 0 0 var(--space-3); } p { - margin: 16px 0; + margin: var(--space-4) 0; color: var(--on-surface-variant); } @@ -310,24 +324,24 @@ code { background: var(--surface-container-lowest); border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); - padding: 32px; + padding: var(--space-7); } /* First (intro) section: keep it flush/light */ -.section:first-child { +.section.first-step { background: transparent; border: none; - padding: 8px 0 0; + padding: var(--space-2) 0 0; } .section h2 { - margin-bottom: 16px; + margin-bottom: var(--space-4); } /* Legacy divider no longer needed; cards separate content */ .bottom-divider { border-bottom: none; - padding-bottom: 32px; + padding-bottom: var(--space-7); } .sub-section { @@ -339,12 +353,35 @@ code { width: 100%; } +/* ---- Settings + live preview layout (step 2 + step 3) ---- + Preview sits pinned next to Settings so the effect of dithering / + threshold / invert etc is visible without scrolling away from the + controls that change it. */ + +.settings-preview-row { + display: flex; + align-items: flex-start; + gap: var(--space-6); +} + +.settings-preview-row #step-2 { + flex: 1 1 auto; + min-width: 0; +} + +.settings-preview-row #step-3 { + flex: 0 0 300px; + width: 300px; + position: sticky; + top: 96px; +} + /* ---- Multi-column (select image row) ---- */ .input-methods-row { display: flex; align-items: space-between; - gap: 20px; + gap: var(--space-5); } .input-methods-row .column-left { @@ -355,14 +392,11 @@ code { } .column-left { - float: left; border-right: 1px solid var(--border-subtle); - padding-right: 12px; + padding-right: var(--space-3); } .column-right { - float: right; - margin-left: auto; border-right: none; } @@ -370,7 +404,7 @@ code { .table { display: table; - margin: 8px 0 0; + margin: var(--space-2) 0 0; width: 100%; border-collapse: collapse; } @@ -382,13 +416,13 @@ code { .table-cell { display: table-cell; - padding: 10px 0; + padding: var(--space-2) 0; vertical-align: top; } .table-cell:first-child { width: 30%; - padding-right: 16px; + padding-right: var(--space-4); } .table-cell:last-child { @@ -403,7 +437,7 @@ code { } .table-cell:last-child label { - margin-right: 10px; + margin-right: var(--space-2); color: var(--on-surface-variant); font-size: 14px; } @@ -412,22 +446,25 @@ code { margin: 0; } -/* ---- Settings grid (step 2) ---- */ +/* ---- Settings grid (step 2) ---- + 2-column min-width forces a predictable 2-up (or 1-up) grid instead of + auto-fitting 3 columns, which paired short/tall fields at random and + left ragged whitespace between rows. */ .settings-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 28px 32px; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: var(--space-6) var(--space-7); } .settings-grid.table { - margin: 8px 0 0; + margin: var(--space-2) 0 0; } .settings-grid .table-row { display: flex; flex-direction: column; - gap: 8px; + gap: var(--space-2); } .settings-grid .table-row.full-row { @@ -454,12 +491,26 @@ code { width: 200px; } +#format-caption-container { + /* Reserve space for the longest caption so switching output format + doesn't shift the fields/buttons below it. */ + min-height: 100px; +} + +#extra-settings-container { + /* Reserve space for the tallest combination (Adafruit GFX shows both its + own fields and the Arduino identifier fields) so the Generate/Copy/ + Download buttons don't jump around when switching output format. */ + min-height: 278px; + transition: min-height 0.15s ease; +} + #format-caption-container div { color: var(--text-muted); display: none; font-size: 14px; line-height: 1.5em; - padding: 10px 0 15px; + padding: var(--space-2) 0 var(--space-4); width: 100%; } @@ -469,7 +520,7 @@ code { color: var(--text-muted); font-size: 14px; line-height: 1.5em; - margin: 4px 0; + margin: var(--space-1) 0; } #glyph-name-note { @@ -482,7 +533,7 @@ code { #continue-note { display: none; - margin-top: 30px; + margin-top: var(--space-7); } .msg { @@ -545,7 +596,7 @@ input[type="radio"] { #text-input-error { color: var(--error); font-size: 14px; - margin-top: 8px; + margin-top: var(--space-2); display: none; } @@ -554,7 +605,7 @@ input[type="radio"] { border: 1px solid var(--outline-variant); border-radius: var(--radius-lg); /* background: var(--primary-container); */ - margin: 10px 0; + margin: var(--space-2) 0; /* padding: 4px; */ max-width: 100%; } @@ -564,7 +615,7 @@ input[type="radio"] { } .text-input-size { - margin: 8px 0; + margin: var(--space-2) 0; color: var(--on-surface-variant); font-size: 14px; } @@ -576,8 +627,8 @@ input[type="radio"] { .sub-field { display: flex; align-items: center; - gap: 8px; - margin-top: 8px; + gap: var(--space-2); + margin-top: var(--space-2); } .sub-field:first-child { @@ -590,7 +641,7 @@ input[type="radio"] { .glyph-input { width: 300px; - margin-left: 10px; + margin-left: var(--space-2); } .text-input { @@ -608,7 +659,7 @@ button { border: none; border-radius: var(--radius-lg); padding: 8px 16px; - margin: 10px 8px 0 0; + margin: var(--space-2) var(--space-2) 0 0; cursor: pointer; transition: opacity 0.15s ease, background-color 0.15s ease; } @@ -628,7 +679,7 @@ input[type="file"] { border-radius: var(--radius-lg); font-size: 14px; padding: 16px 20px; - margin: 10px 0 0; + margin: var(--space-2) 0 0; cursor: pointer; width: 100%; max-width: 360px; @@ -646,14 +697,13 @@ input[type="file"]::file-selector-button { border: none; border-radius: var(--radius-lg); padding: 6px 12px; - margin-right: 12px; + margin-right: var(--space-3); cursor: pointer; } .generate-button { background: var(--primary); color: var(--on-primary); - margin: 32px 8px 20px 0; padding: 10px 20px; } @@ -661,26 +711,34 @@ input[type="file"]::file-selector-button { #download-button { background: var(--secondary); color: var(--on-secondary); - margin-top: 32px; +} + +/* Same margin rhythm across all three action buttons so the gap above + them and before the code output box is consistent regardless of which + buttons happen to be in view. */ +.generate-button, +#copy-button, +#download-button { + margin: var(--space-7) var(--space-2) var(--space-6) 0; } .remove-button { - margin: 0 0 0 10px; + margin: 0 0 0 var(--space-2); padding: 2px 8px; /* background: var(--surface-container-high); */ /* color: var(--on-surface-variant); */ } #no-images-error { - margin-bottom: 10px; + margin-bottom: var(--space-2); } /* ---- File info / size list ---- */ .file-input-entry { display: flex; align-items: center; - gap: 8px; - margin: 10px 0; + gap: var(--space-2); + margin: var(--space-2) 0; } .file-input-entry span { @@ -691,7 +749,7 @@ input[type="file"]::file-selector-button { .file-info { color: var(--text-muted); font-size: 12px; - margin-left: 20px; + margin-left: var(--space-5); max-width: 320px; white-space: pre; } @@ -699,7 +757,7 @@ input[type="file"]::file-selector-button { .file-name { display: block; margin-left: 0; - margin-bottom: 8px; + margin-bottom: var(--space-2); font-weight: 500; color: var(--on-surface-variant); } @@ -716,11 +774,11 @@ input[type="file"]::file-selector-button { #image-size-settings li { display: flex; align-items: flex-start; - gap: 12px; - margin: 6px 0; + gap: var(--space-3); + margin: var(--space-2) 0; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); - padding: 8px 12px; + padding: var(--space-2) var(--space-3); } .image-size-thumb { @@ -752,13 +810,20 @@ input[type="file"]::file-selector-button { border: 1px solid var(--outline-variant); border-radius: var(--radius-lg); /* background: var(--primary-container); */ - margin: 10px 15px 10px 0; - /* padding: 4px; */ + margin: var(--space-2) var(--space-4) var(--space-2) 0; + max-width: 100%; + /* Small (e.g. 16x16/32x32) glyphs were rendering at native size, i.e. as + an illegible postage stamp. Enforce a minimum on-screen size and keep + pixel edges crisp when the browser scales the canvas up. */ + min-width: 96px; + min-height: 96px; + image-rendering: crisp-edges; + image-rendering: pixelated; } .inlineImg { border-radius: var(--radius-lg); - margin-top: 8px; + margin-top: var(--space-2); } /* ---- Code output (dark editor look) ---- */ @@ -798,7 +863,7 @@ input[type="file"]::file-selector-button { display: flex; flex-wrap: wrap; align-items: center; - gap: 12px; + gap: var(--space-3); } .sp-block img { @@ -831,6 +896,16 @@ input[type="file"]::file-selector-button { .sidebar-link.active { border-right: none; } + + .settings-preview-row { + flex-direction: column; + } + + .settings-preview-row #step-3 { + position: static; + width: 100%; + flex: none; + } } @media (max-width: 640px) { @@ -844,7 +919,7 @@ input[type="file"]::file-selector-button { .step-num { position: static; - margin-bottom: 16px; + margin-bottom: var(--space-4); } .step::before { @@ -857,7 +932,6 @@ input[type="file"]::file-selector-button { .column-left, .column-right { - float: none; width: 100%; margin-left: 0; } diff --git a/index.html b/index.html index 9bdcf06..58989d3 100644 --- a/index.html +++ b/index.html @@ -41,7 +41,7 @@
      -
      +

      image2cpp

      image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
      It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but @@ -84,6 +84,7 @@

      +
      2

      Image Settings

      @@ -208,6 +209,7 @@
      +
      4
      @@ -364,10 +366,22 @@ // Sidebar scrollspy var links = Array.prototype.slice.call(document.querySelectorAll('.sidebar-link')); var steps = links.map(function (l) { return document.getElementById(l.dataset.step); }); + // Preview (step 3) is pinned beside Settings as a sticky side panel, not + // an independent scroll region - and Chrome updates a sticky element's + // offsetTop to its stuck position once scrolled past, which would + // otherwise make it permanently outrank Settings here. Skip it. + var previewIndex = links.findIndex(function (l) { return l.dataset.step === 'step-3'; }); function onScroll() { var pos = window.scrollY + 120; var active = 0; - steps.forEach(function (s, i) { if (s && s.offsetTop <= pos) active = i; }); + var bestTop = -Infinity; + steps.forEach(function (s, i) { + if (i === previewIndex) return; + if (s && s.offsetTop <= pos && s.offsetTop > bestTop) { + bestTop = s.offsetTop; + active = i; + } + }); links.forEach(function (l, i) { l.classList.toggle('active', i === active); }); steps.forEach(function (s, i) { if (s) s.classList.toggle('active', i === active); }); } From 1a0c7bc30f05732bb2c9c41cc80e40c6c4a6dee3 Mon Sep 17 00:00:00 2001 From: javl Date: Mon, 17 Aug 2026 20:41:16 +0200 Subject: [PATCH 46/63] remove sidebar --- css/style.css | 85 +-------------------------------------------------- index.html | 16 +++------- 2 files changed, 6 insertions(+), 95 deletions(-) diff --git a/css/style.css b/css/style.css index 68741ff..583bb29 100644 --- a/css/style.css +++ b/css/style.css @@ -137,7 +137,7 @@ body { margin: 0; } -/* ---- Layout: sidebar + main ---- */ +/* ---- Layout: main ---- */ .layout { display: flex; @@ -148,65 +148,6 @@ body { align-items: flex-start; } -.sidebar { - position: sticky; - top: 96px; - flex: 0 0 220px; - width: 220px; - background: var(--surface-container-low); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-xl); - /* padding: 24px 0; */ -} - -.sidebar-nav { - display: flex; - flex-direction: column; -} - -.sidebar-link { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-6); - font-family: var(--font-mono); - font-size: 13px; - font-weight: 500; - color: var(--secondary); - border-right: 3px solid transparent; -} - -.sidebar-link:hover { - background: var(--surface-container-high); - text-decoration: none; -} - -.sidebar-link.active { - color: var(--primary); - font-weight: 700; - background: rgba(26, 188, 156, 0.1); - border-right-color: var(--primary); -} - -.sidebar-num { - display: inline-flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - border-radius: 4px; - background: var(--surface-container-high); - color: var(--secondary); - font-size: 12px; - font-weight: 700; - flex-shrink: 0; -} - -.sidebar-link.active .sidebar-num { - background: var(--primary); - color: var(--on-primary); -} - .wrapper { display: flex; flex-direction: column; @@ -877,26 +818,6 @@ input[type="file"]::file-selector-button { flex-direction: column; } - .sidebar { - position: static; - width: 100%; - flex: none; - } - - .sidebar-nav { - flex-direction: row; - flex-wrap: wrap; - } - - .sidebar-link { - border-right: none; - border-radius: var(--radius-lg); - } - - .sidebar-link.active { - border-right: none; - } - .settings-preview-row { flex-direction: column; } @@ -982,7 +903,3 @@ input[type="file"]::file-selector-button { :root[data-theme="dark"] .theme-icon-light { display: inline; } - -:root[data-theme="dark"] .sidebar-link.active { - background: rgba(26, 188, 156, 0.18); -} diff --git a/index.html b/index.html index 58989d3..df319cd 100644 --- a/index.html +++ b/index.html @@ -30,15 +30,7 @@
      - +
      @@ -59,14 +51,16 @@
      1
      -

      Select image

      + +

      Select image

      You can import multiple images at once.
      All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

      Add more images or continue to step 2
      -

      or paste existing byte array

      + +

      Or paste existing byte array


      x From 1845f4ea8180215abe202a31f3261eb4e1e94403 Mon Sep 17 00:00:00 2001 From: javl Date: Mon, 17 Aug 2026 20:45:47 +0200 Subject: [PATCH 47/63] smaller margins --- css/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/style.css b/css/style.css index 583bb29..ca20caf 100644 --- a/css/style.css +++ b/css/style.css @@ -168,7 +168,7 @@ body { .step-num { position: absolute; left: 16px; - top: 32px; + top: var(--space-4); width: 40px; height: 40px; border-radius: 10px; @@ -265,7 +265,7 @@ code { background: var(--surface-container-lowest); border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); - padding: var(--space-7); + padding: var(--space-4); } /* First (intro) section: keep it flush/light */ From d21cc62c34a215c68af5a9a2aa395b3ad58c4d76 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 09:26:39 +0200 Subject: [PATCH 48/63] layout tweaks --- css/style.css | 91 ++++++++++++++++++++++++++++++--------------------- index.html | 87 ++++++++++++++++++++++++------------------------ js/script.js | 2 +- 3 files changed, 99 insertions(+), 81 deletions(-) diff --git a/css/style.css b/css/style.css index ca20caf..90e5084 100644 --- a/css/style.css +++ b/css/style.css @@ -39,6 +39,7 @@ /* Fonts */ --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --font-size: 14px; } * { @@ -49,7 +50,7 @@ body { font-family: var(--font-body); - font-size: 16px; + font-size: var(--font-size); line-height: 24px; color: var(--on-background); background-color: var(--background); @@ -67,7 +68,7 @@ body { } .topnav-inner { - max-width: 1400px; + max-width: 1200px; margin: auto; height: 64px; padding: 0 var(--space-6); @@ -96,7 +97,7 @@ body { .topnav-links a { color: var(--secondary); font-weight: 500; - font-size: 14px; + font-size: var(--font-size); text-decoration: underline; } @@ -142,7 +143,7 @@ body { .layout { display: flex; gap: var(--space-7); - max-width: 1400px; + max-width: 1200px; margin: auto; padding: var(--space-7) var(--space-6) var(--space-9); align-items: flex-start; @@ -353,6 +354,7 @@ code { .table-row { display: table-row; width: 100%; + border-bottom: 1px solid var(--border-subtle); } .table-cell { @@ -370,66 +372,71 @@ code { width: 70%; } -.table-cell:first-child label { +.table-cell:first-child > label { font-family: var(--font-mono); font-size: 13px; - font-weight: 500; + /* font-weight: 500; */ color: var(--primary); } -.table-cell:last-child label { +.table-cell:last-child > label { margin-right: var(--space-2); color: var(--on-surface-variant); - font-size: 14px; + font-size: var(--font-size); } .nested-table { margin: 0; } -/* ---- Settings grid (step 2) ---- - 2-column min-width forces a predictable 2-up (or 1-up) grid instead of - auto-fitting 3 columns, which paired short/tall fields at random and - left ragged whitespace between rows. */ +/* ---- Settings rows (step 2) ---- + Single-column label-left / value-right rows, hairline-divided. Denser + than the old 2-up grid, and keeps each setting's note attached under + its label instead of floating below the control in the value column. */ .settings-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: var(--space-6) var(--space-7); -} - -.settings-grid.table { + display: block; margin: var(--space-2) 0 0; } .settings-grid .table-row { display: flex; - flex-direction: column; - gap: var(--space-2); + align-items: flex-start; + gap: var(--space-6); + padding: var(--space-1) 0; + border-top: 1px solid var(--border-subtle); } -.settings-grid .table-row.full-row { - grid-column: 1 / -1; +.settings-grid .table-row:first-child { + border-top: none; + padding-top: 0; } .settings-grid .table-cell { - display: block; padding: 0; } .settings-grid .table-cell:first-child { - width: auto; - padding-right: 0; + width: 34%; + flex-shrink: 0; } .settings-grid .table-cell:last-child { width: auto; + flex: 1; + min-width: 0; +} + +.settings-grid .table-cell:first-child .note { + font-size: 13px; + line-height: 1.45em; + margin: var(--space-1) 0 0; } .nested-table .table-cell { color: var(--text-muted); font-size: 0.9em; - width: 200px; + /* width: 200px; */ } #format-caption-container { @@ -442,14 +449,14 @@ code { /* Reserve space for the tallest combination (Adafruit GFX shows both its own fields and the Arduino identifier fields) so the Generate/Copy/ Download buttons don't jump around when switching output format. */ - min-height: 278px; + /* min-height: 278px; */ transition: min-height 0.15s ease; } #format-caption-container div { color: var(--text-muted); display: none; - font-size: 14px; + font-size: var(--font-size); line-height: 1.5em; padding: var(--space-2) 0 var(--space-4); width: 100%; @@ -459,7 +466,7 @@ code { .note { color: var(--text-muted); - font-size: 14px; + font-size: var(--font-size); line-height: 1.5em; margin: var(--space-1) 0; } @@ -468,9 +475,9 @@ code { display: none; } -.upload-note { +/* .upload-note { max-width: 320px; -} +} */ #continue-note { display: none; @@ -494,7 +501,7 @@ input[type="number"], textarea, select { font-family: var(--font-body); - font-size: 14px; + font-size: var(--font-size); color: var(--on-surface-variant); background: var(--surface-container); border: 1px solid transparent; @@ -530,13 +537,13 @@ input[type="radio"] { min-width: 360px; width: 100%; font-family: var(--font-mono); - font-size: 14px; + font-size: var(--font-size); line-height: 22px; resize: vertical; } #text-input-error { color: var(--error); - font-size: 14px; + font-size: var(--font-size); margin-top: var(--space-2); display: none; } @@ -558,7 +565,7 @@ input[type="radio"] { .text-input-size { margin: var(--space-2) 0; color: var(--on-surface-variant); - font-size: 14px; + font-size: var(--font-size); } .size-input { @@ -577,7 +584,10 @@ input[type="radio"] { } .sub-field-label { - min-width: 160px; + min-width: 200px; + font-family: var(--font-mono); + font-size: 13px; + color: var(--primary); } .glyph-input { @@ -618,7 +628,7 @@ input[type="file"] { color: var(--on-surface-variant); /* border: 2px dashed var(--outline-variant); */ border-radius: var(--radius-lg); - font-size: 14px; + font-size: var(--font-size); padding: 16px 20px; margin: var(--space-2) 0 0; cursor: pointer; @@ -778,7 +788,7 @@ input[type="file"]::file-selector-button { border: 1px solid var(--on-surface-variant); border-radius: var(--radius-xl); font-family: var(--font-mono); - font-size: 14px; + font-size: var(--font-size); line-height: 22px; padding: 20px; resize: vertical; @@ -870,6 +880,11 @@ input[type="file"]::file-selector-button { .table-cell:first-child { padding-bottom: 0; } + + .settings-grid .table-row { + flex-direction: column; + gap: var(--space-1); + } } /* ---- Dark theme ---- */ diff --git a/index.html b/index.html index df319cd..e559b2a 100644 --- a/index.html +++ b/index.html @@ -85,7 +85,7 @@
      -
      +
        @@ -99,7 +99,10 @@
        -
        +
        + +
        Used for parts of the canvas that are not covered by your image.
        +
        @@ -107,43 +110,11 @@ -
        - Used for parts of the canvas that are not covered by your image. -
        -
        -
        - -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -
        - -
        - 0 - 255; if the brightness of a pixel is above the given level the pixel becomes white, otherwise they become black. When using alpha, opaque and transparent are used instead. Default is 128. -
        -
        -
        - -
        -
        +
        +
        +
        + +
        +
        +
        Different dithering algorithms produce different results.
        +
        +
        + +
        +
        + +
        +
        + +
        0 - 255; if the brightness of a pixel is above the given level the pixel becomes white, otherwise they become black. When using alpha, opaque and transparent are used instead. Default is 128.
        +
        +
        + +
        +
        + +
        +
        + +
        Centering the image only works when using a canvas larger than the original image.
        +
        -
        - Centering the image only works when using a canvas larger than the original image. -
        @@ -288,7 +290,7 @@
        - Uses uint8_t instead of unsigned char and drops PROGMEM, which is a no-op on ESP32. + Use uint8_t instead of unsigned char and drops PROGMEM, which is a no-op on ESP32.
        @@ -323,10 +325,11 @@ Set to 0 to wrap using the image's width instead, e.g. an 8px wide image adds a newline every 8th byte.
        + - - Adds a dot/hash ascii-art comment after each row of bytes. Forces one image row per output line, regardless of "Bytes per row". + Add an ascii-art comment after each row of bytes. Forces one image row per output line, regardless of "Bytes per row".
        +
        diff --git a/js/script.js b/js/script.js index 4a2ddb5..97b5b94 100644 --- a/js/script.js +++ b/js/script.js @@ -16,7 +16,7 @@ const settings = { drawMode: 'horizontal', prefix: '0x', ditheringThreshold: 128, - ditheringMode: 2, + ditheringMode: 0, outputFormat: 'plain', separator: ', ', outputComments: true, From 75acd49f7f3923032268044cde09df92f0a74341 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 09:37:24 +0200 Subject: [PATCH 49/63] prevent css distorting preview sizes --- css/style.css | 8 +++----- js/script.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/css/style.css b/css/style.css index 90e5084..380098e 100644 --- a/css/style.css +++ b/css/style.css @@ -763,11 +763,9 @@ input[type="file"]::file-selector-button { /* background: var(--primary-container); */ margin: var(--space-2) var(--space-4) var(--space-2) 0; max-width: 100%; - /* Small (e.g. 16x16/32x32) glyphs were rendering at native size, i.e. as - an illegible postage stamp. Enforce a minimum on-screen size and keep - pixel edges crisp when the browser scales the canvas up. */ - min-width: 96px; - min-height: 96px; + /* Display width/height/aspect-ratio are set in JS (setCanvasDisplaySize) + so small glyphs get enlarged without distorting their aspect ratio; + independent min-width/min-height here would stretch non-square canvases. */ image-rendering: crisp-edges; image-rendering: pixelated; } diff --git a/js/script.js b/js/script.js index 97b5b94..4fe676f 100644 --- a/js/script.js +++ b/js/script.js @@ -259,6 +259,19 @@ function invert(canvas, ctx) { ctx.putImageData(imageData, 0, 0); } +// Size the on-screen canvas so tiny glyphs stay legible without distorting +// their aspect ratio (independent min-width/min-height would stretch them). +function setCanvasDisplaySize(canvas) { + const minVisibleSize = 96; + const scale = Math.max(1, minVisibleSize / Math.min(canvas.width, canvas.height)); + // eslint-disable-next-line no-param-reassign + canvas.style.aspectRatio = `${canvas.width} / ${canvas.height}`; + // eslint-disable-next-line no-param-reassign + canvas.style.width = `${Math.round(canvas.width * scale)}px`; + // eslint-disable-next-line no-param-reassign + canvas.style.height = 'auto'; +} + // Draw the image onto the canvas, taking into account color and scaling function placeImage(_image) { const { img } = _image; @@ -439,6 +452,8 @@ function placeImage(_image) { ctx.drawImage(clone, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0); } + + setCanvasDisplaySize(canvas); } // Handle drawing each of our images From e729e47292b8ed325469fbe97044e56ad5be8c5f Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 09:52:37 +0200 Subject: [PATCH 50/63] add option to output entire example script --- index.html | 59 +++++++++++++++++++++++++++++----------------------- js/script.js | 53 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/index.html b/index.html index e559b2a..9d62e13 100644 --- a/index.html +++ b/index.html @@ -86,15 +86,16 @@
        -
        +
        +
        + Glyph name is used to identify the image in the code. +
        +
          Only images file type are allowed
          Select one or more images at step 1 first.
          -
          - Glyph name is used to identify the image in the code. -
          @@ -126,6 +127,19 @@
          +
          +
          + +
          Centering the image only works when using a canvas larger than the original image.
          +
          +
          + + + + +
          +
          +
          @@ -157,18 +171,6 @@
          -
          -
          - -
          Centering the image only works when using a canvas larger than the original image.
          -
          -
          - - - - -
          -
          @@ -225,10 +227,10 @@
          If your image looks all messed up on your display, like the image below, try using a different mode. -
          +
          @@ -236,14 +238,12 @@
          - - - - - - - - +
          Outputs just the raw bytes of the image(s). @@ -286,11 +286,18 @@
          +
          +
          +
          + + Include a full example displaying first image. +
          +
          - Use uint8_t instead of unsigned char and drops PROGMEM, which is a no-op on ESP32. + Use uint8_t instead of unsigned char and PROGMEM.
          diff --git a/js/script.js b/js/script.js index 4fe676f..73b9fe8 100644 --- a/js/script.js +++ b/js/script.js @@ -23,6 +23,7 @@ const settings = { invertColors: false, rotation: 0, esp32Format: false, + fullExample: false, bytesPerRow: 16, showAsciiPreview: false, }; @@ -959,6 +960,41 @@ function glyphComment(image, indent = '') { return `${indent}// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; } +// Wraps generated bitmap array code in a full Arduino sketch, based on +// oled_example/oled_example.ino, that compiles against the Adafruit +// SSD1306/GFX libraries and draws the first bitmap on the display. +function wrapFullExample(arrayCode, varName, width, height) { + return `#include +#include +#include +#include + +#define OLED_RESET 4 +Adafruit_SSD1306 display(OLED_RESET); + +#if (SSD1306_LCDHEIGHT != ${settings.screenHeight}) +#error("Height incorrect, please fix Adafruit_SSD1306.h!"); +#endif + +${arrayCode} +void setup() { + display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64) + + display.clearDisplay(); // Make sure the display is cleared + // Draw the bitmap: + // drawBitmap(x position, y position, bitmap data, bitmap width, bitmap height, color) + display.drawBitmap(0, 0, ${varName}, ${width}, ${height}, WHITE); + + // Update the display + display.display(); +} + +void loop() { + +} +`; +} + // Removes the trailing separator comma left after the last byte value. // When settings.showAsciiPreview is on, the string always ends with a // dot/hash ascii-art comment line instead (see makeAsciiRowTracker), so the @@ -1009,6 +1045,12 @@ function generateOutputString() { } outputString += `const int ${getIdentifier()}allArray_LEN = ${varQuickArray.length};\n`; outputString += `const ${getImageType()}* ${getIdentifier()}allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; + + if (settings.fullExample) { + const first = images.first(); + const firstVarName = getIdentifier() + (first.glyph ? `${first.glyph.replace(/[^a-zA-Z0-9]/g, '_')}` : ''); + outputString = wrapFullExample(outputString, firstVarName, first.canvas.width, first.canvas.height); + } break; } @@ -1027,6 +1069,11 @@ function generateOutputString() { getIdentifier() } []${progmemKeyword()} = {` + `\n${outputString}\n};`; + + if (settings.fullExample) { + const first = images.first(); + outputString = wrapFullExample(outputString, getIdentifier(), first.canvas.width, first.canvas.height); + } break; } @@ -1219,7 +1266,7 @@ window.onload = () => { const fileInput = document.getElementById('file-input'); fileInput.addEventListener('click', () => { this.value = null; }, false); fileInput.addEventListener('change', handleImageSelection, false); - const outputFormatArduino = document.getElementById('outputFormatArduino'); - outputFormatArduino.checked = true; - outputFormatArduino.onchange(); + const outputFormat = document.getElementById('outputFormat'); + outputFormat.value = 'arduino'; + updateOutputFormat(outputFormat); }; From f3bcff11f51ce40b73fcf33fdddc6639192cbfc7 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 10:01:07 +0200 Subject: [PATCH 51/63] update layout --- css/style.css | 10 +++++----- index.html | 19 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/css/style.css b/css/style.css index 380098e..5784869 100644 --- a/css/style.css +++ b/css/style.css @@ -364,7 +364,7 @@ code { } .table-cell:first-child { - width: 30%; + width: 20%; padding-right: var(--space-4); } @@ -417,7 +417,7 @@ code { } .settings-grid .table-cell:first-child { - width: 34%; + width: 250px; flex-shrink: 0; } @@ -439,11 +439,11 @@ code { /* width: 200px; */ } -#format-caption-container { +/* #format-caption-container { */ /* Reserve space for the longest caption so switching output format doesn't shift the fields/buttons below it. */ - min-height: 100px; -} + /* min-height: 100px; */ +/* } */ #extra-settings-container { /* Reserve space for the tallest combination (Adafruit GFX shows both its diff --git a/index.html b/index.html index 9d62e13..d0721c4 100644 --- a/index.html +++ b/index.html @@ -227,7 +227,7 @@
          If your image looks all messed up on your display, like the image below, try using a different mode. -
          @@ -246,20 +246,19 @@
          - Outputs just the raw bytes of the image(s). + Outputs just the raw bytes of the image(s).
          - Adds some extra Arduino code around the output for easy copy-paste into - this example. - If multiple images are loaded, generates a byte array for each and appends a counter to the identifier. + Adds some extra Arduino code around the output. + If multiple images are loaded, generates a byte array for each and appends a counter to the identifier.
          - Adds some extra Arduino code around the output for easy copy-paste. - If multiple images are loaded, generates a single byte array. + Adds some extra Arduino code around the output. + If multiple images are loaded, generates a single byte array.
          - Creates a GFXbitmapFont formatted ouput. Used by a modified version of the Adafruit GFX library. - GitHub project and example here. + Creates a GFXbitmapFont formatted ouput. Used by a modified version of the Adafruit GFX library. + GitHub project and example here.
          First ASCII character value is used only if a glyph identifier of length equal to 1 is not provided for each image. The value itself will be incremented by 1 for each glyph.
          @@ -290,7 +289,7 @@
          - Include a full example displaying first image. + Include a full example displaying the first image.
          From ee8f22c588cd16dd0ce22c113e50a8b0e9fc0ffc Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 10:23:02 +0200 Subject: [PATCH 52/63] Use template for image entry --- css/style.css | 40 +++++++++++++++++++++++++------------- index.html | 18 +++++++++++++++++ js/script.js | 54 +++++++++------------------------------------------ 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/css/style.css b/css/style.css index 5784869..c227e42 100644 --- a/css/style.css +++ b/css/style.css @@ -469,6 +469,7 @@ code { font-size: var(--font-size); line-height: 1.5em; margin: var(--space-1) 0; + font-style: italic; } #glyph-name-note { @@ -686,6 +687,8 @@ input[type="file"]::file-selector-button { /* ---- File info / size list ---- */ .file-input-entry { + border: 1px solid var(--border-subtle); + padding: var(--space-1); display: flex; align-items: center; gap: var(--space-2); @@ -700,17 +703,17 @@ input[type="file"]::file-selector-button { .file-info { color: var(--text-muted); font-size: 12px; - margin-left: var(--space-5); - max-width: 320px; - white-space: pre; } .file-name { - display: block; - margin-left: 0; - margin-bottom: var(--space-2); + flex: 1; + min-width: 0; + margin: 0; font-weight: 500; color: var(--on-surface-variant); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } /* Tighten gap between a row and the explanatory note row that follows */ @@ -724,19 +727,25 @@ input[type="file"]::file-selector-button { #image-size-settings li { display: flex; - align-items: flex-start; - gap: var(--space-3); + flex-direction: column; + gap: var(--space-2); margin: var(--space-2) 0; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: var(--space-2) var(--space-3); } +.image-size-thumb-container { + display: flex; + align-items: center; + gap: var(--space-2); +} + .image-size-thumb { width: auto; height: auto; - max-width: 100px; - max-height: 100px; + max-width: 48px; + max-height: 48px; border: 1px solid var(--outline-variant); border-radius: var(--radius-md, 4px); background: var(--surface-variant, #fff); @@ -744,9 +753,14 @@ input[type="file"]::file-selector-button { vertical-align: middle; } -.image-size-details { - flex: 1; - min-width: 0; +.image-size-row { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.image-size-header { + justify-content: space-between; } /* ---- Preview canvases ---- */ diff --git a/index.html b/index.html index d0721c4..df8bb89 100644 --- a/index.html +++ b/index.html @@ -93,6 +93,24 @@
            +
            Only images file type are allowed
            Select one or more images at step 1 first.
            diff --git a/js/script.js b/js/script.js index 73b9fe8..fd176da 100644 --- a/js/script.js +++ b/js/script.js @@ -813,20 +813,16 @@ function handleImageSelection(evt) { const canvas = document.createElement('canvas'); - const imageEntry = document.createElement('li'); + const entryTemplate = document.getElementById('image-size-entry-template'); + const entryFragment = entryTemplate.content.cloneNode(true); + const imageEntry = entryFragment.querySelector('li'); imageEntry.setAttribute('data-img', file.name); - const thumb = document.createElement('img'); - thumb.className = 'image-size-thumb'; + const thumb = imageEntry.querySelector('.image-size-thumb'); thumb.src = img.src; thumb.alt = file.name; - const w = document.createElement('input'); - w.type = 'number'; - w.name = 'width'; - w.id = 'screenWidth'; - w.min = 1; - w.className = 'size-input'; + const w = imageEntry.querySelector('.js-width'); w.value = img.width; w.oninput = () => { const image = images.get(img); @@ -834,12 +830,7 @@ function handleImageSelection(evt) { placeImage(image); }; - const h = document.createElement('input'); - h.type = 'number'; - h.name = 'height'; - h.id = 'screenHeight'; - h.min = 1; - h.className = 'size-input'; + const h = imageEntry.querySelector('.js-height'); h.value = img.height; h.oninput = () => { const image = images.get(img); @@ -847,14 +838,7 @@ function handleImageSelection(evt) { placeImage(image); }; - const gil = document.createElement('span'); - gil.innerHTML = 'glyph name'; - gil.className = 'file-info'; - - const gi = document.createElement('input'); - gi.type = 'text'; - gi.name = 'glyph'; - gi.className = 'glyph-input'; + const gi = imageEntry.querySelector('.js-glyph'); const [fileName] = file.name.split('.'); gi.value = fileName; gi.onchange = () => { @@ -887,16 +871,8 @@ function handleImageSelection(evt) { checkImagesAvailable(); }; - const rb = document.createElement('button'); - rb.className = 'remove-button'; - rb.innerHTML = 'remove image'; - rb.onclick = removeButtonOnClick; - - const fn = document.createElement('span'); - fn.className = 'file-info file-name'; - fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; - - fn.appendChild(rb); + imageEntry.querySelector('.js-file-name').textContent = `${file.name} (file resolution: ${img.width} x ${img.height})`; + imageEntry.querySelector('.js-remove').onclick = removeButtonOnClick; fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; @@ -905,18 +881,6 @@ function handleImageSelection(evt) { fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); fileInputColumn.appendChild(fileInputColumnEntry); - const imageEntryDetails = document.createElement('div'); - imageEntryDetails.className = 'image-size-details'; - imageEntryDetails.appendChild(fn); - imageEntryDetails.appendChild(w); - imageEntryDetails.appendChild(document.createTextNode(' x ')); - imageEntryDetails.appendChild(h); - imageEntryDetails.appendChild(gil); - imageEntryDetails.appendChild(gi); - - imageEntry.appendChild(thumb); - imageEntry.appendChild(imageEntryDetails); - imageSizeSettings.appendChild(imageEntry); canvas.width = img.width; From 63faf1b69962ebcaa2c413058cceaf2c9b05b55a Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 10:37:20 +0200 Subject: [PATCH 53/63] fix tests --- tests/test_conversions.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 7f9ac36..aed12b3 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -48,13 +48,8 @@ BASE_SETTINGS = { "esp32Format": False, "bytesPerRow": 16, "showAsciiPreview": False, -} - -OUTPUT_FORMAT_IDS = { - "plain": "outputFormatPlain", - "arduino": "outputFormatArduino", - "arduino_single": "outputFormatArduinoSingle", - "adafruit_gfx": "outputFormatAdafruitGfx", + "outputComments": True, + "fullExample": False, } # (scenario name, overrides on top of BASE_SETTINGS, optional canvas resize) @@ -206,9 +201,11 @@ def expected_bin_bytes(output, draw_mode): def reset_canvas_size(page, width, height): - """Set canvas size through the real width/height text inputs.""" - page.fill("#screenWidth", str(width)) - page.fill("#screenHeight", str(height)) + """Set canvas size through the first image's per-image width/height + inputs in the "Canvas size(s)" list (each image has its own size in the + current layout; there's no single global canvas size control anymore).""" + page.fill("#image-size-settings li:nth-child(1) input[name='width']", str(width)) + page.fill("#image-size-settings li:nth-child(1) input[name='height']", str(height)) def apply_settings(page, settings): @@ -216,7 +213,7 @@ def apply_settings(page, settings): # Selecting an output format resets prefix/separator to their defaults # (updateOutputFormat() in script.js), so this must run before those two # fields are filled in below. - page.check(f"#{OUTPUT_FORMAT_IDS[settings['outputFormat']]}") + page.select_option("#outputFormat", settings["outputFormat"]) page.check(f"#backgroundColor{settings['backgroundColor'].capitalize()}") set_checkbox(page, "#invertColors", settings["invertColors"]) page.select_option("#ditheringMode", str(settings["ditheringMode"])) @@ -232,10 +229,13 @@ def apply_settings(page, settings): page.fill("#separator", settings["separator"]) page.fill("#bytesPerRow", str(settings["bytesPerRow"])) set_checkbox(page, "#showAsciiPreview", settings["showAsciiPreview"]) - # esp32Format checkbox only exists in the DOM (visible) for non-plain - # output formats; plain output has no type/PROGMEM declarations to affect. + set_checkbox(page, "#outputComments", settings["outputComments"]) + # esp32Format/fullExample checkboxes only exist in the DOM (visible) for + # non-plain output formats; plain output has no type/PROGMEM/example code + # for them to affect. if settings["outputFormat"] != "plain": set_checkbox(page, "#esp32Format", settings["esp32Format"]) + set_checkbox(page, "#fullExample", settings["fullExample"]) def set_checkbox(page, selector, checked): @@ -285,7 +285,7 @@ def run_roundtrip(page, output, draw_mode, width, height): page.goto(INDEX_HTML.as_uri()) # window.onload defaults outputFormat to "arduino"; force it back to plain # to match the format the scenario was exported in. - page.check(f"#{OUTPUT_FORMAT_IDS['plain']}") + page.select_option("#outputFormat", "plain") page.fill("#byte-input", output) page.fill("#text-input-width", str(width)) page.fill("#text-input-height", str(height)) From 95273b06c7663f9526f5a5a272bb1a7d8bbc47c4 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 10:37:40 +0200 Subject: [PATCH 54/63] move some JS to template instead --- index.html | 7 +++++++ js/script.js | 18 ++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index df8bb89..6905f86 100644 --- a/index.html +++ b/index.html @@ -56,6 +56,13 @@
            You can import multiple images at once.
            All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

            +
            Add more images or continue to step 2
            diff --git a/js/script.js b/js/script.js index fd176da..86538e0 100644 --- a/js/script.js +++ b/js/script.js @@ -796,20 +796,17 @@ function handleImageSelection(evt) { const img = new Image(); img.onload = () => { - const fileInputColumnEntry = document.createElement('div'); - fileInputColumnEntry.className = 'file-input-entry'; + const fileInputEntryTemplate = document.getElementById('file-input-entry-template'); + const fileInputEntryFragment = fileInputEntryTemplate.content.cloneNode(true); + const fileInputColumnEntry = fileInputEntryFragment.querySelector('.file-input-entry'); - const fileInputColumnEntryThumb = document.createElement('img'); - fileInputColumnEntryThumb.className = 'image-size-thumb'; + const fileInputColumnEntryThumb = fileInputColumnEntry.querySelector('.image-size-thumb'); fileInputColumnEntryThumb.src = img.src; fileInputColumnEntryThumb.alt = file.name; - const fileInputColumnEntryLabel = document.createElement('span'); - fileInputColumnEntryLabel.textContent = file.name; + fileInputColumnEntry.querySelector('.js-label').textContent = file.name; - const fileInputColumnEntryRemoveButton = document.createElement('button'); - fileInputColumnEntryRemoveButton.className = 'remove-button'; - fileInputColumnEntryRemoveButton.innerHTML = 'remove image'; + const fileInputColumnEntryRemoveButton = fileInputColumnEntry.querySelector('.js-remove'); const canvas = document.createElement('canvas'); @@ -876,9 +873,6 @@ function handleImageSelection(evt) { fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; - fileInputColumnEntry.appendChild(fileInputColumnEntryThumb); - fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); - fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); fileInputColumn.appendChild(fileInputColumnEntry); imageSizeSettings.appendChild(imageEntry); From 34decab5c02bbccfe29949c69c9140c3e6e9f740 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 10:42:06 +0200 Subject: [PATCH 55/63] update labels / notes --- index.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 6905f86..174aba4 100644 --- a/index.html +++ b/index.html @@ -166,15 +166,14 @@
            -
            +
            -
            -
            Different dithering algorithms produce different results.
            +
            From 324955ed7f5b49c546846a1f0d52646cf2f7f35e Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 11:11:41 +0200 Subject: [PATCH 56/63] update layout and descriptions --- css/style.css | 42 ++++++++++++++++++++++++++++++------------ index.html | 15 +++++++-------- js/script.js | 2 +- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/css/style.css b/css/style.css index c227e42..5fece95 100644 --- a/css/style.css +++ b/css/style.css @@ -11,9 +11,12 @@ --surface-container-low: #f2f4f6; --surface-container: #eceef0; --surface-container-high: #e6e8ea; - --on-surface: #191c1e; - --on-surface-variant: #3c4a45; - --on-background: #191c1e; + /* --on-surface: #191c1e; */ + --on-surface: #000000; + /* --on-surface-variant: #3c4a45; */ + --on-surface-variant: #000000; + /* --on-background: #191c1e; */ + --on-background: #000000; --text-muted: #64748b; --border-subtle: #e2e8f0; --outline-variant: #bbcac3; @@ -145,7 +148,8 @@ body { gap: var(--space-7); max-width: 1200px; margin: auto; - padding: var(--space-7) var(--space-6) var(--space-9); + /* padding: var(--space-7) var(--space-6) var(--space-9); */ + padding: 0 var(--space-6) var(--space-9); align-items: flex-start; } @@ -221,6 +225,13 @@ h1 { margin-bottom: var(--space-2); } +.v2 { + font-size: 18px; + line-height: 22px; + font-weight: 500; + color: var(--secondary); +} + h2 { font-size: 20px; line-height: 28px; @@ -244,12 +255,12 @@ p { a { color: var(--primary); - text-decoration: none; + text-decoration: underline; font-weight: 500; } a:hover { - text-decoration: underline; + text-decoration: none; } code { @@ -266,18 +277,25 @@ code { background: var(--surface-container-lowest); border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); - padding: var(--space-4); + padding: var(--space-2); } /* First (intro) section: keep it flush/light */ .section.first-step { background: transparent; border: none; - padding: var(--space-2) 0 0; + /* padding: var(--space-5) 0 0; */ + /* border: 10px solid red; */ + padding: 0; +} + +.section.first-step p { + margin-top: 0; + padding-top: 0; } .section h2 { - margin-bottom: var(--space-4); + margin-bottom: var(--space-1); } /* Legacy divider no longer needed; cards separate content */ @@ -346,7 +364,7 @@ code { .table { display: table; - margin: var(--space-2) 0 0; + /* margin: var(--space-2) 0 0; */ width: 100%; border-collapse: collapse; } @@ -507,7 +525,7 @@ select { background: var(--surface-container); border: 1px solid transparent; border-radius: var(--radius-lg); - padding: 8px 10px; + padding: 4px 4px; transition: border-color 0.15s ease, box-shadow 0.15s ease; } @@ -630,7 +648,7 @@ input[type="file"] { /* border: 2px dashed var(--outline-variant); */ border-radius: var(--radius-lg); font-size: var(--font-size); - padding: 16px 20px; + padding: 4px 4px; margin: var(--space-2) 0 0; cursor: pointer; width: 100%; diff --git a/index.html b/index.html index 174aba4..e6ba24b 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@
            - image2cpp + image2cpp v2 @@ -34,7 +34,6 @@
            -

            image2cpp

            image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
            It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but has been expanded by the community to be useful in all kind of (embedded) projects.

            @@ -95,7 +94,7 @@
            - Glyph name is used to identify the image in the code. + Glyph name is used to identify the image in the output code.
            @@ -107,13 +106,13 @@
            -
            +
            Canvas size: x
            - glyph name + glyph name:
            @@ -274,7 +273,7 @@
            Adds some extra Arduino code around the output. - If multiple images are loaded, generates a byte array for each and appends a counter to the identifier. + If multiple images are loaded, generates a byte array for each and appends its glyph name (from step 2 above) to the identifier.
            Adds some extra Arduino code around the output. @@ -313,14 +312,14 @@
            - Include a full example displaying the first image. +
            - Use uint8_t instead of unsigned char and PROGMEM. +
            diff --git a/js/script.js b/js/script.js index 86538e0..90190e4 100644 --- a/js/script.js +++ b/js/script.js @@ -868,7 +868,7 @@ function handleImageSelection(evt) { checkImagesAvailable(); }; - imageEntry.querySelector('.js-file-name').textContent = `${file.name} (file resolution: ${img.width} x ${img.height})`; + imageEntry.querySelector('.js-file-name').textContent = `${file.name} (image size: ${img.width} x ${img.height} px)`; imageEntry.querySelector('.js-remove').onclick = removeButtonOnClick; fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; From 7e2efdb4978fec582179b3c884790d2f9b9ee3c7 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 11:15:20 +0200 Subject: [PATCH 57/63] update border colors --- css/style.css | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/css/style.css b/css/style.css index 5fece95..3098065 100644 --- a/css/style.css +++ b/css/style.css @@ -19,6 +19,7 @@ --on-background: #000000; --text-muted: #64748b; --border-subtle: #e2e8f0; + --border-hard: #1abc9c; --outline-variant: #bbcac3; --editor-bg: #ffffff; --error: #ba1a1a; @@ -183,7 +184,7 @@ body { font-weight: 700; background: var(--surface-container-high); color: var(--secondary); - border: 1px solid var(--border-subtle); + border: 1px solid var(--border-hard); z-index: 2; } @@ -200,7 +201,7 @@ body { top: 40px; bottom: -24px; width: 2px; - background: var(--border-subtle); + background: var(--border-hard); z-index: 1; } @@ -275,7 +276,7 @@ code { .section { background: var(--surface-container-lowest); - border: 1px solid var(--border-subtle); + border: 1px solid var(--border-hard); border-radius: var(--radius-xl); padding: var(--space-2); } @@ -299,10 +300,10 @@ code { } /* Legacy divider no longer needed; cards separate content */ -.bottom-divider { - border-bottom: none; - padding-bottom: var(--space-7); -} +/* .bottom-divider { */ + /* border-bottom: none; */ + /* padding-bottom: var(--space-7); */ +/* } */ .sub-section { clear: both; From c898042f210a156158a2b86f7cd2169f53f64071 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 11:17:51 +0200 Subject: [PATCH 58/63] make intro easier to read --- css/style.css | 3 +-- index.html | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/css/style.css b/css/style.css index 3098065..1acc165 100644 --- a/css/style.css +++ b/css/style.css @@ -149,8 +149,7 @@ body { gap: var(--space-7); max-width: 1200px; margin: auto; - /* padding: var(--space-7) var(--space-6) var(--space-9); */ - padding: 0 var(--space-6) var(--space-9); + padding: var(--space-7) var(--space-6) var(--space-9); align-items: flex-start; } diff --git a/index.html b/index.html index e6ba24b..3be61f7 100644 --- a/index.html +++ b/index.html @@ -34,16 +34,22 @@
            -

            image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
            - It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but - has been expanded by the community to be useful in all kind of (embedded) projects.

            -

            More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

            -

            - Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially. -

            -

            - GitHub Sponsor BMC -

            +
            +
            +

            image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
            + It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but + has been expanded by the community to be useful in all kind of (embedded) projects.

            +
            +
            +

            More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

            +

            + Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially. +

            +

            + GitHub Sponsor BMC +

            +
            +
            From 3c71d4fd5139206f183cd1b77e748c885a731546 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 13:55:35 +0200 Subject: [PATCH 59/63] Remove some unneeded clutter --- css/style.css | 23 ++++++++++++----------- index.html | 17 ++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/css/style.css b/css/style.css index 1acc165..2480a87 100644 --- a/css/style.css +++ b/css/style.css @@ -165,7 +165,8 @@ body { .section.step { position: relative; - padding-left: 72px; + /* padding-left: 72px; */ + padding: var(--space-4); scroll-margin-top: 88px; } @@ -193,7 +194,7 @@ body { border-color: var(--primary); } -.step::before { +/* .step::before { content: ""; position: absolute; left: 35px; @@ -202,17 +203,17 @@ body { width: 2px; background: var(--border-hard); z-index: 1; -} +} */ -#step-0.step::before, #step-4.step::before { +/* #step-0.step::before, #step-4.step::before { display: none; -} +} */ /* step-3 (Preview) lives as a pinned side panel next to Settings, not inline in the vertical step flow, so it doesn't carry a connecting rail */ -#step-3.step::before { +/* #step-3.step::before { display: none; -} +} */ /* ---- Typography ---- */ @@ -874,18 +875,18 @@ input[type="file"]::file-selector-button { display: none; } - .section.step { + /* .section.step { padding-left: 32px; - } + } */ .step-num { position: static; margin-bottom: var(--space-4); } - .step::before { + /* .step::before { display: none; - } + } */ .input-methods-row { flex-direction: column; diff --git a/index.html b/index.html index 3be61f7..4a95aec 100644 --- a/index.html +++ b/index.html @@ -53,11 +53,10 @@
            -
            1
            +
            - -

            Select image

            +

            1. Select image

            You can import multiple images at once.
            All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

            @@ -72,7 +71,7 @@
            -

            Or paste existing byte array

            +

            1. Paste existing byte array


            x @@ -92,8 +91,8 @@
            -
            2
            -

            Image Settings

            + +

            2. Image Settings

            @@ -229,7 +228,7 @@
            -
            3
            +

            Preview

            No files selected
            @@ -239,8 +238,8 @@
            -
            4
            -

            Output

            + +

            3. Output

            From 280457f0424f09d01d97f4190c27ae610613168c Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 14:00:08 +0200 Subject: [PATCH 60/63] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9a2257..97cb299 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "image2cpp", - "version": "1.0.0", + "version": "2.0.0", "description": "Use this tool online at http://javl.github.io/image2cpp/ image2cpp is a simple tool to change images into byte arrays (or your array back into an image) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution.", "main": "dithering.js", "scripts": { From a1dc4b05618f40b57ac8d6ec3662dd9eb0fc6230 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 14:27:37 +0200 Subject: [PATCH 61/63] update text --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 4a95aec..f058c87 100644 --- a/index.html +++ b/index.html @@ -36,9 +36,9 @@
            -

            image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.
            +

            image2cpp is a simple tool to convert images into byte arrays (and vice versa) for use with small (monochrome) displays such as OLEDs on your Arduino or ESP.
            It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch here) but - has been expanded by the community to be useful in all kind of (embedded) projects.

            + has been expanded by the community.

            More info (and credits) can be found in the Github repository. This is also where you can report any issues you might come across.

            From fe7017e14c421191d81e9940dcc4bdba16052a56 Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 14:52:49 +0200 Subject: [PATCH 62/63] update example script --- js/script.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/script.js b/js/script.js index 90190e4..9c6ae08 100644 --- a/js/script.js +++ b/js/script.js @@ -936,7 +936,9 @@ Adafruit_SSD1306 display(OLED_RESET); ${arrayCode} void setup() { - display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64) + // initialize with the I2C addr 0x3D for the 128x64 + // replace with 0x3C for the 128x32 + display.begin(SSD1306_SWITCHCAPVCC, 0x3D); display.clearDisplay(); // Make sure the display is cleared // Draw the bitmap: From 5e1d70b54ebcf190f36e52444c65bd498797e84e Mon Sep 17 00:00:00 2001 From: javl Date: Tue, 18 Aug 2026 15:22:31 +0200 Subject: [PATCH 63/63] Add option to view preview image in separate tab --- css/style.css | 11 ++++++--- index.html | 3 ++- js/script.js | 62 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/css/style.css b/css/style.css index 2480a87..35a8da2 100644 --- a/css/style.css +++ b/css/style.css @@ -796,13 +796,18 @@ input[type="file"]::file-selector-button { /* background: var(--primary-container); */ margin: var(--space-2) var(--space-4) var(--space-2) 0; max-width: 100%; - /* Display width/height/aspect-ratio are set in JS (setCanvasDisplaySize) - so small glyphs get enlarged without distorting their aspect ratio; - independent min-width/min-height here would stretch non-square canvases. */ + cursor: pointer; + /* Display width/height are set in JS (setCanvasDisplaySize), capped to + 128px on the largest side while preserving aspect ratio, and never + scaled up beyond the canvas' actual pixel size. */ image-rendering: crisp-edges; image-rendering: pixelated; } +#preview-note { + display: none; +} + .inlineImg { border-radius: var(--radius-lg); margin-top: var(--space-2); diff --git a/index.html b/index.html index f058c87..b2ae168 100644 --- a/index.html +++ b/index.html @@ -59,6 +59,7 @@

            1. Select image

            You can import multiple images at once.
            All processing is done locally in your browser: your images are not uploaded or stored anywhere online.

            +
            Only images file type are allowed
            -
            Only images file type are allowed
            Select one or more images at step 1 first.
            @@ -230,6 +230,7 @@

            Preview

            +
            Click image to view at actual size
            No files selected
            diff --git a/js/script.js b/js/script.js index 9c6ae08..db2e4bf 100644 --- a/js/script.js +++ b/js/script.js @@ -260,17 +260,32 @@ function invert(canvas, ctx) { ctx.putImageData(imageData, 0, 0); } -// Size the on-screen canvas so tiny glyphs stay legible without distorting -// their aspect ratio (independent min-width/min-height would stretch them). +// Open the canvas' current contents as a full-size image in a new tab, +// like a right-click "open image in new tab" on a regular . +function openCanvasInNewTab(canvas) { + canvas.toBlob((blob) => { + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + // Give the new tab time to load the image before freeing the blob. + setTimeout(() => URL.revokeObjectURL(url), 30000); + }); +} + +// Size the on-screen canvas, preserving aspect ratio: shown at its actual +// pixel size up to 128px on its largest side, scaled down to fit within +// 128px otherwise. Never scaled up. Wires up click-to-open-in-new-tab +// (the "click to open at actual size" hint lives once in the Preview +// header, not per canvas). function setCanvasDisplaySize(canvas) { - const minVisibleSize = 96; - const scale = Math.max(1, minVisibleSize / Math.min(canvas.width, canvas.height)); - // eslint-disable-next-line no-param-reassign - canvas.style.aspectRatio = `${canvas.width} / ${canvas.height}`; + const maxPreviewSize = 128; + const largestSide = Math.max(canvas.width, canvas.height); + const scale = largestSide > maxPreviewSize ? maxPreviewSize / largestSide : 1; // eslint-disable-next-line no-param-reassign canvas.style.width = `${Math.round(canvas.width * scale)}px`; // eslint-disable-next-line no-param-reassign - canvas.style.height = 'auto'; + canvas.style.height = `${Math.round(canvas.height * scale)}px`; + // eslint-disable-next-line no-param-reassign + canvas.onclick = () => openCanvasInNewTab(canvas); } // Draw the image onto the canvas, taking into account color and scaling @@ -655,9 +670,11 @@ function setTextInputSize(width, height) { function checkImagesAvailable() { const error = document.getElementById('no-images-error'); const glyphNameNote = document.getElementById('glyph-name-note'); + const previewNote = document.getElementById('preview-note'); const hasImages = images.length() > 0; error.style.display = hasImages ? 'none' : 'block'; glyphNameNote.style.display = hasImages ? 'block' : 'none'; + previewNote.style.display = hasImages ? 'block' : 'none'; return hasImages; } @@ -712,6 +729,7 @@ function handleTextInput(drawMode) { : listToImageVertical(list, canvas); if (success) { + setCanvasDisplaySize(canvas); document.getElementById('text-input-error').style.display = 'none'; document.querySelectorAll('.no-file-selected').forEach((el) => { // eslint-disable-next-line no-param-reassign @@ -927,32 +945,34 @@ function wrapFullExample(arrayCode, varName, width, height) { #include #include -#define OLED_RESET 4 -Adafruit_SSD1306 display(OLED_RESET); +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 -#if (SSD1306_LCDHEIGHT != ${settings.screenHeight}) -#error("Height incorrect, please fix Adafruit_SSD1306.h!"); -#endif +// Initialize display with standard I2C reset pin config (-1) +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); ${arrayCode} void setup() { - // initialize with the I2C addr 0x3D for the 128x64 - // replace with 0x3C for the 128x32 - display.begin(SSD1306_SWITCHCAPVCC, 0x3D); + Serial.begin(9600); + + // Initialize with I2C address 0x3C. You might have to change this to 0x3D for your display. + if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { + Serial.println(F("SSD1306 allocation failed. Check your wiring or I2C address!")); + for(;;); + } display.clearDisplay(); // Make sure the display is cleared - // Draw the bitmap: - // drawBitmap(x position, y position, bitmap data, bitmap width, bitmap height, color) - display.drawBitmap(0, 0, ${varName}, ${width}, ${height}, WHITE); - // Update the display + // drawBitmap(x_coordinate, y_coordinate, array_name, width, height, color) + display.drawBitmap(0, 0, ${varName}, ${width}, ${height}, SSD1306_WHITE); + + // Push the memory buffer out to the physical display display.display(); } void loop() { -} -`; +}`; } // Removes the trailing separator comma left after the last byte value.