Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions load_printer_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
20 changes: 20 additions & 0 deletions tests/test_load_printer_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading