Skip to content
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

heifload ignores clean aperture information #1808

Open
kleisauke opened this issue Sep 6, 2020 · 21 comments
Open

heifload ignores clean aperture information #1808

kleisauke opened this issue Sep 6, 2020 · 21 comments
Labels

Comments

@kleisauke
Copy link
Member

kleisauke commented Sep 6, 2020

This issue is similar to #1574, but found a way to generate a HEIC image with a non-faulty clap box. This image is processable with heif-convert but fails with libvips.

Example image: https://t0.nl/x.heic

Generated with (with libheif v1.8.0 and libvips v8.10)

$ wget https://images.weserv.nl/zebra.jpg
$ vips copy zebra.jpg x.heic

Reproduction

$ wget https://t0.nl/x.heic
$ vips copy x.heic x.jpg

(vips:38053): VIPS-WARNING **: 10:49:00.847: heifload: ignoring nclx profile
...
(vips:38053): VIPS-WARNING **: 10:49:01.383: error in tile 0 x 0
...
heifload: bad image dimensions on decode

$ heif-convert x.heic x.jpg
File contains 1 images
Written to x.jpg

Image info

$ vipsheader x.heic

(vipsheader:36955): VIPS-WARNING **: 10:45:23.958: heifload: ignoring nclx profile
x.heic: 4120x2746 uchar, 3 bands, srgb, heifload

$ heif-info x.heic -d
...
Box: ispe -----
size: 20   (header size: 12)
version: 0
flags: 0
image width: 4120
image height: 2747

Box: clap -----
size: 40   (header size: 8)
clean_aperture: 4120/1 x 2747/1
offset: 0/2 ; -1/2

Note that the above ispe box contains an image height of 2747 pixels which is cropped to 2746 pixels with the clap box info. This is therefore a valid HEIC image.

@kleisauke kleisauke added the bug label Sep 6, 2020
@jcupitt
Copy link
Member

jcupitt commented Sep 6, 2020

This seems to be working for me on ubuntu 20.04 with libvips 8.10 and the system-installed libheif, x265 etc.:

$ wget https://images.weserv.nl/zebra.jpg
--2020-09-06 13:25:04--  https://images.weserv.nl/zebra.jpg
Resolving images.weserv.nl (images.weserv.nl)... 104.27.158.82, 172.67.143.81, 104.27.159.82, ...
Connecting to images.weserv.nl (images.weserv.nl)|104.27.158.82|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1854461 (1.8M) [image/jpeg]
Saving to: ‘zebra.jpg’

zebra.jpg           100%[===================>]   1.77M  2.00MB/s    in 0.9s    

2020-09-06 13:25:05 (2.00 MB/s) - ‘zebra.jpg’ saved [1854461/1854461]

john@kiwi:~/pics$ vips copy zebra.jpg x.heic
john@kiwi:~/pics$ vips copy x.heic x.jpg
john@kiwi:~/pics$ wget https://t0.nl/x.heic
--2020-09-06 13:27:27--  https://t0.nl/x.heic
Resolving t0.nl (t0.nl)... 104.18.58.124, 104.18.59.124, 172.67.146.141, ...
Connecting to t0.nl (t0.nl)|104.18.58.124|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1201212 (1.1M) [application/octet-stream]
Saving to: ‘x.heic.1’

x.heic.1            100%[===================>]   1.15M  2.11MB/s    in 0.5s    

2020-09-06 13:27:28 (2.11 MB/s) - ‘x.heic.1’ saved [1201212/1201212]

john@kiwi:~/pics$ vips copy x.heic x.jpg
john@kiwi:~/pics$ 

Are you using a more recent libheif/x265/etc.?

@kleisauke
Copy link
Member Author

Could you try vips copy x.heic.1 x.jpg?

@jcupitt
Copy link
Member

jcupitt commented Sep 6, 2020

Ooop! Sorry, I missed the renaming there. Yes, I see the error too:

john@kiwi:~/pics$ vips copy x.heic.1 x.jpg
(vips:409364): VIPS-WARNING **: 13:45:12.374: error in tile 0 x 0
heif: Invalid input: Invalid clean-aperture specification (2.120)

heif-convert also chokes on it, so I suppose this is a libheif issue?

$ heif-convert x.heic.1 x.png
File contains 1 images
Could not decode HEIF image: 0: Invalid input: Invalid clean-aperture specification

@kleisauke
Copy link
Member Author

Ah, you're right. Processing the test image with heif-convert curiously fails with libheif v1.7.0 but works fine with libheif v1.8.0. I'll dig further (perhaps it's a libheif issue).

The reason that older versions of libheif created the test image without clap box information is probably due to the fixes within strukturag/libheif#232. I see this when recreating the test image with libheif v1.7.0:

$ heif-info x.heic -d
...
Box: ispe -----
size: 20   (header size: 12)
version: 0
flags: 0
image width: 4120
image height: 2746

(clap box info is missing and the ispe box contains the actual dimensions)

@kleisauke
Copy link
Member Author

I get the same error when reverting commit strukturag/libheif@cd3506d on libheif v1.8.0. So the Invalid clean-aperture specification error seems to be a bug on older versions of libheif (prior to v1.8.0).

After further investigation, it's only the heifload: bad image dimensions on decode error that makes this image not processable. Removing this check for libheif >= v1.8.0 seems to work, see for example this patch:

Patch
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kleis Auke Wolthuizen <github@kleisauke.nl>
Date: Thu, 10 Sep 2020 15:50:00 +0200
Subject: [PATCH 1/1] Disable image dimensions check for libheif >= v1.8.0

See: https://github.com/libvips/libvips/issues/1808.

diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c
index 1111111..2222222 100644
--- a/libvips/foreign/heifload.c
+++ b/libvips/foreign/heifload.c
@@ -790,6 +790,7 @@ vips_foreign_load_heif_generate( VipsRegion *or,
 	}
 
 	if( !heif->data ) {
+#if LIBHEIF_NUMERIC_VERSION < 0x01080000
 		int image_width = heif_image_get_width( heif->img, 
 			heif_channel_interleaved );
 		int image_height = heif_image_get_height( heif->img, 
@@ -805,6 +806,7 @@ vips_foreign_load_heif_generate( VipsRegion *or,
 				"%s", _( "bad image dimensions on decode" ) );
 			return( -1 );
 		}
+#endif
 
 		if( !(heif->data = heif_image_get_plane_readonly( heif->img, 
 			heif_channel_interleaved, &heif->stride )) ) {

But I'm not sure if this might cause side effects, do you perhaps have a test image for the segv mentioned here?:

/* We can sometimes get inconsistency between the dimensions
* reported on the handle, and the final image we fetch. Error
* out to prevent a segv.
*/

@lovell
Copy link
Member

lovell commented Sep 22, 2020

Was the "bad image dimensions on decode" to handle erroneously double-auto-rotated images, where both EXIF and HEIF contained an orientation? If so I think more recent versions of libheif, certainly 1.8.0, no longer do this so Kleis' patch will be safe to add.

@jcupitt
Copy link
Member

jcupitt commented Sep 23, 2020

I think (from memory!) the check was for images smaller than one encoding tile. These used to not be clipped correctly.

@jcupitt
Copy link
Member

jcupitt commented Sep 23, 2020

Oh, you're right, it was the autorotate stuff that was breaking. That's gone now, fortunately.

I'm not sure about this patch though. If that test fails, something is badly wrong, and the image sizes for libheif and libvips are not matched. Isn't removing this check dangerous? Do we need to add extra clipping code?

I'm probably missing something.

@kleisauke
Copy link
Member Author

I'm not sure about the patch above either, I just opened strukturag/libheif#365 which might give us some more information on how to handle this (note that processing x-rav1e.avif works great, so this could be a problem in libheif).

@kleisauke
Copy link
Member Author

The above mentioned patch does not appear to be safe. For example, processing the file corresponding to CVE-2020-10251 with the patch applied above causes an out-of-bounds read:

$ wget https://t0.nl/poc.heic
$ vips copy poc.heic x.png
Segmentation fault (core dumped)

Probably the best way to handle this is to clip the dimensions against heif_image_get_{width,height} instead of returning an error.

@kleisauke
Copy link
Member Author

Probably the best way to handle this is to clip the dimensions against heif_image_get_{width,height} instead of returning an error.

I had an attempt to do this, but unfortunately it did not succeed. The problem is that the poc.heic image is a multi-page image with a size of 1280×4439 for each page (i.e. the dimensions returned from heif_image_handle_get_{width,height}).

$ heif-info poc.heic
MIME type: image/heif
image: 1280x4439 (id=20004), primary
  thumbnail: 320x212
  color profile: no
  alpha channel: no
  depth channel: no
image: 1280x4439 (id=20006)
  thumbnail: 320x212
  color profile: no
  alpha channel: no
  depth channel: no
$ vipsheader poc.heic[page=0]
poc.heic: 1280x4439 uchar, 3 bands, srgb, heifload
$ vipsheader poc.heic[page=1]
poc.heic: 1280x4439 uchar, 3 bands, srgb, heifload

But the actual encoded images are 1280×854 in size (i.e. the dimensions returned from heif_image_get_{width,height}), see:

$ heif-convert poc.heic x.jpg
File contains 2 images
Written to x-1.jpg
Written to x-2.jpg
$ vipsheader x-1.jpg
x-1.jpg: 1280x854 uchar, 3 bands, srgb, jpegload
$ vipsheader x-2.jpg
x-2.jpg: 1280x854 uchar, 3 bands, srgb, jpegload

AFAIK, there's currently no API available within libheif to "sniff" these encoded dimensions without decoding the whole image (i.e. without calling heif_decode_image).

@lovell
Copy link
Member

lovell commented Dec 3, 2020

Probably the best way to handle this is to clip the dimensions against heif_image_get_{width,height} instead of returning an error.

This appears to be the approach taken by ImageMagick, which would imply the following might suffice.

@@ -861,9 +861,8 @@ vips_foreign_load_heif_generate( VipsRegion *or,
                 */
                if( image_width != heif->page_width ||
                        image_height != heif->page_height ) {
-                       vips_error( class->nickname, 
-                               "%s", _( "bad image dimensions on decode" ) );
-                       return( -1 );
+                       heif->page_width = image_width;
+                       heif->page_height = image_height;
                }

lovell referenced this issue Dec 14, 2020
This patch was dropped from 8.10.3 release 1, annoyingly.
kleisauke added a commit to kleisauke/libvips that referenced this issue Dec 15, 2020
Unfortunately this calls heif_decode_image in *_header, which
seems a bit confusing, and is at the expense of performance.

See libvips#1808 for context.
@kleisauke
Copy link
Member Author

WIP commit kleisauke@14c7d5e (on the support-heif-clap-box branch) adds support for this. I tested it without any problems with poc.heic (see above) and with the test images in strukturag/libheif#365 (which is just fixed).

Note that this invokes heif_decode_image during the *_header phase, which seems a bit confusing, and is at the expense of performance.

@jcupitt
Copy link
Member

jcupitt commented Dec 22, 2020

I had a quick go at another solution:

https://github.com/libvips/libvips/compare/fix-heifload-clipping

This clips the decoded image against the reported image size or pads the output, as required. It works with poc.heic, though of course you get lots of black:

vips copy poc.heic x.png
vipsthumbnail x.png --size x500

tn_x

What do you think? The advantage is that we don't need to decode the whole file just to get basic dimensions.

If libheif adds something in the future to give access to the decoded size, we could switch to that.

@jcupitt
Copy link
Member

jcupitt commented Dec 24, 2020

Related: libheif 1.10 seem to have a bug setting the image dimensions. See:

strukturag/libheif#405

You need to test against 1.9.1.

@kleisauke
Copy link
Member Author

I can confirm that your solution works as well. Perhaps we should ask upstream for a method to get the dimensions of the encoded image without having to decode the whole image?

It's a bit confusing that heif_image_handle_get_{width,height} does not return the actual encoded image dimensions. I noticed that not only ImageMagick made this assumption, see for example:
strukturag/libheif#417

@sethify
Copy link

sethify commented Feb 3, 2021

I've reverted to libheif 1.9.1 (as suggested reading through the issues here).

However, ysing libvips 8.10.5 with libheif 1.9.1. I'm having an interesting issue where HEIF files are being corrupted unless, as far as I can tell:

  • the output width is a multiple of 16
  • AND the output height is an even number

Edit: The width being a multiple of 16 doesn't seem to hold. I can output my test images at 600, 900 and a few others, but no other combinations, using only HEVC (the other encoders report a bad seek to ####

@sethify
Copy link

sethify commented Feb 3, 2021

Update - I did more testing and as long as the image dimensions are even (multiples of 2) libvips/libheif will output a really nice looking HEIF image. Should I force my dimensions to even numbers? I would really prefer to resize to exact ratios, but if this is expected behaviour, I can live with it.

@jcupitt
Copy link
Member

jcupitt commented Feb 4, 2021

There's a new libheif due in a couple of days which should improve this. I think I'd try again with that.

@kleisauke
Copy link
Member Author

I noticed that the fix-heifload-clipping branch was still unmerged. Here's another test image that seems to fail:
https://github.com/AOMediaCodec/av1-avif/raw/master/testFiles/Link-U/kimono.crop.avif
(fwiw, Google Chrome seem to ignore the clap box, kimono.crop.avif and kimono.avif are the same here)

$ curl -LO https://github.com/AOMediaCodec/av1-avif/raw/master/testFiles/Link-U/kimono.crop.avif
$ vipsheader kimono.crop.avif
kimono.crop.avif: 722x1024 uchar, 3 bands, srgb, heifload
$ vips copy kimono.crop.avif x.png
(vips:26630): VIPS-WARNING **: 14:16:30.337: error in tile 0 x 0
...
heifload: bad image dimensions on decode
...
vips2png: unable to write to target x.png

Though, on that branch it will also produce lots of black.

Expected Actual
kimono.crop.png x

But at least it doesn't cause an error.

@jcupitt
Copy link
Member

jcupitt commented Jul 18, 2021

I wasn't sure we'd arrived at consensus on this. Do you think that branch is the best solution for now? The huge black areas are very unfortunate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants