Dieser Artikel handelt von der Engineering-Seite von Image-Schiebepuzzles: wie aus einem beliebigen Foto ein spielbares N×N-Puzzle wird. Es ist der Teil, den Spieler nie sehen und Entwickler mehr Zeit darauf verwenden, als sie erwarten.
Die Pipeline
Drei Stufen, der Reihe nach:
1. Quadratisches Croppen
Die meisten Fotos sind nicht quadratisch. iPhone-HEIC-Dateien sind 4:3; im Hochformat aufgenommene 3:4. Ein Schiebepuzzle-Brett ist 1:1. Schritt eins: die quadratische Region des Fotos wählen, die gespielt wird.
Zwei gängige Ansätze:
Mitte-Crop. Nimmt das größte mittige Quadrat, das hineinpasst. Schnell, vorhersagbar, gelegentlich falsch (Subjekt nicht in der Mitte).
Interaktiver Crop. Zeigt dem Nutzer ein verschiebbares Quadrat-Overlay. Lässt ihn wählen. Langsamer, aber der Nutzer bekommt immer, was er will.
Slide Puzzle verwendet interaktiven Crop, standardmäßig mittig. Der Crop ist nicht-destruktiv — die Originaldatei in der Foto-Bibliothek wird nie verändert.
2. Auf Arbeitsauflösung verkleinern
Nach dem Crop ist das Bild meist noch 3000×3000 oder größer (je nach Kamera). Für das Puzzle-Spiel ist das übertrieben. Ein 6×6-Brett in 320pt auf einem 3×-iPhone-Display rendert jedes Plättchen mit etwa 53pt = 160 Gerätepixeln. Eine Quellauflösung von 1024×1024 ergibt etwa 170 Pixel pro Plättchen — schärfer, als das Display zeigen kann.
Verkleinerung auf 1024 (manchmal 2048) ist Standard. Größere Quellen verschwenden Speicher und verlangsamen das Laden ohne sichtbare Verbesserung.
3. Plättchen-Rendering auf Anfrage
Das ist der Teil, den die meisten beim ersten Versuch falsch machen. Das Bild wird tatsächlich nicht in 16 separate Dateien geschnitten. Das wäre langsam, verschwenderisch und unnötig.
Stattdessen rendert die App jedes Plättchen, indem sie dasselbe Quellbild in ein plättchengroßes Rechteck zeichnet, mit dem Quellrechteck verschoben um die Position des Plättchens im Zielbild. CSS nennt das einen „background-position“-Trick; iOS einen CGImage-Clip-Rectangle; SwiftUI verwendet Rectangle().clipped() über Image.
Im Pseudocode, ein Plättchen an Zielposition (Zeile, Spalte) eines N×N-Bretts mit Bild der Seite S zeichnen:
draw image at (-Spalte * S/N, -Zeile * S/N)
within a clip rectangle of (S/N × S/N)
Das ist es. Sechzehn Plättchen für ein 4×4 sind 16 Aufrufe an denselben Renderer mit 16 verschiedenen Versätzen. Die App muss die Datei nie schneiden.
Deshalb ist das Schneiden eines 6×6-Bretts sofort: es sind 36 Aufrufe an denselben Renderer, nicht 36 Dateioperationen.
Was ist mit Animation
Das Schieben eines Plättchens ist ein Translate-Transform des gerenderten Plättchens. Der Bildinhalt im Plättchen ändert sich nicht — nur die Position des Clip-Rahmen am Bildschirm. Die Schiebeanimation wird damit GPU-trivial und läuft mit 120 Hz auf ProMotion-Displays.
Drei Implementierungs-Details, die zählen:
- Clip und Inhalt sind Geschwister, nicht Eltern/Kind. Verschachtelt bewegt sich der Inhalt mit dem Clip, und das Schieben bricht.
- Transform verwenden, nicht Layout. Animation von x/y per CSS-Transform oder SwiftUI-Offset ist GPU-beschleunigt; Animation von left/top nicht.
- Vorrastern beim ersten Erscheinen. Der erste Frame eines Plättchen-Renderings kann langsam sein, weil das Quellbild dekodiert werden muss. Alle Plättchen einmal beim Spielstart rendern, um sie aufzuwärmen.
Speicherung
Wo lebt das importierte Bild wirklich?
In einer privatsphäre-respektierenden App in der Sandbox der App, von iOS im Ruhezustand verschlüsselt. Die Foto-Bibliothek hält weiterhin das Original; die App speichert eine Arbeitskopie 1024×1024 in ihrem eigenen Documents-Ordner. Wenn der Nutzer die App löscht, geht die Arbeitskopie mit.
In einer Cloud-basierten App lebt die Arbeitskopie auf einem Server irgendwo. Die Konsequenzen sind sehr unterschiedlich — für Privatsphäre, Offline-Spiel und dafür, was passiert, wenn der Server verschwindet. Slide Puzzle ist der Sandbox-Typ.
Speicherbudget
Ein typisches 4×4-Image-Schiebepuzzle:
- Quellbild: ~3 MB JPEG.
- Dekodiertes Bild im Speicher: 1024 × 1024 × 4 Byte = 4 MB.
- Eine Kopie pro aktive Partie.
Das geht. Bei 6×6 sind Quelle und Decode gleich groß. Das Brett braucht nicht mehr Speicher.
Wo Speicherbudgets knapp werden, sind Cover-Bibliotheken: 300 Cover × 4 MB = 1,2 GB, wenn alle gleichzeitig dekodiert werden. Apps vermeiden das durch On-Demand-Dekodierung (nur wenn ein Cover gezeigt wird) und Freigabe beim Backgrounding (nur das Bild der aktiven Partie bleibt im Speicher).
Sonderfälle
Drei Dinge, die in der Praxis schiefgehen:
Fotos mit EXIF-Orientierungs-Tag. Ein im Hochformat aufgenommenes, aber im Querformat gespeichertes Foto mit Rotations-Tag zeigt sich in der Foto-Bibliothek korrekt und im Puzzle falsch, wenn die App die EXIF-Rotation vergisst. Die erste Version jeder Foto-Puzzle-App hat diesen Bug.
Sehr große Quellbilder. Manche HEIC-Dateien sind 6000×8000 Pixel. In voller Auflösung in den Speicher zu laden, lässt eine App auf kleineren iPhones abstürzen. Die Lösung: Streaming-Dekodierung bei verkleinerter Zielgröße — Apples ImageIO unterstützt das. Dekodieren Sie direkt von der Festplatte auf 2048×2048; nie in voller Größe dekodieren.
Subpixel-Rendering. Plättchengrößen, die nicht sauber in das Pixelraster fallen, erzeugen eine fraktionelle Pixelspalte an einer Seite jedes Plättchens. Mit reinem Clipping zeigt sich das als haariger Spalt. Lösung: entweder Plättchengrößen auf ganze Pixel snappen (sichtbares Zittern bei nicht-standardmäßigen Brettgrößen) oder Plättchen um 0,5 Pixel überlappen lassen (kein Spalt, kein Zittern).
Das sind keine tiefen Probleme, aber sie werden von Erstimplementierern einheitlich vergessen.
Zusammenfassung
Die Pipeline: croppen → verkleinern → Clip-und-Translate zum Rendern von Plättchen → Translate-Transforms für Schiebeanimationen. In einer privatsphäre-respektierenden App verlässt das Bild das Gerät nie. Die ganze Pipeline passt in etwa 200 Codezeilen, plus weitere 50 für die EXIF-Rotation, die jeder beim ersten Mal vergisst.
Image-Schiebepuzzles sind einfacher als sie von außen aussehen. Das Bild bleibt ein Bild; die Plättchen sind nur gerahmte Ansichten darauf.