-
-
Notifications
You must be signed in to change notification settings - Fork 647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vips jp2ksave creates grayscale JPEG2000 image from SRGB sources #2965
Comments
I could not reproduce this using OpenJPEG 2.4.0 and libvips 8.13.0 on my Fedora PC. Are you using the macOS preview app? If so, this could be a duplicate of libvips/ruby-vips#345. A handy way to check is to convert the JPEG2000 image back to PNG, e.g: $ vips copy sample.jp2 x.png |
The JPEG2000 image shows as grayscale in any app on my Mac: Preview, Safari, Pixelmator. You are right, converting the file back to jpg gives the correct colors. But if I convert the file back using ImageMagick, I get completely wrong colors. Looking at the channels the red channel looks ok, but green and blue have wrong brightness and look inverted. Opening the Vips created JPEG2000 in Affinity Photo on the Mac show the same wrong colors, as the JPEG created from it using ImageMagick. However if I compress from the same source image using So something appears to be wrong with the file. |
Could you try an image with an odd number of columns? |
@jcupitt Yes, the bug doesn't happen with odd widths. Eg. 1023x1023px OK, 1024x1024px buggy. |
I noticed that converting the even width JPEG200 to PNG using |
I noticed some difference when dumping the files with 1023x1023px created by Vips:
1024x1024px created by Vips:
1024x1024px created by ImageMagick:
Note that for the files that work fine, all components have dx=1, dy=1, but the problematic one has dx-2, dy=2 for component 1 and 2. |
@jcupitt The problem seems to be related to chroma subsampling, if I use I noticed that the default for subsample-mode is auto, which enables subsampling only if quality is < 90% and Xsize and Ysize are evenly divisible by 2. This means auto mode enables subsampling for our even width image and disables it for the odd width image. See: https://github.com/libvips/libvips/blob/master/libvips/foreign/jp2ksave.c#L793 |
@jcupitt I found that ImageMagick sets x1 and y1 differently, based on which subsampling is used: https://github.com/ImageMagick/ImageMagick/blob/main/coders/jp2.c#L1019 But Vips just uses width and height: https://github.com/libvips/libvips/blob/master/libvips/foreign/jp2ksave.c#L714 |
Hmm, that's not it, I tried the following patch and still grayscale: diff --git a/libvips/foreign/jp2ksave.c b/libvips/foreign/jp2ksave.c
index 530ce61f4..9a6c4a96d 100644
--- a/libvips/foreign/jp2ksave.c
+++ b/libvips/foreign/jp2ksave.c
@@ -711,8 +711,8 @@ vips_foreign_save_jp2k_new_image( VipsImage *im,
image = vips_opj_image_create( im->Bands, comps, color_space,
allocate );
- image->x1 = width;
- image->y1 = height;
+ image->x1 = (width-1)*(subsample ? 2 : 1)+1;
+ image->y1 = (height-1)*(subsample ? 2 : 1)+1;
/* Tag alpha channels.
*/ |
However the computation of x1 and y1 in libvips seems to be wrong, see: https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/convertpng.c#L212 That matches what I did in my patch above, I'm only ignoring x0 and y0, since they are hardcoded to 0 in libvips. |
Hmm, the ImageMagick and OpenJPEG encoders linked above also both set dx and dy the same on all planes, instead of keeping it to 1 on the first plane, which seems strange to me. But even if I set dx/dy and x1/y1 like those decoders there's no difference. So the problem is likely not in the headers. |
Oh, I just noticed vips only uses YCbCr when subsampling is enabled, so if it's disabled we use RGB: https://github.com/libvips/libvips/blob/master/libvips/foreign/jp2ksave.c#L818 But there are different codepaths for subsample and save_as_ycc, which doesn't make sense in this case: https://github.com/libvips/libvips/blob/master/libvips/foreign/jp2ksave.c#L482 |
Argh, I had the wrong vips binary symlinked in my header tests. So I was testing the original code all the time. Essentially it doesn't seem to matter: Either set dx/dy to 2 for all planes and use the more complicated calculation for x1, y1 or do it just as it currently is. If you only change x1, y1 you get a "Size mismatch between tile data and sent data." error. In the end both variants end up with the version that looks like grayscale when subsampling is enabled, so I'm back at the beginning. |
Ooof, it's been a couple of years since I looked at this, I remember it was hard to get chroma subsampling to work. I've read the libvips jp2k load and save again and it's coming back a bit. As I recall, openjpeg, Because of this, few openjpeg-based decoders support anything other than 4:4:4 RGB. It looks like imagemagick and the built-in macOS converters are 4:4:4 RGB only. This means the default libvips behaviour is to write images which few people can read :-( in retrospect this looks like an error, sorry. We can't change this in 8.13, but for 8.14 lets swap the default to 4:4:4 RGB and have the fancy subsample mode behind a switch. As a workaround, setting |
It'd be good to test the libvips chromasubsample mode against another implementation. I think we'd probably need to get a license for one of the commercial jp2k systems and see how that behaves. Looking at the IM sources for jp2 it looks like it ought to support YCC and chroma subsample save, but it seems to crash for me:
That's git master IM7 and opj 2.5. It supports chroma upsample for load, but doesn't seem to check the image colourspace, so it won't know to do YCC->RGB conversion. |
... libvips 8.14 defaults jp2k save to chroma subsample off. |
@jcupitt There seems to be a problem with the MCT (Multi-Component-Transform) header: If I compress with This explains why the image decoded by ImageMagick looks strange, it is decoded as if it were RGB even though it is actually YCbCr. You can check this with The |
Oh, interesting. We've gone back and forth on mct a few times, it was most recently disabled if subsampling is on. Of course I intelligently didn't make a note of why this change was made :( A workaround for some bug somewhere, I suppose. It sounds like we should always set |
In However, when I hardcode both places we set If I set subsample_mode off in vips and hardcode tcp_mct to either 0 or 1 I get a smaller file with tcp_mct 1 and What I'm asking myself is: Should Vips even be doing the RGB->YCC transform or is that handled by OpenJPEG itself, if the tcp_mct is properly set? |
Huh hmmm I'm not sure either now. The openjpeg docs are rather opaque :( I didn't realize So maybe libvips should set |
I'll try asking on the OpenJPEG Google Group. |
I ended up no asking on the group, but doing some research instead. MCT (Multi Component Transform) is an addition to the JPEG 2000 standard that was added to help in compression of multi dimensional images, like medical imaging. You can supply a custom transformation or use the built-in MCT 1 which does an RGB to YCC transform without chroma subsampling (YCbCr 4:4:4). This means we should only use MCT 1 on (s)RGB data. JPEG2000 can also store YCbCr image data natively, which is what the current subsample_mode "on" or "auto" does, but from some research and own testing on macOS, those images are not compatible with all viewers. So my proposal would be to default to As a side note: The |
That sounds reasonable. I'll make a PR with a proposed revision we can test. Thanks for doing this research @felixbuenemann ! (or would you rather make a PR? I don't mind) |
I noticed you already changed the default for subsample_mode, so I've made a PR with the change to the "auto" behaviour. |
I think this is all done now, I'll close. |
…ubsampling More recent versions of vips disable this by default, but it still needs to be done. Otherwise you get corrupted greyscale images in jp2, depending on exact resolution you are writing to. libvips/libvips#2965 libvips/libvips#3428 (reply in thread)
There's a bug in the JPEG2000 saver in Vips 8.13.0, that causes converted images to be grayscale, even though they have 3 bands.
Example:
I tested this with both Vips 8.13.0 and OpenJPEG 2.5.0 on macOS/arm64 and Linux/aarch64, with the same result.
The text was updated successfully, but these errors were encountered: