from __future__ import annotations import argparse import json from dataclasses import dataclass from pathlib import Path import numpy as np from PIL import Image, ImageDraw from repixelizer.baselines import lanczos_resize_baseline from repixelizer.io import load_rgba, save_rgba from repixelizer.pipeline import run_pipeline @dataclass(slots=True) class RenderedPanel: image: Image.Image content_box: tuple[int, int, int, int] @dataclass(slots=True) class PreviewRow: row_id: str title: str source_rgba: np.ndarray source_subtitle: str lanczos_rgba: np.ndarray lanczos_subtitle: str phase_field_rgba: np.ndarray phase_field_subtitle: str target_width: int target_height: int focus_cell_bbox: tuple[int, int, int, int] def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Regenerate the README comparison sheet and closeups from repo fixtures.") parser.add_argument( "--input", default="tests/fixtures/real/ai-badge-cleaned.png", help="Cleaned AI fake-pixel badge source.", ) parser.add_argument( "--out-sheet", default="docs/readme-assets/badge-example-sheet.png", help="Output path for the full README comparison sheet.", ) parser.add_argument( "--out-guard-crop", default="docs/readme-assets/guard-right-crop-comparison.png", help="Output path for the standalone sword-guard closeup strip.", ) parser.add_argument( "--scratch-dir", default="artifacts/readme-build", help="Directory for intermediate low-res outputs and diagnostics.", ) parser.add_argument( "--dense-input", default="tests/fixtures/real/dense-landscape.png", help="Dense high-resolution pixel-art source used for the second README row.", ) parser.add_argument( "--dense-phase-field-output", default="", help="Optional existing dense landscape output. If omitted, the dense row is regenerated with automatic lattice inference.", ) parser.add_argument( "--guard-cell-bbox", nargs=4, type=int, default=(73, 20, 107, 44), metavar=("X0", "Y0", "X1", "Y1"), help="Sword-guard crop bounds in output-grid cell coordinates for the AI row.", ) parser.add_argument( "--dense-focus-cell-bbox", nargs=4, type=int, default=(44, 396, 124, 476), metavar=("X0", "Y0", "X1", "Y1"), help="Focus crop bounds in output-grid cell coordinates for the dense landscape row.", ) parser.add_argument( "--target-width", type=int, default=126, help="Pinned target width for the canonical badge comparison.", ) parser.add_argument( "--target-height", type=int, default=126, help="Pinned target height for the canonical badge comparison.", ) parser.add_argument( "--dense-target-width", type=int, default=512, help="Fallback target width used only when --dense-phase-field-output is supplied.", ) parser.add_argument( "--dense-target-height", type=int, default=512, help="Fallback target height used only when --dense-phase-field-output is supplied.", ) parser.add_argument( "--dense-auto-inferred-width", type=int, default=418, help="Deprecated display fallback for old fixed-output runs.", ) parser.add_argument( "--dense-auto-inferred-height", type=int, default=418, help="Deprecated display fallback for old fixed-output runs.", ) parser.add_argument("--steps", type=int, default=48, help="Optimizer step budget for both runs.") parser.add_argument("--device", default="cpu", choices=("auto", "cpu", "cuda"), help="Torch device for generation.") return parser.parse_args() def _to_image(rgba: np.ndarray) -> Image.Image: rgba8 = np.clip(np.rint(rgba * 255.0), 0, 255).astype(np.uint8) return Image.fromarray(rgba8, mode="RGBA") def _checkerboard(width: int, height: int, tile: int = 12) -> Image.Image: image = Image.new("RGBA", (width, height), (236, 236, 236, 255)) draw = ImageDraw.Draw(image) accent = (218, 218, 218, 255) for y in range(0, height, tile): for x in range(0, width, tile): if ((x // tile) + (y // tile)) % 2: draw.rectangle((x, y, min(width, x + tile), min(height, y + tile)), fill=accent) return image def _fit_rgba( rgba: np.ndarray, *, target_width: int, target_height: int, pixelated: bool, ) -> tuple[Image.Image, tuple[int, int, int, int]]: image = _to_image(rgba) scale = min(target_width / max(1, image.width), target_height / max(1, image.height)) resized_width = max(1, int(round(image.width * scale))) resized_height = max(1, int(round(image.height * scale))) resample = Image.Resampling.NEAREST if pixelated else Image.Resampling.LANCZOS resized = image.resize((resized_width, resized_height), resample=resample) offset_x = (target_width - resized_width) // 2 offset_y = (target_height - resized_height) // 2 return resized, (offset_x, offset_y, resized_width, resized_height) def _render_panel( label: str, *, rgba: np.ndarray, pixelated: bool, panel_width: int, panel_height: int, subtitle: str | None = None, ) -> RenderedPanel: header_height = 38 if subtitle else 28 gutter = 10 canvas = Image.new("RGBA", (panel_width, panel_height), (18, 18, 20, 255)) checker = _checkerboard(panel_width - gutter * 2, panel_height - header_height - gutter * 2) canvas.alpha_composite(checker, (gutter, header_height + gutter)) fitted, (inner_x, inner_y, inner_w, inner_h) = _fit_rgba( rgba, target_width=checker.width, target_height=checker.height, pixelated=pixelated, ) content_x = gutter + inner_x content_y = header_height + gutter + inner_y canvas.alpha_composite(fitted, (content_x, content_y)) draw = ImageDraw.Draw(canvas) draw.text((gutter, 6), label, fill=(255, 255, 255, 255)) if subtitle: draw.text((gutter, 22), subtitle, fill=(200, 200, 200, 255)) draw.rounded_rectangle((0, 0, panel_width - 1, panel_height - 1), radius=10, outline=(72, 72, 80, 255), width=1) return RenderedPanel(canvas, (content_x, content_y, inner_w, inner_h)) def _draw_source_bbox(render: RenderedPanel, source_bbox: tuple[int, int, int, int], *, source_width: int, source_height: int) -> None: x0, y0, x1, y1 = source_bbox inner_x, inner_y, inner_w, inner_h = render.content_box left = inner_x + int(round(inner_w * (x0 / max(1, source_width)))) right = inner_x + int(round(inner_w * (x1 / max(1, source_width)))) top = inner_y + int(round(inner_h * (y0 / max(1, source_height)))) bottom = inner_y + int(round(inner_h * (y1 / max(1, source_height)))) draw = ImageDraw.Draw(render.image) for delta, color in ((0, (255, 245, 120, 255)), (1, (20, 20, 20, 255))): draw.rectangle((left - delta, top - delta, right + delta, bottom + delta), outline=color, width=1) def _build_guard_strip( source_crop: np.ndarray, lanczos_crop: np.ndarray, repixelized_crop: np.ndarray, *, include_lanczos: bool = True, panel_width: int = 220, panel_height: int = 170, title: str | None = "Sword guard close-up", ) -> Image.Image: panels = [ _render_panel("Source", rgba=source_crop, pixelated=True, panel_width=panel_width, panel_height=panel_height), _render_panel("Repixelized", rgba=repixelized_crop, pixelated=True, panel_width=panel_width, panel_height=panel_height), ] if include_lanczos: panels.insert( 1, _render_panel("Lanczos", rgba=lanczos_crop, pixelated=True, panel_width=panel_width, panel_height=panel_height), ) title_height = 24 if title else 0 gap = 10 width = sum(panel.image.width for panel in panels) + gap * (len(panels) + 1) height = title_height + max(panel.image.height for panel in panels) + gap * 2 canvas = Image.new("RGBA", (width, height), (14, 14, 16, 242)) draw = ImageDraw.Draw(canvas) draw.rounded_rectangle((0, 0, width - 1, height - 1), radius=12, fill=(14, 14, 16, 242), outline=(92, 92, 104, 255), width=1) if title: draw.text((gap, 4), title, fill=(255, 255, 255, 255)) x = gap for panel in panels: canvas.alpha_composite(panel.image, (x, title_height + gap)) x += panel.image.width + gap return canvas def _build_pip_inset( rgba_crop: np.ndarray, *, width: int = 126, height: int = 94, ) -> Image.Image: fitted, (inner_x, inner_y, inner_w, inner_h) = _fit_rgba( rgba_crop, target_width=width - 12, target_height=height - 12, pixelated=True, ) canvas = Image.new("RGBA", (width, height), (14, 14, 16, 236)) draw = ImageDraw.Draw(canvas) draw.rounded_rectangle((0, 0, width - 1, height - 1), radius=10, fill=(14, 14, 16, 236), outline=(92, 92, 104, 255), width=1) checker = _checkerboard(width - 12, height - 12, tile=8) canvas.alpha_composite(checker, (6, 6)) canvas.alpha_composite(fitted, (6 + inner_x, 6 + inner_y)) return canvas def _pip_size_for_crop( rgba_crop: np.ndarray, *, max_width: int, max_height: int, padding: int = 12, ) -> tuple[int, int]: crop_height, crop_width = rgba_crop.shape[:2] if crop_width <= 0 or crop_height <= 0: return max_width, max_height inner_max_width = max(1, max_width - padding) inner_max_height = max(1, max_height - padding) aspect = crop_width / max(1, crop_height) inner_width = int(round(inner_max_height * aspect)) inner_width = max(min(inner_width, inner_max_width), min(inner_max_width, inner_max_height)) return inner_width + padding, inner_max_height + padding def _add_pip_inset(render: RenderedPanel, inset: Image.Image, *, margin_x: int = 12, margin_y: int = 12) -> None: inner_x, inner_y, inner_w, inner_h = render.content_box placement_x = inner_x + max(0, inner_w - inset.width - margin_x) placement_y = inner_y + max(0, inner_h - inset.height - margin_y) render.image.alpha_composite(inset, (placement_x, placement_y)) def _cell_bbox_to_source_bbox( cell_bbox: tuple[int, int, int, int], *, source_width: int, source_height: int, target_width: int, target_height: int, ) -> tuple[int, int, int, int]: x0, y0, x1, y1 = cell_bbox return ( int(np.floor(x0 * source_width / max(1, target_width))), int(np.floor(y0 * source_height / max(1, target_height))), int(np.ceil(x1 * source_width / max(1, target_width))), int(np.ceil(y1 * source_height / max(1, target_height))), ) def _crop_rgba(rgba: np.ndarray, bbox: tuple[int, int, int, int]) -> np.ndarray: x0, y0, x1, y1 = bbox return rgba[y0:y1, x0:x1] def _shrink_bbox_bottom_right( bbox: tuple[int, int, int, int], *, scale_x: float, scale_y: float, ) -> tuple[int, int, int, int]: x0, y0, x1, y1 = bbox width = max(1, x1 - x0) height = max(1, y1 - y0) new_width = max(1, int(round(width * scale_x))) new_height = max(1, int(round(height * scale_y))) return (x1 - new_width, y1 - new_height, x1, y1) def _save_summary(path: Path, payload: dict[str, object]) -> None: path.write_text(json.dumps(payload, indent=2), encoding="utf-8") def _build_row_panels( row: PreviewRow, *, panel_width: int, panel_height: int, inset_width: int = 139, inset_height: int = 103, ) -> tuple[list[RenderedPanel], tuple[int, int, int, int]]: panels = [ _render_panel( "Source", rgba=row.source_rgba, pixelated=False, panel_width=panel_width, panel_height=panel_height, subtitle=row.source_subtitle, ), _render_panel( "Lanczos", rgba=row.lanczos_rgba, pixelated=True, panel_width=panel_width, panel_height=panel_height, subtitle=row.lanczos_subtitle, ), _render_panel( "Phase-field", rgba=row.phase_field_rgba, pixelated=True, panel_width=panel_width, panel_height=panel_height, subtitle=row.phase_field_subtitle, ), ] focus_source_bbox = _cell_bbox_to_source_bbox( row.focus_cell_bbox, source_width=row.source_rgba.shape[1], source_height=row.source_rgba.shape[0], target_width=row.target_width, target_height=row.target_height, ) for panel in panels: _draw_source_bbox( panel, focus_source_bbox, source_width=row.source_rgba.shape[1], source_height=row.source_rgba.shape[0], ) inset_crops = [ _crop_rgba(row.source_rgba, focus_source_bbox), _crop_rgba(row.lanczos_rgba, row.focus_cell_bbox), _crop_rgba(row.phase_field_rgba, row.focus_cell_bbox), ] for panel, crop in zip(panels, inset_crops, strict=True): pip_width, pip_height = _pip_size_for_crop(crop, max_width=inset_width, max_height=inset_height) _add_pip_inset(panel, _build_pip_inset(crop, width=pip_width, height=pip_height), margin_x=4, margin_y=4) return panels, focus_source_bbox def main() -> None: args = _parse_args() scratch_dir = Path(args.scratch_dir) scratch_dir.mkdir(parents=True, exist_ok=True) source_input = Path(args.input) dense_input = Path(args.dense_input) out_sheet = Path(args.out_sheet) out_guard_crop = Path(args.out_guard_crop) out_sheet.parent.mkdir(parents=True, exist_ok=True) out_guard_crop.parent.mkdir(parents=True, exist_ok=True) phase_field_result = run_pipeline( source_input, scratch_dir / "badge-ai-phase-field-pinned.png", target_width=args.target_width, target_height=args.target_height, diagnostics_dir=scratch_dir / "phase-field-diag", steps=args.steps, device=args.device, enable_candidate_rerank=False, ) source_rgba = load_rgba(source_input) lanczos = lanczos_resize_baseline(source_rgba, width=args.target_width, height=args.target_height) dense_source_rgba = load_rgba(dense_input) if args.dense_phase_field_output: dense_phase_field_output = Path(args.dense_phase_field_output) dense_phase_field_rgba = load_rgba(dense_phase_field_output) dense_target_width = int(dense_phase_field_rgba.shape[1]) dense_target_height = int(dense_phase_field_rgba.shape[0]) dense_phase_field_subtitle = f"tracked {dense_target_width}x{dense_target_height} output" dense_title = ( f"Dense {dense_source_rgba.shape[1]}px pixel art -> tracked " f"{dense_target_width}x{dense_target_height} output" ) dense_output_summary = str(dense_phase_field_output) else: dense_phase_field_result = run_pipeline( dense_input, scratch_dir / "dense-landscape-phase-field-auto.png", diagnostics_dir=scratch_dir / "dense-phase-field-auto-diag", steps=args.steps, device=args.device, lattice_inference_mode="autocorr", ) dense_phase_field_rgba = dense_phase_field_result.output_rgba dense_target_width = int(dense_phase_field_result.inference.target_width) dense_target_height = int(dense_phase_field_result.inference.target_height) dense_phase_field_subtitle = f"{dense_target_width}x{dense_target_height} inferred lattice" dense_title = ( f"Dense {dense_source_rgba.shape[1]}px pixel art -> " f"{dense_target_width}x{dense_target_height} inferred lattice" ) dense_output_summary = str(scratch_dir / "dense-landscape-phase-field-auto.png") dense_lanczos = lanczos_resize_baseline( dense_source_rgba, width=dense_target_width, height=dense_target_height, ) save_rgba(scratch_dir / "badge-ai-lanczos.png", lanczos) save_rgba(scratch_dir / f"dense-landscape-lanczos-{dense_target_width}x{dense_target_height}.png", dense_lanczos) panel_width = 320 panel_height = 360 col_gap = 18 margin = 24 row_title_height = 22 row_gap = 20 badge_focus_cell_bbox = _shrink_bbox_bottom_right( tuple(args.guard_cell_bbox), scale_x=0.5, scale_y=0.5, ) dense_focus_cell_bbox = ( int(round(args.dense_focus_cell_bbox[0] * dense_target_width / max(1, args.dense_target_width))), int(round(args.dense_focus_cell_bbox[1] * dense_target_height / max(1, args.dense_target_height))), int(round(args.dense_focus_cell_bbox[2] * dense_target_width / max(1, args.dense_target_width))), int(round(args.dense_focus_cell_bbox[3] * dense_target_height / max(1, args.dense_target_height))), ) rows = [ PreviewRow( row_id="badge", title=f"AI fake pixel art -> {args.target_width}x{args.target_height} pinned lattice", source_rgba=source_rgba, source_subtitle="Cleaned AI fake pixel art", lanczos_rgba=lanczos, lanczos_subtitle=f"{args.target_width}x{args.target_height} baseline", phase_field_rgba=phase_field_result.output_rgba, phase_field_subtitle=f"{args.target_width}x{args.target_height} pinned lattice", target_width=args.target_width, target_height=args.target_height, focus_cell_bbox=badge_focus_cell_bbox, ), PreviewRow( row_id="dense-landscape", title=dense_title, source_rgba=dense_source_rgba, source_subtitle=f"{dense_source_rgba.shape[1]}px source", lanczos_rgba=dense_lanczos, lanczos_subtitle=f"{dense_target_width}x{dense_target_height} baseline", phase_field_rgba=dense_phase_field_rgba, phase_field_subtitle=dense_phase_field_subtitle, target_width=dense_target_width, target_height=dense_target_height, focus_cell_bbox=dense_focus_cell_bbox, ), ] built_rows: list[tuple[PreviewRow, list[RenderedPanel], tuple[int, int, int, int]]] = [] for row in rows: panels, focus_source_bbox = _build_row_panels( row, panel_width=panel_width, panel_height=panel_height, ) built_rows.append((row, panels, focus_source_bbox)) guard_bbox = _cell_bbox_to_source_bbox( tuple(args.guard_cell_bbox), source_width=source_rgba.shape[1], source_height=source_rgba.shape[0], target_width=args.target_width, target_height=args.target_height, ) guard_strip = _build_guard_strip( _crop_rgba(source_rgba, guard_bbox), _crop_rgba(lanczos, tuple(args.guard_cell_bbox)), _crop_rgba(phase_field_result.output_rgba, tuple(args.guard_cell_bbox)), title=None, ) guard_strip.save(out_guard_crop) width = margin * 2 + panel_width * 3 + col_gap * 2 row_block_height = row_title_height + panel_height height = margin * 2 + row_block_height * len(built_rows) + row_gap * max(0, len(built_rows) - 1) canvas = Image.new("RGBA", (width, height), (11, 11, 13, 255)) draw = ImageDraw.Draw(canvas) row_y = margin for row, panels, _focus_source_bbox in built_rows: draw.text((margin, row_y - 2), row.title, fill=(255, 255, 255, 255)) panel_y = row_y + row_title_height x = margin for panel in panels: canvas.alpha_composite(panel.image, (x, panel_y)) x += panel_width + col_gap row_y += row_block_height + row_gap out_sheet.parent.mkdir(parents=True, exist_ok=True) canvas.save(out_sheet) _save_summary( scratch_dir / "readme-preview-summary.json", { "rows": [ { "row_id": row.row_id, "title": row.title, "target_width": row.target_width, "target_height": row.target_height, "focus_cell_bbox": list(row.focus_cell_bbox), "focus_source_bbox": list(focus_source_bbox), } for row, _panels, focus_source_bbox in built_rows ], "badge_input": str(source_input), "dense_input": str(dense_input), "dense_phase_field_output": dense_output_summary, "guard_cell_bbox": list(args.guard_cell_bbox), "guard_source_bbox": list(guard_bbox), "out_sheet": str(out_sheet), "out_guard_crop": str(out_guard_crop), "scratch_dir": str(scratch_dir), }, ) print(str(out_sheet)) print(str(out_guard_crop)) if __name__ == "__main__": main()