Je crée un context bitmap en utilisant CGBitmapContextCreate
avec l'option kCGImageAlphaPremultipliedFirst
.
J'ai fait une image de test 5 x 5 avec quelques colors principales (rouge pur, vert, bleu, blanc, noir), quelques colors mélangées (ie violet) combinées avec quelques variations alpha. Chaque fois que le composant alpha n'est pas 255, la valeur de couleur est erronée.
J'ai trouvé que je pouvais recalculer la couleur quand je fais quelque chose comme:
almostCorrectRed = wrongRed * (255 / alphaValue); almostCorrectGreen = wrongGreen * (255 / alphaValue); almostCorrectBlue = wrongBlue * (255 / alphaValue);
Mais le problème est que mes calculs sont parfois décalés de 3 ou même plus. Ainsi, par exemple, je reçois une valeur de 242 au lieu de 245 pour le vert, et je suis sûr à 100% qu'il doit être exactement de 245. Alpha est 128.
Ensuite, pour la même couleur juste avec une opacité alpha différente dans le bitmap PNG, je reçois alpha = 255 et vert = 245 comme il se doit.
Si alpha est 0, alors rouge, vert et bleu sont également 0. Ici, toutes datatables sont perdues et je n'arrive pas à comprendre la couleur du pixel.
Comment puis-je éviter ou annuler cette prémultiplication alpha pour que je puisse modifier les pixels de mon image en fonction des vraies valeurs de pixels RVB telles qu'elles étaient lors de la création de l'image dans Photoshop? Comment puis-je récupérer les valeurs d'origine pour R, G, B et A?
Informations de base (probablement pas nécessaire pour cette question):
Ce que je fais est ceci: je prends un UIImage, le dessine dans un context bitmap afin d'y effectuer des algorithms simples de manipulation d'image, en changeant la couleur de chaque pixel en fonction de quelle couleur il était auparavant. Rien de vraiment spécial. Mais mon code a besoin des vraies colors. Quand un pixel est transparent (c'est-à-dire qu'il a un alpha inférieur à 255) mon algorithm ne devrait pas se soucier de cela, il devrait juste modifier R, G, B comme nécessaire alors que Alpha rest à ce qu'il est. Parfois, il va aussi déplacer l'alpha vers le haut ou vers le bas. Mais je les vois comme deux choses distinctes. Alpha contors la transparence, tandis que RGB contrôle la couleur.
C'est un problème fondamental avec la prémultiplication dans un type intégral:
Je ne suis pas sûr pourquoi vous obtenez 242 au lieu de 243, mais ce problème rest de toute façon, et il s'aggrave plus bas l'alpha va.
La solution consiste à utiliser des composants à floating point à la place. Le Guide de programmation Quartz 2D donne tous les détails du format que vous devrez utiliser .
Point important: Vous devez utiliser le point flottant à partir de la création de l'image originale (et je ne pense pas qu'il soit même possible de sauvegarder une telle image au format PNG, vous devrez peut-être utiliser le format TIFF). Une image qui était déjà prémultipliée dans un type intégral a déjà perdu cette précision; il n'y a pas de return.
Le cas zéro-alpha est la version extrême de ceci, à tel point que même le point flottant ne peut pas vous aider. Tout ce qui est zéro (alpha) est zéro, et il n'y a pas de récupération de la valeur originale non multipliée à partir de ce point.
La pré-multiplication alpha avec un type de couleur entier est une opération avec perte d'information. Les données sont détruites pendant le process de quantification (arrondi à 8 bits).
Comme certaines données sont détruites (en arrondissant), il n'y a aucun moyen de récupérer la couleur de pixel d'origine exacte (à l'exception de certaines valeurs chanceuses). Vous devez save les colors de votre image Photoshop avant de l'insert dans un context bitmap et utiliser datatables de couleur d'origine, et non datatables de colors multipliées de l'image bitmap.
J'ai rencontré ce même problème en essayant de lire des données d'image, de les restituer à une autre image avec CoreGraphics, puis d'save le résultat sous forme de données non prémultipliées. La solution que j'ai trouvée qui a fonctionné pour moi était de sauvegarder une table qui contient le mappage exact que CoreGraphics utilise pour mapper des données non prémultipliées à des données prémultipliées. Ensuite, estimez la valeur prémultiplée d'origine avec un appel mult et floor (). Ensuite, si l'estimation et le résultat de la search dans la table ne correspondent pas, vérifiez simplement la valeur sous l'estimation et celle au-dessus de l'estimation dans le tableau pour la correspondance exacte.
// Execute premultiply logic on RGBA components split into componenets. // For example, a pixel RGB (128, 0, 0) with A = 128 // would return (255, 0, 0) with A = 128 static inline uint32_t premultiply_bgra_inline(uint32_t red, uint32_t green, uint32_t blue, uint32_t alpha) { const uint8_t* const ressortingct alphaTable = &extern_alphaTablesPtr[alpha * PREMULT_TABLEMAX]; uint32_t result = (alpha << 24) | (alphaTable[red] << 16) | (alphaTable[green] << 8) | alphaTable[blue]; return result; } static inline int unpremultiply(const uint32_t premultRGBComponent, const float alphaMult, const uint32_t alpha) { float multVal = premultRGBComponent * alphaMult; float floorVal = floor(multVal); uint32_t unpremultRGBComponent = (uint32_t)floorVal; assert(unpremultRGBComponent >= 0); if (unpremultRGBComponent > 255) { unpremultRGBComponent = 255; } // Pass the unpremultiplied estimated value through the // premultiply table again to verify that the result // maps back to the same rgb component value that was // passed in. It is possible that the result of the // multiplication is smaller or larger than the // original value, so this will either add or remove // one int value to the result rgb component to account // for the error possibility. uint32_t premultPixel = premultiply_bgra_inline(unpremultRGBComponent, 0, 0, alpha); uint32_t premultActualRGBComponent = (premultPixel >> 16) & 0xFF; if (premultRGBComponent != premultActualRGBComponent) { if ((premultActualRGBComponent < premultRGBComponent) && (unpremultRGBComponent < 255)) { unpremultRGBComponent += 1; } else if ((premultActualRGBComponent > premultRGBComponent) && (unpremultRGBComponent > 0)) { unpremultRGBComponent -= 1; } else { // This should never happen assert(0); } } return unpremultRGBComponent; }
Vous pouvez find la table de valeurs statique complète sur ce lien github .
Notez que cette approche ne récupérera pas les informations "perdues" lorsque le pixel original non multiplié a été prémultiplié. Mais, il renvoie le plus petit pixel non multiplié qui deviendra le pixel prémultiplié une fois parcouru la logique prémultiplée à nouveau. Ceci est utile lorsque le sous-système graphique accepte uniquement les pixels prémultipliés (comme CoreGraphics sur OSX). Si le sous-système graphique accepte uniquement les pixels prémultiplés, il est préférable de ne stocker que les pixels prémultiplés, car less d'espace est consommé par rapport aux pixels non multipliés.