In nvJPEG: Left-over YUV-to-RGB conversion with CMYK JPEG?

I noticed that for none of the CMYK JPEG samples out there (e.g. the one attached underneath Exception reading a CMYK Jpeg · Issue #40 · coobird/thumbnailator · GitHub ) nvJPEG decodes properly the CMYK channels into either NVJPEG_OUTPUT_RGBI or NVJPEG_OUTPUT_UNCHANGED:

  • First, the decoupled decoding appears to be mandatory, even though the documentation may suggest that NVJPEG_OUTPUT_UNCHANGED works with the single step API: The single step API will produce a garbled black channel.

  • The output from decoupled decoding has strange CMY channels, but the correct black

  • NVJPEG_OUTPUT_RGBI gives roughly the same result as NVJPEG_OUTPUT_UNCHANGED and a manual conversion into RGB

  • Fitting the nvJPEG (nv for short) CMY channels vs. libjpeg (lj for short) output using the image linked above gives:

C_lj = 434.2 - 0.999 C_nv - 1.401 M_nv
M_lj = 119.6 - 1.000 C_nv + 0.344 M_nv + 0.714 Y_nv
Y_lj = 480.8 - 0.999 C_nv - 1.768 M_nv + 0.002 Y_nv

With some round-off error, and the constant in the front, this is basically one step of the YUV-to-RGB conversion with intermediate U’, V’, as seen e.g. here: Best Online Casinos - Four Countries Casinos (4th set of equations).

Is the CMYK decoding in nvJPEG currently broken, and activates certain YUV conversion code paths in the library?

CMYK decoding is done in the same manner as in OpenCV (it says so in NVJPEG documentation). To my knowledge, that is not color (or perception) accurate conversion.

You can output unchanged and then convert using ICM profile (as you should, that’s the only valid way of dealing with CMYK JPEG). You can do that using littlecms2 library.

From the description above you can see that I am using NVJPEG_OUTPUT_UNCHANGED, in order to obtain the CMYK channels (hence the comment that K is decoded correctly). The NVJPEG_OUTPUT_RGBI implementation is only to investigate the problem. The original plan was absolutely to apply the color management, as you detailed later.

The problem is that the CMY channels from nvJPEG when decoded with NVJPEG_OUTPUT_UNCHANGED are changed, and is with a YUV-to-RGB, only C being fed as (YUV’s) Y, M being fed as U, Y being ved as V. Or: You can just try 4-channel decoding with nvJPEG vs. libjpeg, and the results are absolutely not the same.

Oh so it’s not correct when decoding as unchanged as well?

To be honest, I haven’t implemented CMYK yet because it requires much more complicated setup and I didn’t have time at the moment. I see that the latest CUDA 10.2 has deprecated some of the decoding APIs (three phase ones) – they say they will introduce something with similar functionality in the next version so it might be better for me to wait until the API stabilizes.

I used only decode single so far, that gives 3x better performance in hybrid mode than Intel’s UIC JPEG decoder on a CPU, but it doesn’t support CMYK at all (you can’t activate it, and docs say it’s off by default).

I just tested this and I can confirm that using NVJPEG_OUTPUT_UNCHANGED produces correct results for me.

What you get when you request unchanged is YCCK which you must convert to CMYK. To do that you need to do YCbCr conversion to RGB, and then to get actual CMY values you do:

C = range_limit(255 - R);
M = range_limit(255 - G);
Y = range_limit(255 - B);

Where range_limit() clips output values to 0…255 range.

Then you can convert CMYK to RGB using either littleCMS and proper ICC profile.

Another option is to convert using naive approach:

R = C * K / 255;
G = M * K / 255;
B = Y * K / 255;

But that is good just for preview since it is just an approximation.

As for NVJPEG_OUTPUT_RGBI and NVJPEG_OUTPUT_BGRI, they indeed do not work correctly.

That is really incorrect. One should target sRGB here, but this does not. Neither CMYK with USA profile nor sRGB are contained inside one another.

“To my knowledge, that is not color (or perception) accurate conversion.” Yes, it is. Without color managment green will be to green, etc.