mockframe
Macht aus einem App-Screenshot ein perspektivisch gerendertes Gerätebild. Kein Browser, keine gekauften 3D-Modelle, keine GPU.
mockframe render heute.png --auto-device --bg light -o hero.png
Das Gehäuse entsteht prozedural aus Millimeterangaben und wird mit einem eigenen Software-Rasterizer schattiert. Ein neues Gerät ist deshalb ein Tabelleneintrag, keine Asset-Datei.
Version 0.1, 34 Tests grün. Ein Bild in 1400 x 1750 braucht auf einem Apple-Silicon-Mac rund drei bis vier Sekunden.
Warum kein fertiges 3D-Modell
Der naheliegende Weg wäre, ein iPhone-Modell zu kaufen und es zu rendern. Drei Gründe dagegen, in dieser Reihenfolge.
Lizenz. Modelle von Sketchfab, TurboSquid oder CGTrader sind überwiegend „editorial use only" oder verbieten die Weitergabe im Produkt. Ein Modell in ein Repo zu legen, das andere klonen, ist eine andere Rechtslage als ein Bild damit zu rendern. Prozedurale Geometrie aus öffentlich bekannten Maßen umgeht das Problem, statt es zu verwalten.
Geometrie. Ein Telefongehäuse ist ein Rounded Rect, extrudiert entlang eines gekrümmten Kantenprofils. Das sind vierzig Zeilen Code. Ein importiertes Modell bringt Material-Setups, Skalierungsfragen und Dreiecksmüll mit, ohne etwas zu lösen, was hier schwer wäre.
Gerätewahl. Wenn die Geometrie aus Zahlen entsteht, ist ein neues Modell ein Tabelleneintrag. Bei importierten Meshes ist es eine neue Datei, ein neues Material und ein neuer Kalibrierungslauf.
Was das kostet: der Rasterizer kann genau eine Klasse von Objekten, und Dinge, die ein fertiges Modell mitbringt, fehlen hier noch — Kamerabuckel, Antennenlinien, echte Glasrefraktion. Die Abwägung steht ausführlich in PROJEKT.md.
Architektur
graph TD
CLI["cli.py — Kommandozeile"]
SCENE["scene.py — Komposition, Presets"]
GEO["geometry.py — Mesh, Projektion"]
RAST["raster.py — Z-Buffer, Culling"]
SHADE["shading.py — Material, Umgebungen"]
SCR["screen.py — Warp, Aspect-Pruefung"]
DEV["devices.py — Geraetetabelle, Kantenprofil"]
CLI --> SCENE
SCENE --> GEO
SCENE --> RAST
SCENE --> SCR
RAST --> SHADE
GEO --> DEV
SCR --> DEV
SCENE --> DEV
devices.py importiert nichts und wird von allem gelesen. Deshalb ist ein neues Gerät eine Tabellenzeile und keine Codeänderung.
Zwei Entscheidungen prägen den Rest. Die Glasebene wird bewusst nicht mitrasterisiert, sondern separat per Vier-Punkt-Perspektivwarp komponiert: sie ist planar, deshalb ist der Warp exakt und schärfer als eine Texturinterpolation über Dreiecke. Und die Normalen werden analytisch berechnet statt über Nachbarfacetten gemittelt — das ist der Grund, warum auf der schmalen Seitenschiene keine Facettenspuren auftreten.
Der vollständige Ablauf eines Renderings, inklusive Abbruchzweig bei falschem Seitenverhältnis: ARCHITECTURE.md.
Benutzen
Mit Claude Code — einmalig, danach genügt „mach mir ein Hero-Bild":
/plugin marketplace add moOritzl/claude-plugins
/plugin install mockframe@moritzlenhard
Ohne Claude Code, oder mit einem anderen Agenten:
uvx mockframe render shot.png --auto-device -o hero.png
uvx lädt das Paket beim ersten Aufruf und legt im Projekt nichts ab. Ohne uv:
brew install uv auf macOS, sonst die Anleitung von
Astral.
Das Unterkommando capture braucht zusätzlich macOS mit Xcode, alles andere
läuft plattformunabhängig.
Entwicklung
Python 3.11 oder neuer. Alles landet in einer venv im Projektordner, nichts im System-Python.
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
-e installiert editierbar, Änderungen an src/mockframe/ wirken sofort.
[dev] zieht pytest mit; ohne den Zusatz kommen nur die
Laufzeitabhängigkeiten numpy, Pillow und scipy.
Der Skill ruft bewusst immer uvx mockframe, also die veröffentlichte
Version. Beim Entwickeln deshalb .venv/bin/mockframe ... direkt aufrufen.
Kommandos
mockframe steht hier und im Rest der Datei für den Aufruf, den du oben
gewählt hast: uvx mockframe ohne Installation, .venv/bin/mockframe aus dem
Projektordner, oder das blanke mockframe bei aktivierter venv.
mockframe devices # Tabelle mit Quellenangabe
mockframe render shot.png --device iphone-16-pro --preset hero-left
mockframe render shot.png --auto-device --bg dark
mockframe hdri-synth studio.npy # synthetische Environment-Map
mockframe capture --screens today,history --render # nur macOS
--auto-device leitet das Gerät aus der Screenshot-Auflösung ab. 1206 x 2622 ist ein iPhone 16 Pro, und das Werkzeug weiß das.
Passt der Screenshot nicht zum Gerät, bricht der Lauf ab statt still zu strecken:
Abbruch: Screenshot 1920x1080 (Verhaeltnis 1.7778) passt nicht zu
iPhone 16 Pro (Natural Titanium) (erwartet 0.4600).
Das ist Absicht. Ein gestrecktes Rendering sieht fast richtig aus, und der Fehler fällt oft erst auf, wenn das Bild schon veröffentlicht ist.
HDRI
Die eingebaute Umgebung ist eine Formel aus Gaußkeulen. Sie ist sauber, aber glatt, weil eine Formel keine Struktur hat. Der Wechsel auf eine echte Environment Map ist der größte Qualitätssprung pro Zeile Code, und er ersetzt genau eine Funktion — die Schnittstelle ist sample(R), ein Reflexionsvektor rein, lineare Radianz raus.
| Analytische Studioumgebung | Synthetische HDRI |
|---|---|
| Standard, keine Datei nötig | --hdri studio.npy |
Beide Bilder sind derselbe Aufruf, dasselbe Gerät, dasselbe Preset. Nur die Umgebung unterscheidet sich. Die synthetische Map ist nicht automatisch die schönere Wahl — sie hat einen dunklen Grundton mit einer einzelnen Softbox, was die Schiene kontrastreicher, aber auch härter macht. Sie existiert, um den Sampling-Pfad zu belegen:
mockframe hdri-synth studio.npy
mockframe render shot.png --hdri studio.npy
Für Produktbilder eine echte Studio-HDRI von Poly Haven nehmen, die stehen unter CC0. Für .exr oder .hdr zusätzlich pip install -e ".[hdr]", die Datei selbst einlesen und an HDRIEnvironment(array) geben. Das ist der einzige Weg hier, der eine eigene Installation braucht statt uvx — er läuft nicht über die Kommandozeile, sondern im eigenen Python.
Environment Maps sind nicht eingecheckt, *.npy ist ignoriert. Die
synthetische ist 6 MB groß und aus dem Code bitgleich reproduzierbar; ein
Test hält den Hash fest.
Tests
pytest -q
test_convergence.py ist der Test, auf den es ankommt. Bei korrekter Interpolation darf das Bild nicht von der Dreieckszahl abhängen. Er existiert, weil im Prototyp die barycentrischen Gewichte um eine Position rotiert zugeordnet waren: w0 ist die Kantenfunktion für v0 nach v1 und damit das Gewicht von v2, nicht von v1. Der Fehler war im Bild als Leitermuster auf der Seitenschiene deutlich sichtbar, aber Hochfrequenzmetriken fielen dadurch nur von 2,93 auf 2,47. Der Konvergenztest fällt eindeutig durch.
Bei Renderern ist das generell der Test, der trägt: variiere einen Parameter, der das Ergebnis nicht verändern darf, und prüfe, dass er es nicht tut.
Genauigkeit der Maße
Breite, Höhe und Dicke stammen aus Apples Tech-Specs. Eckradius und Bezelbreite sind nicht offiziell dokumentiert und sind Näherungen. Jeder Tabelleneintrag hat ein source-Feld, und ein Test schlägt fehl, wenn es leer ist. Ohne das weiß in drei Monaten niemand mehr, welche Zahl geprüft ist und welche geraten.
Stand
Fünf Geräte, vier Kamerapresets, vier Hintergründe, analytische Studioumgebung, HDRI-Sampling, Seitenverhältnis-Prüfung, Aufnahme aus dem iOS-Simulator.
Was fehlt: Kamerabuckel auf der Rückseite, Antennenlinien, Bodenreflexion, das Duo-Preset. Für das Duo braucht es zuerst den Kamerabuckel, weil dort ein Gerät angeschnitten sichtbar ist.
Der Rasterizer ist eine Python-Schleife über Dreiecke. Vektorisierung über Kacheln oder Numba würde die Renderzeit spürbar drücken, ist aber Komfort und kommt deshalb nach der Bildqualität. Die begründete Reihenfolge steht in PROJEKT.md.
Lizenz und Rechtliches
MIT, siehe LICENSE.
NOTICE hält fest, was die Lizenz nicht abdeckt: das Projekt ist nicht mit Apple verbunden, das Repo enthält keine Assets Dritter, und für Bilder, die in den App Store gehen, gelten zusätzlich Apples eigene Marketingvorgaben.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mockframe-0.1.0.tar.gz.
File metadata
- Download URL: mockframe-0.1.0.tar.gz
- Upload date:
- Size: 27.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57adbcc2e2d16ab59e7a7b09f7e30a55e9d7d0dba718b90f95df788a551bc7eb
|
|
| MD5 |
7d9a0c4c2f76edcdae0e275898fc5236
|
|
| BLAKE2b-256 |
d547a9737b30b1b1ebaf143b4b8e1884621a815bb0a7921464a30d132ae921ad
|
File details
Details for the file mockframe-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mockframe-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b45fcd2a4f4b499b599fddc4173a0b1440bda44bcfe1227d880e2a776be1c319
|
|
| MD5 |
a0f42339bad6c5670179b0360c616196
|
|
| BLAKE2b-256 |
b89787e38c758b637e0a03d6afca8c33aec224497c712208c5ba8bf10147c2bb
|