- Python 75.1%
- TypeScript 7.8%
- JavaScript 7.5%
- HTML 6.4%
- CSS 2.9%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docs | ||
| examples/corpus | ||
| scripts | ||
| src/repixelizer | ||
| tests | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
Repixelizer
Repixelizer is a standalone Python CLI for images that are doing a pixel art impression instead of actually respecting the grid.
It is aimed at the annoying middle ground: sprites, emblems, and logos that look locally pixelated but fall apart the second you ask them to commit to one lattice like grown-ups.
Instead of pretending this is a resize problem, Repixelizer treats it as lattice inference plus local structure preservation: infer the implied grid, choose real output cells, and snap them back onto a coherent pixel lattice while preserving the source's local adjacency patterns.
Examples
Repixelizer was built to rescue fake pixel art, but it can also be used to generate pixel art directly from non-pixel source art when the shapes are clean and the local structure is doing something useful.
Rows show the source art, a plain Lanczos downscale to the same target size, and the repixelized result. The bottom row also carries a sword-guard picture-in-picture, because that tiny region is one of the more honest tests of whether the tool is preserving local cell adjacency instead of just getting the vibes approximately correct.
That is the two-headed pitch in one image: force non-pixel art onto a coherent grid, or take AI pixel art that only respects the grid locally and make it commit.
Current Status
This is already a serious experimental baseline, not a toy prototype:
- CUDA-backed inference and solver paths are wired up
- a shared source-lattice reference now drives solver, metrics, and reranking
- round-trip corpus benchmarking is built in
- soft and crisp corruption profiles are supported
- diagnostics and baseline comparisons are written automatically, including per-stage source-fidelity in
run.json - a black-box tuning harness exists for longer offline parameter sweeps
The current pipeline is especially strong on benchmarked sprite and emblem inputs where local fake-pixel texture is meaningful but the global grid is inconsistent.
Recent adjacency-focused status:
- the cleaned AI badge regression now beats naive resize on the repo's source-lattice consistency metric under the selected lattice
- the snap-to-refine handoff is now source-first rather than dominated by the softened representative lattice
- low-confidence phase reranking now uses a soft size penalty instead of a hard size-jump reject
- an experimental
tile-graphreconstruction mode now exists behind--reconstruction-mode tile-graph
Current tile-graph status:
- it now keeps candidates strictly local to each output coord and only uses literal source-pixel colors, so it no longer invents averaged patch colors or borrow labels from distant regions
- candidate seeding now starts from source-side atomic regions projected onto the output lattice, layered together with the old sharp/edge anchors instead of replacing them outright
- the latest full-emblem probe under
artifacts/full-emblem-tile-graph-atomic-v3-cuda/lands at0.0224source-fidelity, beating the earlier full-CUDA tile-graph baseline at0.0283 - tile-graph now falls back to its initial assignment when the propagation loop would make source-lattice fidelity worse, which is currently important for preserving sharp internal contour cells
- it still beats naive resize on the repo's synthetic thin-feature regression and now does so without letting the final propagation step blur past the initial placement
- source-region connected-components now have a device-side Torch path, so the expensive labeling step no longer depends on Python flood fill
- on the real cleaned badge, the current tile-graph model build now takes about
9.48son CPU versus6.37son CUDA while producing the same2499source regions and23223candidates - the remaining large-fixture bottleneck is the per-component one-cell window cutting pass, which still hops back to CPU after labeling
- elongated source regions now get a stroke-aware slicing pass that follows their principal axis instead of only marching with cardinal queue steps, and the repo now has a shallow-stroke regression that checks for fan-out on that kind of component
- that stroke-aware slicer is a real synthetic improvement but not a real-badge win yet: the latest badge probe under
artifacts/badge-tile-graph-stroke-v2-cuda/still keeps the initial assignment and slightly regresses source-fidelity (0.1832vs0.1800) - this fixes the core design mismatch that had allowed repeated distant labels to create big same-color patches and opaque black background blocks
- on the current
24x24emblem smoke case, an end-to-endtile-graphrun dropped from about2.57son CPU to0.61son CUDA on this machine - the older hard-edge-only candidate widening pass under
artifacts/full-emblem-tile-graph-hard-edge-v2-cuda/remains a useful negative result: more edge choices alone sharpen some cells locally but still regress full-emblem source-fidelity (0.0377)
Quickstart
python -m venv .venv
.venv\Scripts\python -m pip install -e .[dev]
Run the optimizer:
repixelize input.png --out output.png
repixelize input.png --out output.png --diagnostics-dir diagnostics --device auto
repixelize input.png --out output.png --reconstruction-mode tile-graph --diagnostics-dir diagnostics --device cpu
repixelize input.png --out output.png --reconstruction-mode tile-graph --diagnostics-dir diagnostics --device cuda
Run the optimizer plus baselines:
repixelize compare input.png --out output.png --diagnostics-dir diagnostics
Corpus And Benchmarks
Prepare a local Creative Commons corpus:
repixelize prepare-corpus --corpus-dir examples/corpus
Run the round-trip benchmark:
repixelize benchmark --corpus-dir examples/corpus --out-dir artifacts/benchmark
Useful benchmark flags:
--profile softor--profile crisp--case <name>repeated for a focused slice--limit <n>for the firstnmatching cases--infer-sizeto test automatic size inference--keep-existingif you explicitly want to preserve an old output directory
By default the benchmark clears its output directory before each run so the artifacts folder stays readable.
Tuning
Repixelizer includes a black-box tuning loop for longer offline searches over solver weights:
repixelize tune --corpus-dir examples/corpus --out-dir artifacts/tuning --profile soft --limit 8
This is intentionally a search-based tuner, not gradient descent. The objective depends on discrete argmins, thresholding, and benchmark comparisons, so it is better treated as a repeatable black-box optimization problem.
Repository Hygiene
This repo is set up to be safe for local experimentation:
- generated outputs under
artifacts/are ignored - local corpus payloads under
examples/corpus/originals/are ignored - archived raw sprite sheets under
examples/corpus/source-sheets/are ignored - generated corpus metadata files are ignored
That keeps benchmark assets, attribution exports, and tuning runs from polluting upstream history while still keeping the code, docs, and corpus layout tracked.
Repo Layout
src/repixelizer: core packagetests: focused regression testsdocs/spec.md: product and technical specdocs/implementation-plan.md: working roadmapexamples/corpus/README.md: local corpus layout and attribution workflow
Core modules:
inference.py: target size and phase inferencecontinuous.py: source-aware lattice snapping and local discrete refinementmetrics.py: fidelity, adjacency, and motif metricsbenchmark.py: corpus benchmark runnertuning.py: black-box hyperparameter search harnesssynthetic.py: facsimile generation for tests and benchmarks
Validation
Run the focused test suite with:
.venv\Scripts\python -m pytest -q
Regenerating README Assets
The README comparison sheet is generated from repo-tracked fixtures, not from random artifacts left lying around:
.venv\Scripts\python scripts\generate_readme_previews.py --vector-input tests\fixtures\real\ai-badge-vector.png --ai-input tests\fixtures\real\ai-badge-cleaned.png --out-sheet docs\readme-assets\badge-example-sheet.png --out-guard-crop docs\readme-assets\guard-right-crop-comparison.png --scratch-dir artifacts\readme-build --device cpu
That regenerates the README sheet, the standalone sword-guard closeup strip, and scratch outputs under artifacts/readme-build/ so you can inspect the actual low-res results used to build the docs sample.
Diagnostic Closeups
For docs or regression notes, you can generate reproducible source/output closeups from output-grid cell coordinates with:
.venv\Scripts\python scripts\render_focus_crop.py --input tests\fixtures\real\ai-badge-cleaned.png --out docs\readme-assets\guard-right-crop-source-final.png --cell-bbox 73 20 107 44 --panels source,final --scale 16 --steps 48 --device cpu
If you want the full internal state sheet instead of the docs-facing comparison:
.venv\Scripts\python scripts\render_focus_crop.py --input tests\fixtures\real\ai-badge-cleaned.png --out docs\readme-assets\guard-right-crop-states.png --cell-bbox 73 20 107 44 --panels source,snap,relaxed,final --scale 16 --steps 48 --device cpu
Notes:
--cell-bboxis in output-grid coordinates, not source-image pixels.- The script maps that region back to the matching source crop automatically.
--device cpuis the polite choice for docs generation if you do not want to stress the GPU for a tiny crop.
Notes
- The local corpus is optional; the codebase is still usable without it.
- The benchmark and tuning commands are designed for iterative local work, not for shipping generated artifacts in git.
- Palette enforcement is optional and can be left off for unconstrained recovery runs.