我正在使用带有CGBitmapContextCreate
选项的kCGImageAlphaPremultipliedFirst
创建位图上下文。
我用一些主要颜色(纯红色,绿色,蓝色,白色,黑色)制作了一个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和green = 245。
如果alpha为0,则红色,绿色和蓝色也为0.这里所有数据都丢失了,我无法弄清楚像素的颜色。
如何完全避免或撤消此alpha预乘,以便我可以根据真实的R G B像素值修改图像中的像素,就像在Photoshop中创建图像时一样?如何恢复R,G,B和A的原始值?
背景信息(此问题可能不是必需的):
我正在做的是:我采用UIImage,将其绘制到位图上下文,以便在其上执行一些简单的图像处理算法,根据之前的颜色移动每个像素的颜色。没什么特别的。但我的代码需要真正的颜色。当一个像素是透明的(意味着它的alpha小于255)时,我的算法不应该关心这个,它应该根据需要修改R,G,B,而Alpha保持不管它是什么。有时虽然它会向上或向下移动alpha。但我认为它们是两个不同的东西。 Alpha控制透明度,而R G B控制颜色。
答案 0 :(得分:6)
这是整数类型中的预乘的基本问题:
我不确定为什么你会得到242而不是243,但是这个问题仍然存在,并且随着alpha的降低,情况会变得更糟。
解决方案是使用floating-point components代替。 Quartz 2D Programming Guide提供了the full details of the format you'll need to use。
重要的一点:您需要在创建原始图像时使用浮点(我不认为甚至可以将这样的图像保存为PNG;您可能必须使用TIFF)。已经以整数形式预乘的图像已经失去了这种精度;没有回来。
零alpha的情况是这个的极端版本,甚至浮点也无法帮助你。零(alpha)的任何时间都为零,并且没有从该点恢复原始的未经过相乘的值。
答案 1 :(得分:0)
将alpha乘以整数颜色类型是一种信息有损操作。在量化过程中数据被破坏(舍入到8位)。
由于某些数据被破坏(通过舍入),因此无法恢复确切的原始像素颜色(除了一些幸运值)。您必须在将Photoshop图像绘制到位图上下文之前保存它们的颜色,并使用原始颜色数据,而不是位图中的乘法颜色数据。
答案 2 :(得分:0)
我在尝试读取图像数据时遇到了同样的问题,使用CoreGraphics将其渲染到另一个图像,然后将结果保存为非预乘数据。我发现对我有用的解决方案是保存一个表,其中包含CoreGraphics用于将非预乘数据映射到预乘数据的精确映射。然后,使用mult和floor()调用估计原始的预乘值。然后,如果估计和表查找的结果不匹配,只需检查估计值下方的值和表格中估计值之上的值,即可完全匹配。
// 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;
}
您可以在此github link找到完整的静态值表。
请注意,当原始未预乘的像素被预乘时,此方法不会恢复“丢失”的信息。但是,它确实返回了最小的未经倍增的像素,一旦再次通过预乘逻辑,它将成为预乘像素。当图形子系统仅接受预乘像素(如OSX上的CoreGraphics)时,这非常有用。如果图形子系统只接受预乘的像素,那么最好只存储预乘的像素,因为与未经过相乘的像素相比,消耗的空间更少。