2011-06-18 18 views
5

Estoy creando un contexto de mapa de bits usando CGBitmapContextCreate con la opción kCGImageAlphaPremultipliedFirst.¿Cómo obtener los valores reales de color RGB o ARGB sin alpha premultiplicado?

Hice una imagen de prueba de 5 x 5 con algunos colores importantes (rojo puro, verde, azul, blanco, negro), algunos colores mezclados (es decir, púrpura) combinados con algunas variaciones alfa. Cada vez que el componente alfa no es 255, el valor del color es incorrecto.

me di cuenta que podía volver a calcular el color cuando hago algo como:

almostCorrectRed = wrongRed * (255/alphaValue); 
almostCorrectGreen = wrongGreen * (255/alphaValue); 
almostCorrectBlue = wrongBlue * (255/alphaValue); 

Pero el problema es que mis cálculos son a veces fuera por 3 o incluso más. Entonces, por ejemplo, obtengo un valor de 242 en lugar de 245 para verde, y estoy 100% seguro de que debe ser exactamente 245. Alpha es 128.

Luego, para el mismo color exacto con diferente opacidad alfa en el Mapa de bits PNG, obtengo alfa = 255 y verde = 245 como debería ser.

Si alfa es 0, entonces rojo, verde y azul también son 0. Aquí todos los datos se pierden y no puedo determinar el color del píxel.

¿Cómo puedo evitar o deshacer esta premultiplicación alfa conjuntamente para poder modificar los píxeles en mi imagen en función de los verdaderos valores de píxel R G B tal como estaban cuando la imagen se creó en Photoshop? ¿Cómo puedo recuperar los valores originales para R, G, B y A?


información de fondo (probablemente no es necesario para esta pregunta):

Lo que estoy haciendo es la siguiente: Tomo un UIImage, dibuje a un contexto de mapa de bits con el fin de realizar alguna manipulación de imágenes simples algoritmos en él, cambiando el color de cada píxel según el color que tenía antes. Nada realmente especial. Pero mi código necesita los colores reales. Cuando un píxel es transparente (lo que significa que tiene un valor alfa inferior a 255) mi algoritmo no debería preocuparse por esto, solo debería modificar R, G, B según sea necesario, mientras que Alpha permanece en lo que sea. A veces, aunque también cambiará el alfa hacia arriba o hacia abajo. Pero los veo como dos cosas separadas. Alpha contorsiona la transparencia, mientras que R G B controla el color.

+0

Tuve cierto éxito al redondear el resultado al usar cualquier valor por encima de 0.5 en el siguiente entero más alto y cualquier valor por debajo en el entero más bajo. Tendrá que asegurarse de que todas las piezas de la ecuación estén en CGFloat. – jjxtra

Respuesta

5

Este es un problema fundamental con premultiplicación en un tipo integral:

  • 245 * (128/255) = 122,98
  • 122,98 truncada a un número entero = 122
  • 122 * (255/128) = 243.046875

No estoy seguro de por qué está obteniendo 242 en vez de 243, pero este problema persiste de cualquier manera, y g peor cuanto más bajo va el alfa.

La solución es usar floating-point components en su lugar. La Guía de programación 2D de Quartz da the full details of the format you'll need to use.

Punto importante: Debería usar punto flotante desde la creación de la imagen original (y no creo que sea posible guardar una imagen como PNG, puede que tenga que usar TIFF). Una imagen que ya estaba premultiplicada en un tipo integral ya ha perdido esa precisión; no hay forma de recuperarlo.

El caso zero-alpha es la versión extrema de esto, a tal punto que incluso el punto flotante no puede ayudarle. Cualquier cosa que sea cero (alfa) es cero, y no hay recuperación del valor original no multiplicado desde ese punto.

+1

Nota adicional: Xcode también ejecuta 'pngcrush -iphone' para aplicar las optimizaciones de iDevice, una de las cuales es Premultiply alpha, por lo que de forma predeterminada se premultiplicarán todos los PNG en su aplicación. La solución consiste en colocar las imágenes que no desea que se rompan en una carpeta y agregarlas como una "referencia de carpeta" (útil si sirve PNG desde un servidor web incorporado). –

0

La multiplicación previa de alfa con un tipo de color entero es una operación de pérdida de información. Los datos se destruyen durante el proceso de cuantificación (redondeando a 8 bits).

Como algunos datos se destruyen (redondeando), no hay forma de recuperar el color exacto del píxel original (excepto algunos valores afortunados). Debe guardar los colores de su imagen de Photoshop antes de dibujarla en un contexto de mapa de bits, y usar los datos de color originales, no los datos de color multiplicados del mapa de bits.

0

Me encontré con este mismo problema al tratar de leer datos de imágenes, renderizarlas en otra imagen con CoreGraphics y luego guardar el resultado como datos no premultiplicados. La solución que encontré que funcionó para mí fue la de guardar una tabla que contiene la asignación exacta que CoreGraphics utiliza para asignar datos no premultiplicados a datos premultiplicados. Luego, calcule cuál sería el valor premultipled original con una llamada mult y floor(). Luego, si la estimación y el resultado de la búsqueda en la tabla no coinciden, simplemente verifique el valor por debajo de la estimación y el que está por encima de la estimación en la tabla para la coincidencia exacta.

// 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 restrict 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; 
} 

puede encontrar la tabla estática completa de los valores en este github link.

Tenga en cuenta que este enfoque no recuperará la información "perdida" cuando el píxel original no premultiplicado se premultiplicó. Pero devuelve el píxel no premoldeado más pequeño que se convertirá en el píxel premultiplicado una vez que vuelva a funcionar a través de la lógica premultiplicada. Esto es útil cuando el subsistema de gráficos solo acepta píxeles premultiplicados (como CoreGraphics en OSX). Si el subsistema de gráficos solo acepta píxeles premultipled, entonces es mejor que almacene solo los píxeles premultipled, ya que se consume menos espacio en comparación con los píxeles no premoldeados.

Cuestiones relacionadas