Page 1 of 1

MagickWand/MagickCore:RGB -> YCC & YCC->RGB aren't inverses

Posted: 2008-07-26T15:57:52-07:00
by mjthe2nd
First, wanted to thank the developers of ImageMagick for a great, well-documented, time-saving library.

I've been using the MagickWand API (ImageMagick 6.4.2) to extract pixel data from various image types, and do some colorspace conversion. The colorspaces of interest are RGB (RGBColorspace), CIELAB (LabColorspace), CMYK (CMYKColorspace), Grayscale (GrayscaleColorspace), and Kodak PhotoYCC (YCCColorspace, not to be confused with YCbCr or YPbPr).

I came across an interesting anomaly: the colorspace conversions defined in colorspace.c work great, except when involving YCC. The equations listed seem fine (the comments below about scaling and such are ambiguous, but that's a separate issue. Said equations are excerpted below:

Code: Select all

      /*
        Initialize YCC tables:

          Y =  0.29900*R+0.58700*G+0.11400*B
          C1= -0.29900*R-0.58700*G+0.88600*B
          C2=  0.70100*R-0.58700*G-0.11400*B

        YCC is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
      */

Code: Select all

      
/*
        Initialize YCC tables:

          R = Y            +1.340762*C2
          G = Y-0.317038*C1-0.682243*C2
          B = Y+1.632639*C1

        YCC is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.

*/
However, the two transforms implemented are not inverses each other. Quite to the contrary, if one converts an image from RGB to YCC, then back to RGB, the values yielded in the floating-point pixel variable are off by a factor of exactly 81000, which yields stored pixel component quantized values between 0x0 and roughly 0xCF00 (instead of 0x0 - 0xFFFFFFFF for quantum_depth=32, which gives all 0's (black)in the resulting RGB image after bit depth reduction.

Perhaps I'm misunderstanding something, but it seems wrong that if I have a pixel value (quantum_depth = 32) of {0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF} in RGB, convert it to YCC via RGBTransformImage, then back to RGB via [TransformImageRGB[/i], that the resulting pixel value is {0x00000000,0x00000000,0x0000000}.

I've found the following two mathematically-equivalent fixes:

In my code

Code: Select all

  //After the YCC->RGB MagickSetImageColorspace() call, but BEFORE we write out to file
  //Scale by 81000 if we're doing YCC to RGB.
  //No root cause found yet, but without this, if you convert RGB->YCC->RGB, the first conversion goes fine, but the second yields
  //a black image (all values are non-zero, but way less than 1 LSB).
  if(inputColorspace == YCCColorspace)
  {
    status=MagickEvaluateImage(magick_wand, MultiplyEvaluateOperator, 81000.0f);
    if(status == MagickFalse)
      ThrowWandException(magick_wand);
  }
Patching colorspace.c

Code: Select all

 case YCCColorspace:
            {
              pixel.red/=1.3584000f;
              pixel.red*=81000.0f; //FIX
              pixel.green/=1.3584000f;
              pixel.green*=81000.0f; //FIX
              pixel.blue/=1.3584000f;
              pixel.blue*=81000.0f; //FIX
              break;
            }
I can provide more info if needed, but both fixes have been demonstrated to work. Need to figure out where the translation from equations to code went bad, or what assumptions I'm missing, which is difficult because the code for this is (overly) optimized, combined arithmetic operations involving constants instead of being clear and letting the compiler do it for you. Not a big deal though..

Anyway, thanks in advance for looking at this!
-Matt

Re: MagickWand/MagickCore:RGB -> YCC & YCC->RGB aren't inverses

Posted: 2008-07-27T10:04:19-07:00
by magick
We can reproduce the problem you posted and have a fix. Look for it in ImageMagick 6.4.2-5 Beta by tomorrow.

Re: MagickWand/MagickCore:RGB -> YCC & YCC->RGB aren't inverses

Posted: 2008-07-28T02:35:56-07:00
by mjthe2nd
Will do! I'll post my findings here, and look forward to seeing if the fix reveals root cause more than my "*= 81000.0f" hack :-)
Thanks!
-Matt

Re: MagickWand/MagickCore:RGB -> YCC & YCC->RGB aren't inverses

Posted: 2008-07-28T05:57:17-07:00
by magick
There is still a problem. YCC is actually PhotoYCC which may not be reversible. We're still investigating. See http://www5.informatik.tu-muenchen.de/l ... COL_34.htm.

Re: MagickWand/MagickCore:RGB -> YCC & YCC->RGB aren't inverses

Posted: 2008-07-28T07:01:53-07:00
by mjthe2nd
Haha that's precisely the page I was looking at whilst investigating this issue. I also suspect that the transform may not be totally reversible. It definitely won't be perfect if the map table is only 2^16 entries, and there's no interpolation between map entries. Also, I wasn't sure if the ImageMagick code that implemented the lower range of the piecewise transform between {R,G,B} and {R', G', B'} (for R,G,B values < 0.018) matched the equation itself, I was having a hard time convincing myself of that (besides the lack of gamma correction).

Also see http://www.kodak.com/global/en/professi ... -042.jhtml for a great description of how best to deal with the mismatch in gamuts between RGB and PhotoYCC, particularly the non-linear transform (lookup table) to scale the luminance, which can go much higher than RGB is really capable of representing, in order to be displayed on a monitor.

Many more questions than answers here, but at least the fix (the one I suggested, and presumably the one included in the new beta (the one I could find at ftp://ftp.imagemagick.org/pub/ImageMagick/beta/ hasn't been changed in a couple days and doesn't include a fix as far as I can tell, but I'm assuming a new tar-ball is on the way :-) ).

Thanks again!
-Matt

Edit: I probably should've specified again that I'm using Q32, which means that the fact that MaxMap is #defined in magick-type.h to be 65535UL for Q16, Q32, and Q64 means there'll be at least some quantization for Q32 and Q64. While I appreciate that a 2^32 or 2^64 entry LUT is impractical, some interpolation, even linear, would be a great feature to have for these high bit-depth configurations!

Also, I saw that there is support for remapping >1.0 luminance values in colorspace.c (YCCMap), that purports to apply (or expect to be baked into the input?) a gamma of 2.2. The concern here is that I believe the RGB values, pre-mapping, can be up to 354 or so, and YCCMap is only 351 entries. And again, the limited number of entries further increases the quantization of data for quantum bit depths > 8, necessitating either a larger LUT that better approximates the real-value function implemented (if there is one, and it's not just based on a table like the one from the Kodak link), or interpolation between LUT entries (linear or something fancier).