iOS-Bildzuschnitt prüfen: von der Auswahl zu den Pixelgrenzen
Sie zeigen ein Bild mit 4000×3000 Pixeln in einer 300×300 Punkte großen Zuschneideansicht an und wählen den mittleren Bereich aus. Nach dem Export ist die abgebildete Person jedoch nach oben verschoben. Ändern Sie nicht gleich die Gestensteuerung: Bei proportionaler Einpassung bleiben oben und unten jeweils 37.5 Punkte frei. Wer die Koordinaten anhand der gesamten Ansicht umrechnet, erhält sowohl einen falschen Auswahlursprung als auch eine falsche Auswahlhöhe. Prüfen Sie die Koordinaten zunächst mit reinem Swift auf einem Cloud-Mac und binden Sie danach die Oberfläche an. So lassen sich Rechenfehler von Darstellungsfehlern trennen.
Den Vertrag zwischen den drei Koordinatensystemen festlegen
Unterscheiden Sie drei Objekte: das Pixelrechteck des Originalbilds, das tatsächlich vom Bild belegte Rechteck in der Ansicht und die vom Nutzer aufgezogene Auswahl. Die letzten beiden müssen im selben Koordinatensystem liegen. Es darf sich also nicht eines auf die übergeordnete Ansicht und das andere auf den Bildcontainer beziehen.
Dieser Artikel behandelt ausschließlich die proportionale Einpassung, nicht das flächenfüllende Zuschneiden, Drehungen oder perspektivische Transformationen. Der Ursprung liegt oben links, die horizontale Achse verläuft nach rechts und die vertikale nach unten. Die Ausrichtung des Eingabebilds ist bereits für die Anzeige normalisiert. Breite und Höhe stammen aus den tatsächlichen Pixelmaßen, nicht aus der Größe des Bildobjekts in Punkten.
Der Skalierungsfaktor des Bildschirms ist nicht der Skalierungsfaktor des Bilds. Die Auswahl mit dem Bildschirmfaktor zu multiplizieren, funktioniert nur bei zufällig passenden Abmessungen und ist keine allgemeingültige Formel für den Bildzuschnitt.
Bei verschachtelten Ansichten müssen Sie die Auswahl zuerst über die Schnittstellen zur Koordinatenumrechnung in das Koordinatensystem des Bildcontainers übertragen. Rufen Sie erst danach die folgende Funktion auf. Sind Zoomgesten möglich, müssen zuvor außerdem Verschiebung und Skalierung rückgängig gemacht werden. Ein bereits transformiertes Rechteck lässt sich nicht direkt in die statische Formel einsetzen.
Anzeigerechteck und Pixelumrechnung implementieren
Speichern Sie den folgenden Inhalt als CropCheck.swift. Ist die vollständige Xcode-Toolchain verfügbar, führen Sie im Terminal xcrun swift CropCheck.swift aus. Der Code benötigt lediglich das systemseitige Foundation-Framework, greift nicht auf das Netzwerk zu und kommt ohne Simulator aus.
import Foundation
func cropPixels(
source: CGSize,
viewport: CGSize,
selection: CGRect
) -> CGRect? {
let values = [
source.width, source.height,
viewport.width, viewport.height,
selection.origin.x, selection.origin.y,
selection.width, selection.height
]
guard values.allSatisfy({ $0.isFinite }),
source.width > 0, source.height > 0,
viewport.width > 0, viewport.height > 0,
source.width.rounded(.down) == source.width,
source.height.rounded(.down) == source.height,
selection.width > 0, selection.height > 0
else {
return nil
}
let scale = min(
viewport.width / source.width,
viewport.height / source.height
)
guard scale.isFinite, scale > 0 else {
return nil
}
let display = CGRect(
x: (viewport.width - source.width * scale) / 2,
y: (viewport.height - source.height * scale) / 2,
width: source.width * scale,
height: source.height * scale
)
let clipped = selection.intersection(display)
guard !clipped.isNull, !clipped.isEmpty else {
return nil
}
let left = max(
0, floor((clipped.minX - display.minX) / scale)
)
let top = max(
0, floor((clipped.minY - display.minY) / scale)
)
let right = min(
source.width,
ceil((clipped.maxX - display.minX) / scale)
)
let bottom = min(
source.height,
ceil((clipped.maxY - display.minY) / scale)
)
guard right > left, bottom > top else {
return nil
}
return CGRect(
x: left, y: top,
width: right - left, height: bottom - top
)
}
Die Reihenfolge ist verbindlich: Zuerst wird die Schnittmenge aus Auswahl und Anzeigebereich gebildet, danach der Versatz des Anzeigebereichs abgezogen und zuletzt durch den Skalierungsfaktor geteilt. Werden die Ansichtskoordinaten vorher gerundet, vergrößert sich der Fehler entsprechend dem Größenverhältnis zwischen Originalbild und Ansicht.
Die Pixelgrenzen werden nach außen gerundet, damit alle von der Auswahl berührten Pixel enthalten sind. Dadurch bleiben die mathematischen Abmessungen der Auswahl nicht zwingend exakt erhalten. Verlangt das Produkt feste Ausgabeabmessungen, sollte die Skalierung beim Export als separater nachgelagerter Schritt erfolgen.
Versatzfehler mit nachrechenbaren Beispielen abfangen
Hängen Sie die folgenden Assertions an dieselbe Datei an. Bei erfolgreicher Ausführung gibt es keine Ausgabe. Schlägt eine Assertion fehl, endet der Prozess mit einem Fehlerstatus. Führen Sie dieses Abnahmeskript nicht mit Optimierungseinstellungen aus, die Assertions entfernen.
let source = CGSize(width: 4000, height: 3000)
let viewport = CGSize(width: 300, height: 300)
func mapped(_ rectangle: CGRect) -> CGRect? {
cropPixels(
source: source,
viewport: viewport,
selection: rectangle
)
}
assert(
mapped(CGRect(x: 75, y: 75, width: 150, height: 150))
== CGRect(x: 1000, y: 500, width: 2000, height: 2000)
)
assert(
mapped(CGRect(x: 0, y: 0, width: 300, height: 300))
== CGRect(x: 0, y: 0, width: 4000, height: 3000)
)
assert(
mapped(CGRect(x: 10, y: 0, width: 20, height: 20))
== nil
)
assert(
mapped(CGRect(x: 0, y: 0, width: 0, height: 20))
== nil
)
assert(
mapped(CGRect(x: 225, y: 225, width: 100, height: 100))
== CGRect(x: 3000, y: 2500, width: 1000, height: 500)
)
Das Beispiel für die mittige Auswahl lässt sich von Hand nachrechnen: Der Anzeigefaktor beträgt 0.075. Nach Abzug des oberen Leerraums liegt der vertikale Auswahlursprung bei 37.5 Punkten. Das entspricht einer vertikalen Koordinate von 500 Pixeln im Originalbild. Das letzte Beispiel überschreitet zugleich den rechten und den unteren Rand. Es belegt, dass die Funktion tatsächlich die überstehenden Teile abschneidet und nicht nur den Ursprung begrenzt.
Ergänzen Sie Tests mit einem Hochformatbild, einem nicht quadratischen Container und einer Auswahl mit gebrochenen Koordinaten. Prüfen Sie für jedes gültige Ergebnis, dass Breite und Höhe größer als null sind, die Grenzen innerhalb des Originalbilds liegen und alle vier Kanten ganzzahlige Koordinaten haben. Eingaben mit negativer Breite oder Höhe werden hier direkt abgewiesen. Die Oberfläche muss die Auswahl entsprechend der Ziehrichtung bereits vorher normalisieren.
Rechenergebnis und tatsächlichen Export getrennt prüfen
Wenn die mathematischen Tests bestanden sind, erstellen Sie ein Testbild mit festen Pixelmaßen, unterschiedlichen Farben an den vier Ecken und einem Kreuz in der Mitte. Exportieren Sie zunächst in einem verlustfreien Format, damit verlustbehaftete Komprimierung die Farbprüfungen nicht durch Störungen verfälscht.
Prüfen Sie drei Ergebnisse: Die Exportabmessungen stimmen mit dem Rückgabewert der Funktion überein; ein Zuschnitt unten rechts enthält keine Markierung aus der linken oberen Ecke; bei Auswahl des gesamten Bilds entstehen keine zusätzlichen transparenten Ränder. Vergleichen Sie nicht nur die Dateibytes. Änderungen an den vom Encoder hinzugefügten Metadaten bedeuten nicht zwangsläufig, dass sich die Pixel geändert haben.
Arbeitet die Zuschneidefunktion direkt mit dem ausrichtungsnormalisierten Pixelbild, übergeben Sie das hier berechnete Pixelrechteck. Wird das Bild dagegen über einen Grafikkontext neu gezeichnet, prüfen Sie Ursprung, Achsenrichtung und Ausgabeskalierung gesondert. Der häufigste nachgelagerte Fehler besteht darin, dass der Exportcode erneut mit dem Anzeigefaktor multipliziert, obwohl die Koordinatenfunktion bereits Pixelwerte geliefert hat.
Prüfen Sie auch das asynchrone Laden von Bildern: Wird nach dem Aufziehen der Auswahl ein anderes Originalbild mit abweichenden Abmessungen eingesetzt, muss die Auswahl neu berechnet oder die bisherige Auswahl ungültig gemacht werden. Kombinieren Sie nicht den Skalierungsfaktor des alten Bilds mit den Pixelmaßen des neuen.
Fehlerprotokolle in reproduzierbare Eingaben überführen
Halten Sie bei jedem Fehler die Pixelbreite und -höhe des Originalbilds, die Containergröße in Punkten, das Auswahlrechteck, den tatsächlich verwendeten Anzeigemodus und das Rechenergebnis fest. Die Zahlenwerte genügen; Nutzerfotos müssen nicht hochgeladen werden. Verwenden Sie zur Reproduktion stattdessen ein Testbild mit denselben Abmessungen.
Decken Sie vor dem Zusammenführen der Änderungen mindestens sechs Eingabekategorien ab: Querformat, Hochformat, Auswahl des gesamten Bilds, Auswahl ausschließlich im Leerraum, Überschreiten der Bildränder und gebrochene Grenzkoordinaten. Nach einer Layoutänderung müssen die Containermaße neu ausgelesen werden. Die vor einer Drehung gespeicherten Werte dürfen nicht weiterverwendet werden.
Die abschließende Abnahme besteht aus zwei Ebenen: Die reine Funktion überprüft die Koordinatenbeziehungen, die Exporttests überprüfen Pixelausrichtung und Abmessungen. Erst wenn beide Ebenen bestanden sind, prüfen Sie per Remotedesktop, wie sich das Aufziehen der Auswahl anfühlt. So lassen sich Fehler bei Oberflächenanpassungen, beim Austausch des Originalbilds und bei Änderungen am Export jeweils klar zuordnen.
Häufig gestellte Fragen
Müssen Zuschnittkoordinaten mit der Bildschirmskalierung multipliziert werden?
Nicht direkt. Bestimmen Sie das Verhältnis aus den Quellmaßen in Pixeln und dem tatsächlich dargestellten Bildrechteck. Die Bildschirmskalierung beschreibt ein anderes Verhältnis.
Warum werden Pixelgrenzen nach außen gerundet?
Abgerundete Untergrenzen und aufgerundete Obergrenzen erfassen alle berührten Pixel. Begrenzen Sie das gerundete Rechteck anschließend auf die Quellmaße.
Führen Sie Ihre Projekte auf einem dedizierten physischen Mac aus
Erledigen Sie Builds, Tests und Remote-Desktop-Aufgaben auf einem Mac in der Cloud. Wählen Sie Ausstattung und Abrechnungszeitraum passend zu Ihrem Projekt. Den aktuellen Verfügbarkeitsstatus sehen Sie direkt in der Konsole.