Files
lucky-cat/tools/png_to_rle.py
T
CSBOOK\44744 bc74db61a9 电机最大旋转角度限定100度
财位指令旋转电机以后返回原位
2026-07-01 15:54:13 +08:00

144 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Convert PNG images to the binary RGB565 RLE format used by lcd_direct."""
import argparse
import struct
from pathlib import Path
try:
from PIL import Image
except ImportError as exc:
raise SystemExit("Pillow is required. Install it with: python -m pip install Pillow") from exc
MAX_RUN_COUNT = 0xFFFF
def parse_rgb(value):
text = value.strip()
if text.startswith("#"):
text = text[1:]
if len(text) != 6:
raise argparse.ArgumentTypeError("expected RRGGBB or #RRGGBB")
try:
return tuple(int(text[index:index + 2], 16) for index in (0, 2, 4))
except ValueError as exc:
raise argparse.ArgumentTypeError("background must be hexadecimal RGB") from exc
def rgb888_to_rgb565(red, green, blue):
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3)
def image_to_runs(path, background):
with Image.open(path) as image:
rgba = image.convert("RGBA")
width, height = rgba.size
bg_red, bg_green, bg_blue = background
runs = []
current_color = None
current_count = 0
for red, green, blue, alpha in rgba.getdata():
if alpha < 255:
red = ((red * alpha) + (bg_red * (255 - alpha))) // 255
green = ((green * alpha) + (bg_green * (255 - alpha))) // 255
blue = ((blue * alpha) + (bg_blue * (255 - alpha))) // 255
color = rgb888_to_rgb565(red, green, blue)
if color == current_color and current_count < MAX_RUN_COUNT:
current_count += 1
continue
if current_color is not None:
runs.append((current_color, current_count))
current_color = color
current_count = 1
if current_color is not None:
runs.append((current_color, current_count))
return width, height, runs
def write_rle(path, runs):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("wb") as output:
for color, count in runs:
output.write(struct.pack("<HH", color, count))
def iter_inputs(input_path, recursive):
if input_path.is_file():
if input_path.suffix.lower() != ".png":
raise SystemExit(f"input file is not a PNG: {input_path}")
yield input_path
return
pattern = "**/*.png" if recursive else "*.png"
yield from sorted(input_path.glob(pattern))
def output_path_for(input_file, input_root, output):
if output is None:
return input_file.with_suffix(".rle")
if input_root.is_file():
return output
relative = input_file.relative_to(input_root)
return (output / relative).with_suffix(".rle")
def convert_file(input_file, input_root, output, background):
out_path = output_path_for(input_file, input_root, output)
width, height, runs = image_to_runs(input_file, background)
write_rle(out_path, runs)
print(f"{input_file} -> {out_path} ({width}x{height}, {len(runs)} runs)")
def main():
parser = argparse.ArgumentParser(
description="Convert PNG files to lcd_direct RGB565 RLE binary assets.",
)
parser.add_argument("input", type=Path, help="PNG file or directory containing PNG files.")
parser.add_argument(
"-o",
"--output",
type=Path,
help="Output .rle file for one input file, or output directory for an input directory.",
)
parser.add_argument(
"-r",
"--recursive",
action="store_true",
help="Recursively convert PNG files when input is a directory.",
)
parser.add_argument(
"--background",
type=parse_rgb,
default=(0, 0, 0),
help="RGB background for transparent pixels, as RRGGBB or #RRGGBB. Default: 000000.",
)
args = parser.parse_args()
input_path = args.input.resolve()
if not input_path.exists():
raise SystemExit(f"input path does not exist: {input_path}")
output = args.output.resolve() if args.output is not None else None
inputs = list(iter_inputs(input_path, args.recursive))
if not inputs:
raise SystemExit(f"no PNG files found in: {input_path}")
if input_path.is_file() and output is not None and output.suffix.lower() != ".rle":
raise SystemExit("single-file output must use a .rle suffix")
for input_file in inputs:
convert_file(input_file, input_path, output, args.background)
if __name__ == "__main__":
main()