110 lines
4.2 KiB
Python
110 lines
4.2 KiB
Python
"""Encode the supplied Freddy iris as a 178 px LVGL RGB565+alpha image.
|
|
|
|
The source WebP stays unchanged. Run from the project root with the bundled
|
|
Python (Pillow required): python firmware/tools/make_iris.py
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from PIL import Image, ImageDraw
|
|
import hashlib
|
|
import math
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "reference_images/official_freddy_eye_texture.webp"
|
|
OUTPUT = ROOT / "firmware/eye_test/FreddyIris.cpp"
|
|
PREVIEW = ROOT / "firmware/assets/freddy_iris_preview.png"
|
|
DISPLAY_PREVIEW = ROOT / "firmware/assets/freddy_eye_display_preview.png"
|
|
BLINK_PREVIEW = ROOT / "firmware/assets/freddy_eye_blink_preview.png"
|
|
RED_PREVIEW = ROOT / "firmware/assets/freddy_eye_red_preview.png"
|
|
SIZE = 178
|
|
|
|
|
|
def main() -> None:
|
|
source = Image.open(SOURCE).convert("RGB")
|
|
if source.size != (508, 466):
|
|
raise ValueError(f"Unexpected source size: {source.size}")
|
|
|
|
# The artwork's painted disc spans roughly x=159..372, y=126..337.
|
|
# Keep an equal square around that disc, then scale to the renderer's iris.
|
|
iris = source.crop((159, 125, 373, 339)).resize(
|
|
(SIZE, SIZE), Image.Resampling.LANCZOS
|
|
)
|
|
preview = Image.new("RGBA", (SIZE, SIZE))
|
|
encoded = bytearray()
|
|
radius = SIZE / 2
|
|
|
|
for y in range(SIZE):
|
|
for x in range(SIZE):
|
|
r, g, b = iris.getpixel((x, y))
|
|
distance = math.hypot(x + 0.5 - radius, y + 0.5 - radius)
|
|
# Two-pixel feather removes the source's white rectangular backing.
|
|
alpha = round(max(0.0, min(1.0, (radius - distance) / 2.0)) * 255)
|
|
preview.putpixel((x, y), (r, g, b, alpha))
|
|
rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
|
|
encoded.extend((rgb565 & 0xFF, rgb565 >> 8, alpha))
|
|
|
|
PREVIEW.parent.mkdir(parents=True, exist_ok=True)
|
|
preview.save(PREVIEW)
|
|
display = Image.new("RGBA", (480, 480), (57, 39, 29, 255))
|
|
draw = ImageDraw.Draw(display)
|
|
draw.ellipse((18, 18, 461, 461), fill=(116, 56, 36, 255))
|
|
draw.ellipse((24, 24, 455, 455), fill=(245, 244, 234, 255))
|
|
display.alpha_composite(preview, (151, 151))
|
|
DISPLAY_PREVIEW.parent.mkdir(parents=True, exist_ok=True)
|
|
display.save(DISPLAY_PREVIEW)
|
|
blink = display.copy()
|
|
blink_draw = ImageDraw.Draw(blink)
|
|
blink_draw.rectangle((0, 0, 479, 139), fill=(116, 56, 36, 255))
|
|
blink_draw.rectangle((0, 340, 479, 479), fill=(116, 56, 36, 255))
|
|
blink.save(BLINK_PREVIEW)
|
|
red = Image.new("RGBA", (480, 480), (57, 39, 29, 255))
|
|
red_draw = ImageDraw.Draw(red)
|
|
red_draw.ellipse((18, 18, 461, 461), fill=(116, 56, 36, 255))
|
|
red_draw.ellipse((24, 24, 455, 455), fill=(33, 25, 27, 255))
|
|
for diameter, color, alpha in (
|
|
(178, (21, 9, 9), 255),
|
|
(144, (110, 11, 8), 34),
|
|
(116, (190, 27, 19), 55),
|
|
(82, (226, 45, 30), 90),
|
|
(48, (250, 65, 43), 165),
|
|
(28, (255, 58, 43), 255),
|
|
(13, (255, 242, 227), 255),
|
|
):
|
|
layer = Image.new("RGBA", red.size)
|
|
offset = (480 - diameter) // 2
|
|
ImageDraw.Draw(layer).ellipse(
|
|
(offset, offset, offset + diameter - 1, offset + diameter - 1),
|
|
fill=(*color, alpha),
|
|
)
|
|
red = Image.alpha_composite(red, layer)
|
|
red.save(RED_PREVIEW)
|
|
lines = [
|
|
"// Generated from reference_images/official_freddy_eye_texture.webp.",
|
|
f"// Source SHA-256: {hashlib.sha256(SOURCE.read_bytes()).hexdigest()}",
|
|
"// Regenerate with firmware/tools/make_iris.py; do not edit this array.",
|
|
'#include "FreddyIris.h"',
|
|
"",
|
|
"static const uint8_t kFreddyIrisPixels[] = {",
|
|
]
|
|
for offset in range(0, len(encoded), 18):
|
|
lines.append(
|
|
" " + ", ".join(f"0x{byte:02x}" for byte in encoded[offset : offset + 18]) + ","
|
|
)
|
|
lines += [
|
|
"};",
|
|
"",
|
|
"const lv_img_dsc_t kFreddyIris = {",
|
|
" .header = {.cf = LV_IMG_CF_TRUE_COLOR_ALPHA, .always_zero = 0,",
|
|
f" .reserved = 0, .w = {SIZE}, .h = {SIZE}}},",
|
|
" .data_size = sizeof(kFreddyIrisPixels),",
|
|
" .data = kFreddyIrisPixels,",
|
|
"};",
|
|
]
|
|
OUTPUT.write_text("\n".join(lines) + "\n")
|
|
print(f"Wrote {OUTPUT} ({len(encoded):,} image bytes) and previews in {PREVIEW.parent}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|