Vérifier la conversion Display P3 vers sRGB sur un Mac cloud
Une même photo s’affiche correctement dans Aperçu, mais paraît plus sombre après l’exportation ? Avant d’augmenter la qualité JPEG, vérifiez la gestion des couleurs. Attribuer directement une étiquette sRGB à des pixels Display P3 change leur signification colorimétrique ; supprimer le profil laisse au logiciel destinataire le soin de deviner comment les interpréter. Un Mac cloud peut servir à tester cette chaîne de traitement contre les régressions : générer des aplats fixes, effectuer une véritable conversion, puis relire le fichier livré pour le vérifier, plutôt que de se fier au rendu du bureau à distance.
Définir d’abord le contrat de sortie
Cet article se limite à la validation d’images RGB opaques à plage dynamique standard, avec pour cible un JPEG sRGB doté d’un profil colorimétrique. Les images transparentes, les images à plage dynamique étendue et les captures d’écran doivent disposer de leurs propres jeux d’essai, sans être mélangées à cette base de référence.
Distinguez trois opérations : lire l’espace colorimétrique source, dessiner dans l’espace colorimétrique cible, puis encoder le fichier. Les deux premières déterminent les couleurs représentées par les pixels ; seule la dernière détermine les pertes dues à la compression. Si l’entrée ne possède pas de profil fiable, il faut, selon les règles du produit, la refuser ou renseigner explicitement son espace source. Une source « inconnue » ne doit jamais être considérée comme Display P3 par défaut.
Le profil indique comment interpréter les pixels ; la conversion colorimétrique modifie leurs valeurs. Ce sont deux opérations distinctes, qui doivent être testées séparément.
Fixez également l’intention de rendu. Les couleurs situées hors du gamut sRGB ne peuvent pas être conservées à l’identique : un écrêtage n’est donc pas nécessairement une erreur d’implémentation. Il ne faut pas non plus exiger que les valeurs RGB restent identiques d’un gamut à l’autre.
Tester la conversion avec un aplat synthétique
Sur un Mac cloud où les outils de développement en ligne de commande sont déjà installés, enregistrez le contenu suivant dans color-check.swift. Le script utilise uniquement les frameworks système, ne lit aucune photo personnelle et ne dépend pas du réseau.
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")
Exécutez xcrun swift color-check.swift. En cas de réussite, le code de sortie est zéro et le fichier de test est créé. Le script n’affiche aucun message de débogage ; en cas d’échec, les assertions interrompent la tâche. Le déballage forcé des valeurs optionnelles convient ici à un jeu d’essai contrôlé. Dans un exportateur de production, les échecs de création ou de décodage doivent être remontés sous forme d’erreurs explicites.
Distinguer les erreurs de conversion des pertes de compression
L’aplat a été choisi pour rendre visible la variation des valeurs entre P3 et sRGB. La première assertion prouve uniquement que la conversion ne s’est pas réduite à une copie directe des canaux ; elle ne démontre pas que toutes les couleurs sont converties correctement. Une suite de tests complète doit également inclure des gris neutres, des tons proches du noir et du blanc, ainsi que des aplats proches des limites du gamut cible.
Les valeurs relues dans le JPEG sont comparées à la référence sRGB obtenue après conversion, et non aux valeurs P3 d’origine. Les deux images sont dessinées dans des contextes de même format, puis le pixel central est lu, afin d’éviter les faux échecs dus à des différences d’ordre des canaux ou d’espace colorimétrique.
La tolérance maximale de 8 par canal utilisée dans le script ne vaut que pour cet aplat uniforme ; ce n’est pas un critère général de qualité d’image. Pour de vraies photos, mesurez les écarts sur plusieurs zones d’échantillonnage. Ne réutilisez pas ce seuil tel quel pour les traits fins, le texte ou les textures à haute fréquence. De même, la présence d’un nom de profil n’est qu’une vérification minimale : le nom seul ne garantit pas que le contenu du profil est correct.
Tester le véritable exportateur de l’application
Le script autonome valide la chaîne de traitement du système. L’étape suivante consiste à fournir le même échantillon P3 à la méthode d’exportation réellement utilisée par l’application, puis à faire lire au test le fichier qu’elle produit, au lieu de réimplémenter un « export correct » dans le test.
Conservez de préférence deux groupes d’assertions. Le groupe structurel vérifie la réussite du décodage, les dimensions, le modèle RGB et la présence d’un profil ; le groupe consacré aux pixels vérifie les résultats de référence des aplats fixes et l’écart après relecture avec pertes. Ajoutez un cas négatif : attribuez simplement un autre profil aux pixels source, sans les convertir, et assurez-vous que le groupe de vérifications des pixels détecte l’erreur. Le contrôle ne doit pas se limiter à l’extension du fichier.
Fixez d’abord les paramètres de redimensionnement, de recadrage et de correction d’orientation dans le code de production. Pour cette série de tests, seul le traitement des couleurs doit varier ; sinon, les changements de dimensions et l’interpolation risquent de masquer l’origine d’une dérive colorimétrique. À chaque échec, conservez l’échantillon d’origine, le fichier de sortie et la configuration du test. Ne remplacez pas les fichiers ayant servi au test par des pièces jointes recompressées.
Stabiliser la référence et préciser les limites de l’affichage
Consignez dans les informations de build la version du système, celle des outils de développement, les profils source et cible, l’intention de rendu et la qualité d’encodage. Lors d’une mise à jour de l’environnement d’exécution, commencez par exécuter les anciens échantillons dans les deux environnements. Déterminez si les différences viennent de la conversion ou de l’encodage avant de décider d’actualiser les valeurs de référence ; n’élargissez pas simplement les seuils pour faire disparaître les échecs.
Une fois les tests automatisés réussis, vérifiez de vraies photos sur un appareil prenant en charge le gamut concerné. Le bureau à distance peut ajouter un encodage et un traitement colorimétrique supplémentaires : son rendu ne constitue pas une référence pour les valeurs des pixels. La vérification sur appareil ne remplace pas non plus la relecture du fichier.
Le fichier livré doit finalement respecter trois conditions : le profil cible est identifiable, les pixels ont effectivement été convertis et les pertes restent dans les limites définies pour l’échantillon. Inscrivez ces trois exigences dans les tests de l’exportateur : une impression de « couleurs incorrectes » devient alors un échec technique localisable et reproductible.
Questions fréquentes
Attribuer un profil sRGB suffit-il à convertir une image ?
Non. L’attribution change l’interprétation des valeurs existantes. La conversion demande de dessiner l’image source dans un contexte cible sRGB.
Les pixels doivent-ils être identiques après relecture du JPEG ?
Non, car JPEG est un format avec pertes. Comparez les images dans le même espace sRGB, avec une tolérance définie pour vos échantillons fixes.
Réservez un Mac dédié à votre prochain build.
Choisissez un Mac cloud non virtualisé et conservez votre environnement Xcode et vos workflows CI. Configurez la machine selon la charge de vos projets ; sa disponibilité réelle est indiquée en temps réel dans la console.