From e67da429ddd4333537b07f8810dfc658ccd7922e Mon Sep 17 00:00:00 2001 From: Mendel Mobach Date: Fri, 16 Oct 2026 18:57:15 +0200 Subject: [PATCH] Print all non-white logo colors as solid black --- README.md | 4 +++- load_printer_logo.py | 8 +++++--- tests/test_load_printer_logo.py | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8636feb..775f6af 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ uploading. Restart the backend afterwards. The default stored width is 192 dots; `LOGO` prints at twice the stored width and height, so the printed width is at most 384 dots. `--width` changes the stored width. Images retain their aspect ratio; transparent pixels and padding -become white. Stored logos persist after power-off; receipt generation does not +become white. Only near-white pixels (all RGB channels at least 245) remain +white; every other color becomes solid black, without dithering. +Stored logos persist after power-off; receipt generation does not upload the image again. Epson's [FS q command](https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/fs_lq.html) diff --git a/load_printer_logo.py b/load_printer_logo.py index 7c4154d..2017ca9 100644 --- a/load_printer_logo.py +++ b/load_printer_logo.py @@ -5,7 +5,7 @@ import struct import time -from PIL import Image, ImageOps +from PIL import Image, ImageChops, ImageOps import serial from config import load_config @@ -20,8 +20,10 @@ def encode_logo(path, width=192): image = source.convert("RGBA") image.thumbnail((width, 512), Image.Resampling.LANCZOS) white = Image.new("RGBA", image.size, "white") - gray = Image.alpha_composite(white, image).convert("L") - ink = ImageOps.invert(gray.point(lambda value: 255 if value >= 128 else 0)) + red, green, blue = Image.alpha_composite(white, image).convert("RGB").split() + # Keep only near-white pixels white, including bright saturated colors as ink. + darkest = ImageChops.darker(ImageChops.darker(red, green), blue) + ink = ImageOps.invert(darkest.point(lambda value: 255 if value >= 245 else 0)) x_bytes, y_bytes = (image.width + 7) // 8, (image.height + 7) // 8 padded = Image.new("1", (x_bytes * 8, y_bytes * 8), 0) padded.paste(ink.convert("1"), (0, 0)) diff --git a/tests/test_load_printer_logo.py b/tests/test_load_printer_logo.py index b6a3974..5fd4b20 100644 --- a/tests/test_load_printer_logo.py +++ b/tests/test_load_printer_logo.py @@ -33,6 +33,26 @@ def test_logo_scales_proportionally(tmp_path): ) +def test_only_near_white_pixels_remain_white(tmp_path): + path = tmp_path / "colors.png" + image = Image.new("RGB", (8, 1)) + image.putdata( + [ + (255, 255, 255), + (245, 245, 245), + (244, 244, 244), + (180, 180, 180), + (255, 255, 0), + (255, 255, 200), + (244, 255, 255), + (0, 0, 0), + ] + ) + image.save(path) + + assert loader.encode_logo(path)[7:] == b"\x00\x00" + b"\x80" * 6 + + @pytest.mark.parametrize("width", [0, 513]) def test_invalid_width_does_not_load_or_upload(width): with pytest.raises(ValueError):