88 lines
2.7 KiB
Python
Executable File
88 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""Convert an image to an uncompressed LVGL RGB565 launcher background."""
|
|
|
|
import argparse
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
LV_IMAGE_HEADER_MAGIC = 0x19
|
|
LV_COLOR_FORMAT_RGB565 = 0x12
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Create an uncompressed LVGL .bin image for "
|
|
"/sdcard/tactility/launcher/background.bin. Use a square image sized "
|
|
"to the display's longest edge to support both orientations without scaling "
|
|
"(for example, 320x320 for a 320x240 display)."
|
|
)
|
|
)
|
|
parser.add_argument("input", type=Path, help="Source image")
|
|
parser.add_argument("output", type=Path, help="Destination .bin file")
|
|
parser.add_argument("--width", type=int, required=True, help="Output width")
|
|
parser.add_argument("--height", type=int, required=True, help="Output height")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
if args.width <= 0 or args.width > 65535 or args.height <= 0 or args.height > 65535:
|
|
raise SystemExit("width and height must be between 1 and 65535")
|
|
|
|
magick = shutil.which("magick")
|
|
if magick is None:
|
|
raise SystemExit("ImageMagick is required (the 'magick' command was not found)")
|
|
|
|
command = [
|
|
magick,
|
|
str(args.input),
|
|
"-resize",
|
|
f"{args.width}x{args.height}^",
|
|
"-gravity",
|
|
"center",
|
|
"-extent",
|
|
f"{args.width}x{args.height}",
|
|
"-depth",
|
|
"8",
|
|
"rgb:-",
|
|
]
|
|
rgb888 = subprocess.run(command, check=True, stdout=subprocess.PIPE).stdout
|
|
expected_size = args.width * args.height * 3
|
|
if len(rgb888) != expected_size:
|
|
raise SystemExit(f"unexpected ImageMagick output: {len(rgb888)} bytes, expected {expected_size}")
|
|
|
|
rgb565 = bytearray(args.width * args.height * 2)
|
|
for source_offset in range(0, len(rgb888), 3):
|
|
r, g, b = rgb888[source_offset : source_offset + 3]
|
|
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
|
|
destination_offset = (source_offset // 3) * 2
|
|
struct.pack_into("<H", rgb565, destination_offset, pixel)
|
|
|
|
stride = args.width * 2
|
|
header = struct.pack(
|
|
"<BBHHHHH",
|
|
LV_IMAGE_HEADER_MAGIC,
|
|
LV_COLOR_FORMAT_RGB565,
|
|
0,
|
|
args.width,
|
|
args.height,
|
|
stride,
|
|
0,
|
|
)
|
|
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_bytes(header + rgb565)
|
|
print(
|
|
f"Wrote {args.output} ({args.width}x{args.height}, "
|
|
f"{len(header) + len(rgb565)} bytes, uncompressed RGB565)"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|