How to get the real RGBA or ARGB color values without premultiplied alpha?

混江龙づ霸主 提交于 2019-12-04 06:31:43

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!