Files
ViperOS/Scripts/gen_imagesee_assets.py
T

171 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Generate the sample images shipped with the ImageSee demo.
Run from the ViperOS root:
python Scripts/gen_imagesee_assets.py
The generated files live in Assets/images/ and are copied to /IMAGES on the
ViperOS disk by build.py. Requires Pillow (only for generating the assets;
the OS itself does not need it).
"""
import os
import struct
import zlib
from PIL import Image, ImageDraw, ImageFilter
W, H = 320, 220
OUT = os.path.join("Assets", "images")
BG_OUT = os.path.join("Assets", "background.jpg")
def test_pattern():
img = Image.new("RGBA", (W, H), (0, 0, 0, 255))
px = img.load()
for y in range(H):
for x in range(W):
r = (x * 255) // (W - 1)
g = (y * 255) // (H - 1)
b = 255 - ((x + y) * 255) // (W + H - 2)
px[x, y] = (r, g, b, 255)
d = ImageDraw.Draw(img)
d.rectangle([12, 12, 92, 72], fill=(255, 255, 255, 255))
d.rectangle([22, 22, 82, 62], fill=(0, 0, 0, 255))
d.rectangle([108, 12, 188, 72], fill=(230, 40, 40, 255))
d.rectangle([198, 12, 278, 72], fill=(40, 200, 80, 255))
d.rectangle([288, 12, 308, 72], fill=(40, 80, 230, 255))
d.ellipse([40, 100, 160, 210], fill=(255, 220, 40, 255))
d.ellipse([150, 90, 250, 190], outline=(255, 255, 255, 255), width=5)
d.line([0, H - 1, W - 1, 80], fill=(255, 0, 255, 255), width=3)
d.line([0, 80, W - 1, H - 1], fill=(0, 255, 255, 255), width=3)
for i in range(0, 40):
d.rectangle([280 - i, 160 + i, 286 - i, 166 + i], fill=(i * 6, i * 6, i * 6, 255))
return img
def png_chunk(typ: bytes, data: bytes) -> bytes:
c = struct.pack(">I", len(data)) + typ + data
c += struct.pack(">I", zlib.crc32(typ + data) & 0xFFFFFFFF)
return c
def write_interlaced_png(path, img):
"""Adam7 interlaced RGBA PNG (filter 0), to exercise the pass decoder."""
w, h = img.size
px = img.load()
passes = [(0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4),
(0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)]
raw = bytearray()
for x0, y0, dx, dy in passes:
pw = (w - x0 + dx - 1) // dx if w > x0 else 0
ph = (h - y0 + dy - 1) // dy if h > y0 else 0
if pw <= 0 or ph <= 0:
continue
for ry in range(ph):
raw.append(0) # filter: None
y = y0 + ry * dy
for rx in range(pw):
x = x0 + rx * dx
r, g, b, a = px[x, y]
raw += bytes((r, g, b, a))
ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 1)
out = b"\x89PNG\r\n\x1a\n" + png_chunk(b"IHDR", ihdr)
out += png_chunk(b"IDAT", zlib.compress(bytes(raw), 9))
out += png_chunk(b"IEND", b"")
with open(path, "wb") as f:
f.write(out)
def make_background():
"""960x540 desktop wallpaper shipped as /sys/images/screen/background.jpg."""
w, h = 960, 540
base = Image.new("RGB", (w, h))
px = base.load()
for y in range(h):
t = y / (h - 1)
r = int(12 + 34 * t)
g = int(16 + 20 * t)
b = int(34 + 44 * t)
for x in range(w):
# subtle diagonal tint
u = (x / (w - 1) + t) * 0.5
px[x, y] = (min(255, r + int(10 * u)),
min(255, g + int(6 * u)),
min(255, b + int(18 * u)))
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
d = ImageDraw.Draw(overlay)
blobs = [
(140, 420, 210, (30, 120, 190, 120)),
(330, 140, 170, (150, 40, 180, 110)),
(700, 360, 260, (20, 90, 160, 120)),
(820, 110, 150, (220, 60, 130, 90)),
(520, 470, 190, (40, 170, 170, 90)),
]
for cx, cy, rr, col in blobs:
d.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=col)
overlay = overlay.filter(ImageFilter.GaussianBlur(30))
base = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB")
# vignette keeps the corners dark so white window chrome stays readable
mask = Image.new("L", (w, h))
mp = mask.load()
for y in range(h):
for x in range(w):
dx = (x - w / 2) / (w / 2)
dy = (y - h / 2) / (h / 2)
d2 = dx * dx + dy * dy
v = 255 - int(min(150, d2 * 110))
mp[x, y] = max(0, v)
base = Image.composite(base, Image.new("RGB", (w, h), (0, 0, 0)), mask)
base.save(BG_OUT, "JPEG", quality=88, subsampling=2, optimize=False)
print("[assets] wrote", BG_OUT)
def main():
os.makedirs(OUT, exist_ok=True)
img = test_pattern()
# Opaque RGBA PNG (exercises color type 6, non-interlaced).
img.save(os.path.join(OUT, "sample.png"), "PNG")
# Palette PNG with one transparent color (color type 3 + tRNS).
pal = img.convert("RGB").quantize(colors=64, method=Image.MEDIANCUT)
pal.info["transparency"] = 0
pal.save(os.path.join(OUT, "pal.png"), "PNG", transparency=0)
# Baseline 4:2:0 JPEG.
img.convert("RGB").save(os.path.join(OUT, "photo.jpg"), "JPEG",
quality=90, subsampling=2, optimize=False)
# Grayscale JPEG (single component path).
img.convert("L").save(os.path.join(OUT, "gray.jpg"), "JPEG", quality=90)
# Interlaced RGBA PNG with a vertical alpha ramp (Adam7 + alpha).
inter = img.copy()
ip = inter.load()
for y in range(H):
for x in range(W):
r, g, b, a = ip[x, y]
ip[x, y] = (r, g, b, (x * 255) // (W - 1))
write_interlaced_png(os.path.join(OUT, "inter.png"), inter)
make_background()
# Reference samples for the app's serial output.
print("[assets] reference samples (PNG/JPEG decode with PIL):")
for name in ("sample.png", "photo.jpg", "gray.jpg",
"pal.png", "inter.png"):
path = os.path.join(OUT, name)
ref = Image.open(path).convert("RGBA")
pts = [(ref.width // 4, ref.height // 3),
(ref.width // 2, ref.height // 2),
((ref.width * 3) // 4, (ref.height * 2) // 3)]
vals = [ref.getpixel(p) for p in pts]
print(f"[assets] {name:12s} {ref.width}x{ref.height} " +
" ".join(f"{p[0]},{p[1]},{p[2]}" for p in vals))
print("[assets] wrote", OUT)
if __name__ == "__main__":
main()