问题描述
|
我正在使用带有
kCGImageAlphaPremultipliedFirst
选项的CGBitmapContextCreate
创建位图上下文。
我制作了一个5 x 5的测试图像,其中包含一些主要颜色(纯红色,绿色,蓝色,白色,黑色),一些混合颜色(即紫色)和一些alpha变化。每当alpha分量不为255时,颜色值都是错误的。
我发现当执行以下操作时,我可以重新计算颜色:
almostCorrectRed = wrongRed * (255 / alphaValue);
almostCorrectGreen = wrongGreen * (255 / alphaValue);
almostCorrectBlue = wrongBlue * (255 / alphaValue);
但是问题是,我的计算有时会偏离3甚至更多。例如,对于绿色,我得到的值为242而不是245,我100%确信它必须恰好是245。Alpha是128。
然后,对于PNG位图中具有相同alpha透明度的完全相同的颜色,我得到的alpha = 255和绿色= 245。
如果alpha为0,则红色,绿色和蓝色也为0。这里所有数据都丢失了,我无法弄清像素的颜色。
如何完全避免或完全取消此Alpha预乘,以便可以像在Photoshop中创建图像时一样,根据真实的R G B像素值修改图像中的像素?如何恢复R,G,B和A的原始值?
背景信息(此问题可能不是必需的):
我正在做的是:我将UIImage绘制到位图上下文上,以便对其执行一些简单的图像处理算法,并根据之前的颜色移动每个像素的颜色。没什么特别的。但是我的代码需要真实的颜色。当一个像素是透明的(意味着它的alpha小于255)时,我的算法不会在乎这一点,它只需要根据需要修改R,G,B,而Alpha则保持不变。有时,尽管它也会使alpha向上或向下移动。但是我将它们视为两个独立的事物。 Alpha扭曲了透明度,而R G B控制了颜色。
解决方法
这是整数类型中预乘的基本问题:
245 *(128/255)= 122.98
122.98被截断为整数= 122
122 *(255/128)= 243.046875
我不知道为什么得到的是242,而不是243,但是这个问题仍然存在,而且随着alpha的降低,情况变得更糟。
解决方案是改用浮点组件。 《 Quartz 2D编程指南》提供了您将需要使用的格式的全部详细信息。
要点:您需要在创建原始图像时使用浮点数(而且我认为将这样的图像另存为PNG甚至是不可能的;您可能必须使用TIFF)。已经以整数类型预乘的图像已经失去了精度。没有找回来。
零字母大小写是这种情况的极端版本,以至于即使浮点数也无法帮助您。零(alpha)等于零的任何时间,都无法从该点恢复原始的未乘数值。
, 将alpha与整数颜色类型预乘是一种信息有损操作。数据在量化过程中被破坏(四舍五入到8位)。
由于某些数据被破坏(通过舍入),因此无法恢复精确的原始像素颜色(某些幸运值除外)。您必须先保存Photoshop图像的颜色,然后再将其绘制到位图上下文中,并使用该原始颜色数据,而不是位图中的相乘颜色数据。
,尝试读取图像数据,使用CoreGraphics将其渲染到另一幅图像,然后将结果另存为非预乘数据时,我遇到了同样的问题。我发现对我有用的解决方案是保存一个表,该表包含CoreGraphics用于将未预乘的数据映射到预乘的数据的精确映射。然后,通过调用mult和floor()来估计原始的预乘值是多少。然后,如果估算值与表查找结果不匹配,则只需检查表中估算值以下和估算值以上的值是否完全匹配即可。
// Execute premultiply logic on RGBA components split into componenets.
// For example,a pixel RGB (128,0) with A = 128
// would return (255,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,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;
}
您可以在此github链接中找到完整的静态值表。
注意,当原始未预乘像素被预乘时,该方法将不会恢复信息“丢失”。但是,它的确会返回最小的未预乘像素,一旦再次通过预乘逻辑,它将变为预乘像素。当图形子系统仅接受预乘像素(例如OSX上的CoreGraphics)时,此功能很有用。如果图形子系统仅接受预乘像素,那么最好不要存储预乘像素,因为与未预乘像素相比,它消耗的空间更少。