Display P3 nach sRGB: iOS-Bildexport auf einem Cloud-Mac prüfen
Dasselbe Foto sieht in der Vorschau richtig aus, ist nach dem Export aber zu dunkel? Dann ist eine höhere JPEG-Qualität nicht der erste Ansatz. Wer Pixeln aus Display P3 einfach ein sRGB-Profil zuweist, verändert ihre farbliche Bedeutung. Fehlt das Profil ganz, muss die empfangende Anwendung raten. Ein Cloud-Mac kann die Regressionstests für diese Verarbeitungskette übernehmen: feste Farbfelder erzeugen, eine echte Konvertierung durchführen und die ausgelieferten Dateien zur Prüfung wieder einlesen. Der Bildeindruck auf dem Remote-Desktop ist dafür kein verlässlicher Maßstab.
Zuerst die Ausgabeanforderungen festlegen
Dieser Test beschränkt sich auf deckende RGB-Bilder mit Standarddynamikumfang. Das Ausgabeziel ist eine sRGB-JPEG-Datei mit eingebettetem Farbprofil. Transparente Bilder, Bilder mit erweitertem Dynamikumfang und Screenshots erhalten eigene Testfälle und gehören nicht in diese Basisprüfung.
Drei Schritte müssen getrennt bleiben: den Quellfarbraum auslesen, in den Zielfarbraum zeichnen und die Datei kodieren. Die ersten beiden Schritte bestimmen, welche Farben die Pixel repräsentieren; erst der letzte bestimmt die Kompressionsverluste. Fehlt der Eingabe ein verlässliches Profil, muss sie gemäß den Produktvorgaben abgelehnt oder ihre Farbraumzuordnung ausdrücklich ergänzt werden. „Unbekannt“ darf nicht stillschweigend als Display P3 gelten.
Ein zugewiesenes Profil legt fest, wie Pixelwerte zu interpretieren sind. Eine Farbkonvertierung verändert die Pixelwerte. Das sind zwei unterschiedliche Vorgänge, die getrennt getestet werden müssen.
Auch der Rendering Intent muss feststehen. Farben außerhalb des sRGB-Farbumfangs lassen sich nicht unverändert erhalten. Clipping ist deshalb nicht automatisch ein Implementierungsfehler. Ebenso wenig darf man verlangen, dass RGB-Werte in unterschiedlichen Farbräumen identisch bleiben.
Die Konvertierung mit synthetischen Farbfeldern prüfen
Speichern Sie den folgenden Inhalt auf einem Cloud-Mac mit bereits installierten Befehlszeilen-Entwicklungswerkzeugen als color-check.swift. Das Skript verwendet ausschließlich Systemframeworks, liest keine privaten Fotos und benötigt keinen Netzwerkzugriff.
import Foundation
import CoreGraphics
import ImageIO
import UniformTypeIdentifiers
let size = 64
let p3 = CGColorSpace(name: CGColorSpace.displayP3)!
let srgb = CGColorSpace(name: CGColorSpace.sRGB)!
let layout = CGBitmapInfo.byteOrder32Big.rawValue
| CGImageAlphaInfo.premultipliedLast.rawValue
func context(_ space: CGColorSpace) -> CGContext {
CGContext(
data: nil, width: size, height: size,
bitsPerComponent: 8, bytesPerRow: size * 4,
space: space, bitmapInfo: layout
)!
}
func pixel(_ context: CGContext) -> [Int] {
let bytes = context.data!.assumingMemoryBound(to: UInt8.self)
let offset = (32 * context.bytesPerRow) + 32 * 4
return (0..<3).map { Int(bytes[offset + $0]) }
}
let source = context(p3)
source.setFillColor(CGColor(
colorSpace: p3, components: [0.8, 0.4, 0.2, 1.0]
)!)
source.fill(CGRect(x: 0, y: 0, width: size, height: size))
let original = source.makeImage()!
let converted = context(srgb)
converted.setRenderingIntent(.relativeColorimetric)
converted.draw(
original, in: CGRect(x: 0, y: 0, width: size, height: size)
)
let reference = pixel(converted)
precondition(reference != pixel(source), "Conversion was not observed")
let url = URL(fileURLWithPath: "srgb-check.jpg")
let output = CGImageDestinationCreateWithURL(
url as CFURL, UTType.jpeg.identifier as CFString, 1, nil
)!
CGImageDestinationAddImage(output, converted.makeImage()!, [
kCGImageDestinationLossyCompressionQuality: 0.95
] as CFDictionary)
precondition(CGImageDestinationFinalize(output), "Encoding failed")
let input = CGImageSourceCreateWithURL(url as CFURL, nil)!
let decoded = CGImageSourceCreateImageAtIndex(input, 0, nil)!
precondition(decoded.width == size && decoded.height == size)
precondition(decoded.colorSpace?.model == .rgb)
let readback = context(srgb)
readback.draw(
decoded, in: CGRect(x: 0, y: 0, width: size, height: size)
)
let actual = pixel(readback)
let error = zip(reference, actual).map { abs($0 - $1) }.max()!
precondition(error <= 8, "JPEG round-trip exceeded tolerance")
let properties = CGImageSourceCopyPropertiesAtIndex(
input, 0, nil
) as NSDictionary?
let profile = properties?[kCGImagePropertyProfileName] as? String
precondition(profile?.isEmpty == false, "Missing color profile")
Führen Sie xcrun swift color-check.swift aus. Bei Erfolg endet das Skript mit dem Exitcode null und erzeugt die Testdatei. Debug-Ausgaben gibt es nicht; bei einem Fehler bricht eine Zusicherung den Lauf ab. Das erzwungene Entpacken von Optionals ist für diese kontrollierte Test-Fixture vertretbar. Im produktiven Exporter müssen Fehler beim Erzeugen oder Dekodieren dagegen als eindeutige Fehler gemeldet werden.
Konvertierung und Kompression getrennt beurteilen
Das Farbfeld ist bewusst so gewählt, dass sich die Zahlenwerte bei der Konvertierung von P3 nach sRGB sichtbar ändern. Die erste Zusicherung belegt lediglich, dass die Konvertierung nicht zu einem bloßen Kopieren der Kanäle verkommen ist. Sie beweist nicht, dass sämtliche Farben korrekt umgerechnet werden. Eine vollständige Testsuite braucht zusätzlich Neutralgrau, nahezu schwarze und nahezu weiße Farbfelder sowie Farben nahe den Grenzen des Zielfarbraums.
Die aus dem JPEG zurückgelesenen Werte werden nicht mit den ursprünglichen P3-Werten verglichen, sondern mit der sRGB-Referenz nach der Konvertierung. Beide Bilder werden in Kontexte desselben Formats gezeichnet; anschließend wird jeweils das mittlere Pixel ausgelesen. So führen unterschiedliche Kanalreihenfolgen oder Farbräume nicht zu falschen Fehlermeldungen.
Die im Skript festgelegte maximale Kanalabweichung von 8 gilt nur für dieses gleichmäßige Farbfeld. Sie ist kein allgemeiner Maßstab für Bildqualität. Bei echten Fotos sollten mehrere Bildbereiche statistisch ausgewertet werden. Übernehmen Sie den Grenzwert nicht unverändert für feine Linien, Text oder hochfrequente Texturen. Auch ein vorhandener Profilname ist nur eine Mindestprüfung: Aus dem Namen allein lässt sich nicht ableiten, ob der Profilinhalt korrekt ist.
Den tatsächlichen Exporter der Anwendung einbinden
Das eigenständige Skript prüft die Verarbeitungskette des Systems. Im nächsten Schritt muss dieselbe P3-Fixture die Exportmethode durchlaufen, die die Anwendung tatsächlich verwendet. Der Test liest dann die von ihr erzeugte Datei ein, statt selbst einen zweiten, vermeintlich „korrekten Export“ zu implementieren.
Sinnvoll sind zwei Gruppen von Zusicherungen. Die Strukturprüfungen kontrollieren erfolgreiche Dekodierung, Abmessungen, RGB-Modell und das Vorhandensein eines Profils. Die Pixelprüfungen kontrollieren die Referenzwerte der festen Farbfelder und die Abweichung nach verlustbehafteter Kodierung und erneutem Einlesen. Ergänzen Sie einen Negativfall, der die Quellpixel nur neu kennzeichnet, ohne sie zu konvertieren. Die Pixelprüfungen müssen diesen Fall erkennen, damit die Testfreigabe nicht bloß von der Dateiendung abhängt.
Fixieren Sie zunächst die Parameter für Skalierung, Zuschnitt und Ausrichtungskorrektur im Produktionscode. In diesem Testdurchlauf darf sich nur die Farbverarbeitung ändern. Andernfalls können Größenänderungen und Interpolation die Ursache von Farbabweichungen verdecken. Bewahren Sie bei jedem Fehler die ursprüngliche Fixture, die Ausgabedatei und die Testkonfiguration auf. Erneut komprimierte Anhänge sind kein Ersatz für die Originaldateien des fehlgeschlagenen Laufs.
Die Baseline festschreiben und die Grenzen der Anzeige beachten
Halten Sie im Build-Protokoll die Betriebssystemversion, die Version der Entwicklungswerkzeuge, Quell- und Zielprofil, Rendering Intent und Kodierqualität fest. Führen Sie bei einer Aktualisierung der Laufzeitumgebung zunächst die bisherigen Testfälle im direkten Vergleich aus. Prüfen Sie, ob Änderungen aus der Konvertierung oder der Kodierung stammen, und entscheiden Sie erst danach über neue Referenzwerte. Erhöhen Sie nicht einfach die Toleranzen, nur damit ein fehlgeschlagener Test wieder besteht.
Nach bestandenen automatisierten Tests prüfen Sie echte Fotos auf einem Gerät, das den jeweiligen Farbumfang unterstützt. Ein Remote-Desktop kann zusätzliche Kodierungs- und Farbverarbeitungsschritte durchlaufen. Sein angezeigtes Bild ist deshalb keine Referenz für die tatsächlichen Pixelwerte. Umgekehrt ersetzt auch die Geräteprüfung nicht das erneute Einlesen der Datei.
Die endgültige Ausgabedatei muss drei Bedingungen zugleich erfüllen: Das Zielprofil ist erkennbar, die Pixel wurden tatsächlich konvertiert, und die verlustbedingte Abweichung bleibt innerhalb der für das Testmuster vereinbarten Grenzen. Werden diese drei Bedingungen Bestandteil der Exportertests, wird aus „sieht irgendwie falsch aus“ ein lokalisierbarer und reproduzierbarer technischer Fehler.
Häufig gestellte Fragen
Reicht es, dem Bild ein sRGB-Profil zuzuweisen?
Nein. Die Zuweisung ändert nur die Interpretation der vorhandenen Werte. Für eine Konvertierung muss das Quellbild in einen sRGB-Zielkontext gezeichnet werden.
Müssen JPEG-Pixel nach dem Rücklesen identisch sein?
Nein. JPEG ist verlustbehaftet. Beide Bilder müssen im selben sRGB-Decodierraum verglichen werden, mit einer für die festen Testbilder definierten Toleranz.
Der nächste Build auf einem exklusiven Mac.
Wählen Sie einen Cloud-Mac ohne Virtualisierung und behalten Sie Ihren gewohnten Xcode- und CI-Workflow bei. Konfigurieren Sie die Maschine passend zu Ihrer Arbeitslast; den aktuellen Verfügbarkeitsstatus sehen Sie live in der Konsole.