Auf der Fotoauswahlseite steht ein Bild hochkant, nach dem Upload liegt die Miniaturansicht jedoch quer. Ändern Sie bei einem solchen Fehler nicht gleich das Seitenverhältnis für den Zuschnitt: Ein JPEG kann Pixel im Querformat enthalten und zugleich per EXIF-Ausrichtungsangabe festlegen, dass ein Bildbetrachter sie gedreht anzeigen soll. UIImage unter iOS kann das Bild richtig darstellen; der Exportcode oder die empfangende Seite behandelt es aber nicht zwangsläufig genauso. Im Folgenden erzeugen wir in einem iOS-Test-Target auf einem Cloud-Mac ein reproduzierbares Testbild und prüfen getrennt, ob die Anzeige und die exportierte Datei korrekt sind.
Ausrichtungsangabe und Pixel unterscheiden
Halten Sie drei Werte fest: die EXIF-Ausrichtung der Datei, UIImage.imageOrientation nach dem Dekodieren sowie Pixelbreite und -höhe des zugrunde liegenden CGImage. Der EXIF-Ausrichtungswert 6 bedeutet, dass das Bild zur Anzeige im Uhrzeigersinn gedreht werden muss. Er bedeutet nicht, dass die Pixel bereits gedreht wurden. Sind die zugrunde liegenden Pixel 120 breit und 80 hoch, muss das korrekt angezeigte Bild 80 breit und 120 hoch sein.
Beurteilen Sie die Korrektur nicht allein anhand eines Screenshots der Vorschau. Die Vorschaukomponente kann die Ausrichtungsangabe berücksichtigen, während nachfolgende Verarbeitungsschritte weiterhin die ursprünglichen Pixel weitergeben.
Klären Sie zuerst, was erhalten bleiben muss: Wird die Originaldatei benötigt, speichern Sie sie getrennt von der normalisierten Fassung. Wird nur ein Upload-Bild mit einheitlicher Ausrichtung benötigt, zeichnen Sie es vor dem Export neu und prüfen Sie die exportierte Datei nach erneutem Dekodieren. Setzen Sie den Ausrichtungswert der Originaldatei nicht einfach auf 1 – dadurch bewegt sich kein Pixel.
Ein reproduzierbares Testbild im iOS-Test erzeugen
Fügen Sie den folgenden Test einem iOS-Unit-Test-Target mit UIKit-Unterstützung hinzu. Er erzeugt zur Laufzeit ein JPEG mit 120×80 Pixeln und schreibt den Ausrichtungswert 6 hinein. Damit ist er weder auf Fotos vom Rechner der Entwickler angewiesen, noch gelangen echte Bilder oder Nutzerdaten ins Repository. Der Code prüft außerdem die mit ImageIO gelesene Angabe und die Ausrichtung nach der Dekodierung durch UIKit.
import XCTest
import UIKit
import ImageIO
import UniformTypeIdentifiers
final class ImageOrientationTests: XCTestCase {
func testOrientationSurvivesExport() throws {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
format.opaque = true
let source = UIGraphicsImageRenderer(
size: CGSize(width: 120, height: 80),
format: format
).image { _ in
UIColor.red.setFill()
UIRectFill(CGRect(x: 0, y: 0, width: 60, height: 80))
UIColor.blue.setFill()
UIRectFill(CGRect(x: 60, y: 0, width: 60, height: 80))
}
let jpeg = NSMutableData()
let destination = try XCTUnwrap(
CGImageDestinationCreateWithData(
jpeg, UTType.jpeg.identifier as CFString, 1, nil
)
)
CGImageDestinationAddImage(
destination,
try XCTUnwrap(source.cgImage),
[kCGImagePropertyOrientation: 6] as CFDictionary
)
XCTAssertTrue(CGImageDestinationFinalize(destination))
let input = jpeg as Data
let imageSource = try XCTUnwrap(
CGImageSourceCreateWithData(input as CFData, nil)
)
let properties = try XCTUnwrap(
CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
as? [CFString: Any]
)
XCTAssertEqual(
(properties[kCGImagePropertyOrientation] as? NSNumber)?.intValue,
6
)
let decoded = try XCTUnwrap(UIImage(data: input))
XCTAssertEqual(decoded.imageOrientation, .right)
XCTAssertEqual(decoded.cgImage?.width, 120)
XCTAssertEqual(decoded.cgImage?.height, 80)
let upright = UIGraphicsImageRenderer(
size: CGSize(width: 80, height: 120),
format: format
).image { _ in
decoded.draw(in: CGRect(x: 0, y: 0, width: 80, height: 120))
}
XCTAssertEqual(upright.imageOrientation, .up)
let exported = try XCTUnwrap(upright.pngData())
let reopened = try XCTUnwrap(UIImage(data: exported))
XCTAssertEqual(reopened.imageOrientation, .up)
XCTAssertEqual(reopened.cgImage?.width, 80)
XCTAssertEqual(reopened.cgImage?.height, 120)
}
}
Prüfen Sie vor dem Start, ob die Swift-Datei im Test-Target enthalten ist, und wählen Sie einen vom Projekt unterstützten iOS-Simulator. Tragen Sie beim Aufruf über die Befehlszeile für -workspace, -scheme und -destination die tatsächlichen Werte des Projekts ein. Gehen Sie nicht davon aus, dass ein beispielhaft genannter Gerätename auf jedem Rechner als Testziel verfügbar ist.
Die Prüfung an den tatsächlichen Exportpfad anbinden
Der obige Test zeigt, dass die Normalisierung funktioniert. Er belegt noch nicht, dass der Anwendungscode sie tatsächlich aufruft. Verlagern Sie deshalb „Dekodieren, in der Zielgröße zeichnen, Kodieren“ in die vorhandene Exportfunktion des Projekts und lassen Sie den Test dieselbe Funktion aufrufen. Die Eingabedaten können weiterhin zur Laufzeit entstehen. Die Assertions müssen sich jedoch auf die erneut geöffnete Exportdatei beziehen, nicht auf das UIImage vor der Rückgabe aus der Funktion.
Einen eindeutigen Größenvertrag für die Anwendungsfunktion festlegen
Legen Sie zunächst fest, dass die Funktion die Daten der Originaldatei entgegennimmt und Daten mit eindeutig festgelegter Anzeigeausrichtung zurückgibt. Beim Testbild mit Ausrichtung 6 müssen aus 120×80 Ausgabepixeln 80×120 werden. Skaliert die Anwendung das Bild zusätzlich, prüfen Sie stattdessen das Seitenverhältnis und die exakte Zielgröße nach der Skalierung; testen Sie die Skalierungsregeln separat. Fassen Sie Fehler bei Drehung, Komprimierung und Zuschnitt nicht in einer einzigen Assertion zusammen.
Die zwei Farben links und rechts helfen, eine Spiegelung des Bildinhalts bei der Sichtprüfung zu erkennen. Soll auch ein automatischer Test die Farbpositionen prüfen, verwenden Sie einen Pixelvergleich mit Toleranz: Die verlustbehaftete JPEG-Kodierung verändert Farben in der Nähe der Grenze. Eine bitgenaue Übereinstimmung jedes Farbkanals ist daher keine sinnvolle Anforderung.
Abweichungen zwischen Vorschau und Export zurückverfolgen
Finden Sie zuerst heraus, an welcher Stelle das Bild seine Repräsentation wechselt: Dateidaten, UIImage, CGImage, Zeichenkontext oder die neu kodierte Datei. Protokollieren Sie bei jeder Umwandlung Ausrichtung und Pixelmaße. So lässt sich häufig eine Abzweigung wie „cgImage entnehmen und direkt kodieren“ finden: cgImage liefert die Pixel. Dass UIImage beim Zeichnen die Ausrichtung berücksichtigt, heißt nicht, dass die Drehung bereits in den Pixeln steckt.
Prüfen Sie auch das Exportergebnis. Durch erneutes Zeichnen entstehen neue Pixel und Dateidaten; außerdem können sich Kodierformat, Metadaten und Dateigröße ändern. Muss der Workflow weitere Metadaten der Originaldatei bewahren, listen Sie die erforderlichen Felder einzeln auf und schreiben Sie Tests dafür. Gehen Sie nicht davon aus, dass beim Neuzeichnen alles unverändert übernommen wird. Dekodieren Sie die endgültige Datei nach Möglichkeit noch einmal vor dem Versand: Ein korrekt angezeigtes Zwischenobjekt ist nicht das ausgelieferte Ergebnis.
Checkliste vor dem Merge
Decken Sie mindestens ein normales Bild mit Ausrichtung 1, ein gedrehtes Bild mit Ausrichtung 6 und die tatsächlich von der Anwendung zugelassenen gespiegelten Ausrichtungen ab. Prüfen Sie für jedes Testbild die Eingabeangabe, die Pixelbreite und -höhe nach der Normalisierung sowie die Anzeigeausrichtung nach erneutem Dekodieren. Falls der Produktionscode Zuschnitte erlaubt, ergänzen Sie ein weiteres nicht quadratisches Testbild: Bei einem Quadrat fällt eine fehlerhafte Drehung in der Prüfung von Breite und Höhe nicht auf.
Führen Sie den Test abschließend in demselben iOS-Test-Target aus, das auch im Auslieferungsprozess verwendet wird. Bei einem Fehler genügt es, Eingabeausrichtung, Ausgabegröße und Verarbeitungsschritt festzuhalten; schreiben Sie keine Originalfotos von Nutzern in öffentlich zugängliche Logs. So weist ein fehlgeschlagener Regressionstest auf einen Schritt der Dateiverarbeitung hin, statt nur zu melden, dass „ein Foto falsch aussieht“.
Häufig gestellte Fragen
Warum ist ein Foto in UIImage richtig ausgerichtet, nach dem Export aber gedreht?
UIImage berücksichtigt beim Zeichnen die Ausrichtungsmetadaten. Ein Empfänger kann die exportierte Datei anders auswerten. Dekodieren Sie die Ausgabe erneut und prüfen Sie ihre Pixel.
Reicht es, den Ausrichtungswert auf 1 zu setzen?
Nein. Bleiben die Pixel unverändert, ändert ein neuer Metadatenwert nur die Darstellung. Zeichnen Sie das Bild unter Beachtung der ursprünglichen Ausrichtung neu.
Lassen Sie Ihren nächsten Build auf einem dedizierten Cloud-Mac laufen.
Vergleichen Sie ViewMini-Modelle, Standorte und Laufzeiten und wählen Sie den physischen Rechner, der zu Ihrem Workflow passt.