【问题标题】:How to get the real RGBA or ARGB color values without premultiplied alpha?如何在没有预乘 alpha 的情况下获得真正的 RGBA 或 ARGB 颜色值?
【发布时间】:2011-09-17 18:38:37
【问题描述】:

我正在使用带有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 中创建图像时的真实 R G B 像素值修改图像中的像素?如何恢复 R、G、B 和 A 的原始值?


背景信息(这个问题可能不需要):

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

【问题讨论】:

  • 我已经成功地将结果四舍五入,方法是使用高于 0.5 的任何值到下一个最高整数,以及低于任何值的更低整数。您需要确保等式中的所有部分都转换为 CGFloat。

标签: iphone ios ipad core-graphics quartz-2d


【解决方案1】:

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

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

我不知道为什么你得到的是 242 而不是 243,但不管怎样,这个问题仍然存在,而且 alpha 越低,情况就越糟。

解决方案是改用floating-point components。 Quartz 2D 编程指南给出了the full details of the format you'll need to use

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

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

【讨论】:

  • 附加说明:Xcode 还运行pngcrush -iphone 以应用 iDevice 优化,其中之一是预乘 alpha,因此默认情况下,您的应用程序中的所有 PNG 都将被预乘。解决方法是将您不想被破坏的图像放入一个文件夹并将其添加为“文件夹引用”(如果您从嵌入式 Web 服务器提供 PNG 则很有用)。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    我在尝试读取图像数据时遇到了同样的问题,使用 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)时,这很有用。如果图形子系统只接受预乘像素,那么最好只存储预乘像素,因为与未预乘像素相比,占用的空间更少。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-02
      • 2020-11-13
      • 2012-07-11
      • 1970-01-01
      • 2018-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多