libvips / pyvips

python binding for libvips using cffi
MIT License
649 stars 50 forks source link

Question: icc_import behavior when embedded=True on an image without an embedded profile #426

Closed erdmann closed 1 year ago

erdmann commented 1 year ago

Hi,

I recently ran pyvips.Image.icc_import on an image that didn't have a color profile and I was surprised to find that the command still worked. I expected an error, but after reading the documentation I see that the embedded option is only engaged if it's possible. I couldn't find documentation on what profile it's implicitly assuming, so I did a little (possibly flawed) experiment using numpy and pyvips:

In [1]: import numpy as np

In [2]: import pyvips

In [3]: im = np.c_[255 * np.eye(3, dtype=np.uint8), np.full(3, 255, dtype=np.uint8)].T[None]

In [4]: im # 1 row, 4 colums: red, green, blue and white pixels
Out[4]: 
array([[[255,   0,   0],
        [  0, 255,   0],
        [  0,   0, 255],
        [255, 255, 255]]], dtype=uint8)

In [5]: vim = pyvips.Image.new_from_array(im)

In [6]: vim
Out[6]: <pyvips.Image 4x1 uchar, 3 bands, srgb>

# Now I extract the chromaticity coordinates of these 4:

In [7]: vim.icc_import(pcs='xyz').colourspace('yxy').numpy().squeeze()[...,1:]
Out[7]: 
array([[0.64086705, 0.33172554], # red primary
       [0.31002232, 0.5852917 ], # green primary
       [0.12321617, 0.05295137], # blue primary
       [0.31268787, 0.32899842]], dtype=float32) # white

# embedded=True has no effect since there's no profile.
In [8]: vim.icc_import(pcs='xyz', embedded=True).colourspace('yxy').numpy().squeeze()[...,1:]
Out[8]: 
array([[0.64086705, 0.33172554],
       [0.31002232, 0.5852917 ],
       [0.12321617, 0.05295137],
       [0.31268787, 0.32899842]], dtype=float32)

If Wikipedia can be trusted (?), this is close to, but not the same as, the sRGB primaries and whitepoint. In particular, the blue primary is listed there as x=0.15, y=0.06 while the value calculated above is x=0.12, y=0.05. At any rate I'm confused about the expected behavior when using icc_import without a color profile and whether the above is the expected output.

Many thanks in advance!

jcupitt commented 1 year ago

Hi Rob,

It'll fall back to one of the profiles embedded in libvips:

https://github.com/libvips/libvips/tree/master/libvips/colour/profiles

Is this an RGB JPEG? In which case it'll be that sRGB profile.

Perhaps this is a D65 / D50 difference? sRGB is officially D65, but most sRGB profiles are actually D50.

erdmann commented 1 year ago

Indeed it could be the D50/D65 difference; I hadn't thought of that.

I have vague recollections that doing icc_import without an embedded or explicitly-specified profile used to throw an error. Did the default behavior change at some point? In any case, please consider this a minor documentation request so that the fallback to those profiles in the case where neither embedded nor explicitly specified (with input_profile=...) is stated.

Many thanks again.

jcupitt commented 1 year ago

You can see the logic here:

https://github.com/libvips/libvips/blob/master/libvips/colour/icc_transform.c#L670-L750

You're right, the docs have fallen behind a bit. I'll update them.

jcupitt commented 1 year ago

I had a go: https://github.com/libvips/libvips/commit/efed966147d0effa41510ea83c85b2c8fa18e857

Thanks for the suggestion!

erdmann commented 1 year ago

You never cease to amaze, John!