如何在不预乘 alpha 的情况下获得真实的 RGBA 或 ARGB 颜色值?

发布于 2024-11-16 02:09:20 字数 1030 浏览 2 评论 0原文

我正在使用带有 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 和 green = 245,因为它应该是。

如果 alpha 为 0,则红色、绿色和蓝色也为 0。这里所有数据都丢失了,我无法弄清楚像素的颜色。

如何完全避免或撤消这种 alpha 预乘,以便我可以根据在 Photoshop 中创建图像时的真实 RGB 像素值来修改图像中的像素?如何恢复 R、G、B 和 A 的原始值?


背景信息(对于这个问题可能不是必需的):

我正在做的是这样的:我获取一个 UIImage,将其绘制到位图上下文中,以便对其执行一些简单的图像处理算法,改变每个图像的颜色像素取决于它之前的颜色。没什么特别的。但我的代码需要真实的颜色。当像素是透明的(意味着它的 alpha 小于 255)时,我的算法不应该关心这个,它应该只根据需要修改 R、G、B,而 Alpha 保持不变。有时它也会向上或向下移动阿尔法。但我将它们视为两个不同的事物。 Alpha 控制透明度,而 RGB 控制颜色。

I'm creating an bitmap context using CGBitmapContextCreate with the kCGImageAlphaPremultipliedFirst option.

I made a 5 x 5 test image with some major colors (pure red, green, blue, white, black), some mixed colors (i.e. purple) combined with some alpha variations. Every time when the alpha component is not 255, the color value is wrong.

I found that I could re-calculate the color when I do something like:

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

But the problem is, that my calculations are sometimes off by 3 or even more. So for example I get a value of 242 instead of 245 for green, and I am 100% sure that it must be exactly 245. Alpha is 128.

Then, for the exact same color just with different alpha opacity in the PNG bitmap, I get alpha = 255 and green = 245 as it should be.

If alpha is 0, then red, green and blue are also 0. Here all data is lost and I can't figure out the color of the pixel.

How can I avoid or undo this alpha premultiplication alltogether so that I can modify pixels in my image based on the true R G B pixel values as they were when the image was created in Photoshop? How can I recover the original values for R, G, B and A?


Background info (probably not necessary for this question):

What I'm doing is this: I take a UIImage, draw it to a bitmap context in order to perform some simple image manipulation algorithms on it, shifting the color of each pixel depending on what color it was before. Nothing really special. But my code needs the real colors. When a pixel is transparent (meaning it has alpha less than 255) my algorithm shouldn't care about this, it should just modify R,G,B as needed while Alpha remains at whatever it is. Sometimes though it will shift alpha up or down too. But I see them as two separate things. Alpha contorls transparency, while R G B control the color.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冧九 2024-11-23 02:09:20

这是整数类型预乘法的一个基本问题:

  • 245 * (128/255) = 122.98
  • 122.98 截断为整数 = 122
  • 122 * (255/128) = 243.046875

我不确定为什么你得到的是 242 而不是 243,但是无论哪种方式这个问题仍然存在,并且它变得alpha 值越低,情况就越糟。

解决方案是使用 浮动-点组件 代替。 Quartz 2D 编程指南给出了 您需要使用的格式的完整详细信息

重要的一点:您需要在创建原始图像时使用浮点(我认为甚至不可能将这样的图像保存为 PNG;您可能必须使用 TIFF)。已经以整数类型预乘的图像已经失去了精度;没有办法收回来了。

零 alpha 情况是这种情况的极端版本​​,甚至浮点也无法帮助您。任何乘以零 (alpha) 的值都为零,并且无法从该点恢复原始未预乘的值。

This is a fundamental problem with premultiplication in an integral type:

  • 245 * (128/255) = 122.98
  • 122.98 truncated to an integer = 122
  • 122 * (255/128) = 243.046875

I'm not sure why you're getting 242 instead of 243, but this problem remains either way, and it gets worse the lower the alpha goes.

The solution is to use floating-point components instead. The Quartz 2D Programming Guide gives the full details of the format you'll need to use.

Important point: You'd need to use floating-point from the creation of the original image (and I don't think it's even possible to save such an image as PNG; you might have to use TIFF). An image that was already premultiplied in an integral type has already lost that precision; there is no getting it back.

The zero-alpha case is the extreme version of this, to such an extent that even floating-point cannot help you. Anything times zero (alpha) is zero, and there is no recovering the original unpremultiplied value from that point.

心欲静而疯不止 2024-11-23 02:09:20

将 alpha 与整数颜色类型预乘是一种信息有损操作。数据在量化过程中被破坏(四舍五入到 8 位)。

由于某些数据被破坏(通过舍入),因此无法恢复确切的原始像素颜色(除了一些幸运值)。在将 Photoshop 图像绘制到位图上下文之前,您必须保存它的颜色,并使用原始颜色数据,而不是位图中相乘的颜色数据。

Pre-multiplying alpha with an integer color type is an information lossy operation. Data is destroyed during the quantization process (rounding to 8 bits).

Since some data is destroy (by rounding), there is no way to recover the exact original pixel color (except for some lucky values). You have to save the colors of your photoshop image before you draw it into a bitmap context, and use that original color data, not the multiplied color data from the bitmap.

乞讨 2024-11-23 02:09:20

当我尝试读取图像数据,使用 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 链接找到完整的静态值表。

请注意,当原始未预乘像素被预乘时,此方法将无法恢复“丢失”的信息。但是,它确实返回最小的未预乘像素,一旦再次运行预乘逻辑,该像素将成为预乘像素。当图形子系统仅接受预乘像素时(如 OSX 上的 CoreGraphics),这非常有用。如果图形子系统只接受预乘像素,那么最好只存储预乘像素,因为与未预乘像素相比,消耗的空间更少。

I ran into this same problem when trying to read image data, render it to another image with CoreGraphics, and then save the result as non-premultiplied data. The solution I found that worked for me was to save a table that contains the exact mapping that CoreGraphics uses to map non-premultiplied data to premultiplied data. Then, estimate what the original premultipled value would be with a mult and floor() call. Then, if the estimate and the result from the table lookup do not match, just check the value below the estimate and the one above the estimate in the table for the exact match.

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

You can find the complete static table of values at this github link.

Note that this approach will not recover information "lost" when the original unpremultiplied pixel was premultiplied. But, it does return the smallest unpremultiplied pixel that will become the premultiplied pixel once run through the premultiply logic again. This is useful when the graphics subsystem only accepts premultiplied pixels (like CoreGraphics on OSX). If the graphics subsystem only accepts premultipled pixels, then you are better off storing only the premultipled pixels, since less space is consumed as compared to the unpremultiplied pixels.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文