strukturag / libheif

libheif is an HEIF and AVIF file format decoder and encoder.
Other
1.7k stars 298 forks source link

Handle image rotation for JPEG output #227

Open farindk opened 4 years ago

farindk commented 4 years ago

Image rotation is not working properly in all cases when outputting in JPEG format. The problem is that is HEIF image is rotated, but the copied EXIF data causes the JPEG image to rotate again. Usually libheif disables HEIF image rotation when the output is JPEG and there is EXIF data, but with tiles images, this is not implemented. It is also a bit hacky anyways because when transformations are ignored, also other transformations (e.g. cropping) are not processed.

The right way to implement this would be to remove the Orientation tag from the output Exif data.

jcupitt commented 4 years ago

Hello, libvips is currently fighting this issue too. See also https://github.com/strukturag/libheif/issues/117 for previous battles with orientation.

The situation seems to be that orientation is recorded in two places: as the set of transforms, and in EXIF. This means there are three cases:

  1. The device adds transforms to flip the image upright and does not record the orientation in EXIF.
  2. The device does not set any transforms, but does set the EXIF orientation tag. Downstream processing somewhere will need to read the tag and handle orientation.
  3. Some Android phones seem to set transforms, and ALSO set EXIF. In this case, the EXIF orientation tag must be removed to prevent accidental double rotation later.

The problem for us is that there seems to be no easy way to separate cases 2. and 3., so libvips doesn't know whether to strip the EXIF orientation or not.

How about adding a little API so libheif users can tell if any load transforms were applied? It would save libheif having to add an EXIF parser as a dependency.

farindk commented 4 years ago

Hi John, the HEIF standard says that the EXIF rotation is informational only and should not be used to actually rotate the image. This is different from JPEG where the EXIF data is used to rotate the image.

Actually, carrying out rotation based on EXIF according your case 2 is wrong with HEIF images. I have written an HEIF->JPEG converter (not part of libheif), and the way to handle this correctly was to remove all Orientation, and resolution related tags from the EXIF before it was copied to the JPEG.

An API as you propose is not so simple, because in theory, HEIF transformations can be a chain of lots of different things. Rotate -> crop -> overlay with another image -> rotate back - > flip -> then put this into a grid with other images -> rotate the whole thing again -> ...

jcupitt commented 4 years ago

Hi Dirk, thank you for replying so quickly.

I'll remove our case 2 then, though I think that means some malformed images will not rotate correctly. I suppose I can at least blame someone else!

farindk commented 4 years ago

Yes, blaming someone else for case 2 is the way to go :-)

In fact, it's difficult to say how to correctly transform malformed images... If "correcting" malformed images means that errors have to be introduced into the processing that will in consequence introduce errors to correct images, it is not worth it.

jhonderson commented 4 years ago

My workaround was to use: convert input_file.jpg -rotate 270 output_file.jpg

After convert the image.

ziriax commented 3 years ago

To remove the orientation from the converted JPEG, I patched the heif-convert example in this repository using the attached patch, to be applied on top of v1.9.1 90b7f4f1e3db3e3451ddee6aa298c1de382b79e0

It uses libexif to parse the metadata, I didn't find a way to modify the metadata using this library. I guess a PR is not welcome since the dependency on the libexif?

orientation.zip

janokruta commented 3 years ago

@farindk Can you share source which proves this?

the HEIF standard says that the EXIF rotation is informational only and should not be used to actually rotate the image

I found the opposite information here: https://nokiatech.github.io/heif/technical.html#table-7

Name: Image Rotation (‘irot’) Type: Transformative Description: Rotation by 90, 180, or 270 degrees.

farindk commented 3 years ago

Your reference mentions the 'irot' box. This is the mandatory information. But this is not part of the EXIF data. The EXIF data is separate and also specifies a rotation (orientation). That EXIF metadata is informational only.

See the excerpt from the standard:

Screenshot from 2021-09-09 16-55-01

Kayvlim commented 2 years ago

I was affected by this behavior today:

GIMP (and at least Dolphin in KDE Plasma) is able to interpret the orientation tag and asks me whether to rotate the image. Keeping the original in GIMP is the right choice. Dolphin doesn't ask; it always rotates.

If anyone needs a "fix" that doesn't involve reprocessing the image, what I did was modify the EXIF information (using exif) for every image I converted from HEIC to JPG, and manually set Orientation = top-left.

mkdir ../modified
for image in *.jpg; do
  exif --ifd=0 --tag=0x112 --set-value=1 "$image" -o ../modified/"$image"
done
xiaoyjy commented 2 years ago

My trick way:

mv /usr/bin/heif-convert /usr/bin/heif-convert-bug

then edit /usr/bin/heif-convert paste and save

#!/bin/bash

lastnm="${@: -1}"
lastnm=`printf "%q" "$lastnm"`
suffix=${lastnm##*.}

args=()
for arg in "$@"; do
    args[${#args[@]}]=`printf "%q" "$arg"`
done

if [ "$suffix" = "jpg" ] || [ "$suffix" = "jpeg" ]; then
    eval heif-convert-bug ${args[@]}.png
    eval convert $lastnm.png $lastnm
    eval rm $lastnm.png
else
    eval heif-convert-bug ${args[@]}
fi

chmod +x /usr/bin/heif-convert

Batwam commented 2 years ago

I have noticed the same issue with converted files being rotated while the Exif value seems to be as per the original picture. I am having to add exiftool -n -Orientation=1 file.jpg after each conversion. Has a solution been implemented?

farindk commented 1 year ago

I have completely rewritten the orientation handling when converting from/to JPEG. Now, when converting from JPEG to HEIF, irot/imir boxes are generated to match the JPEG orientation. When converting from HEIF to JPEG, the image orientation is normalized, and the EXIF Orientation tag is set to "Normal". 8dc9fe14cec29d37f1191b03441455a56094b4ab 4d9f13b8e00220193dc3e0b2bcd655ca0e9fa02d

lastzero commented 1 year ago

Now, when converting from JPEG to HEIF, irot/imir boxes are generated to match the JPEG orientation. When converting from HEIF to JPEG, the image orientation is normalized, and the EXIF Orientation tag is set to "Normal".

Unfortunately, this test image is displayed with the wrong orientation using the latest release v1.15.2:

The only solution for us is to use the old version v1.13.0. It seems to generate the same output JPEG, but does not reset the orientation, resulting in a correctly displayed image.

farindk commented 1 year ago

@lastzero Hm I'm sure I already wrote an answer to this last week, but it's not here. Maybe I forgot to press 'send' ...

I had a look at the iphone_7.heic file and it is decoded correctly with libheif > 1.13.0. "Correctly" means in this case that the wrong orientation is the correct output. The coded image has size 4032x3024 (landscape) and there is no 'irot' box that would rotate it. I see that there is a rotation tag in the Exif data, but for HEIF, the Exif data is only informal and should not be used for carrying out the transformation.

There was a lot of confusion about this in the past and a lot of software was/is handling it incorrectly. The current behavior of libheif is correct.

If you want to emulate the old (wrong) behavior, you have to get the orientation tag from the Exif and compare it with the transformations in the HEIF (libheif v1.16.1 can now output the transformation properties). If they disagree, you can apply the Exif rotation. But be warned: this is a wrong behavior.

lastzero commented 1 year ago

Thank you for your reply, @farindk!

Inconsistent use of media metadata is a common phenomenon, unfortunately. We do our best to hide the complexity from our users, for example by automatically converting formats and normalizing data. However, since we don't have direct HEIC support in Go (which is why we very much appreciate your work), it's not clear to me how we can best work around the "wrong behavior":

farindk commented 1 year ago

I'm afraid there is no simple solution. When you say that

With v1.13.0, all of our file examples are displayed correctly

this is not actually true, since that version just made the same common errors. The current version (v1.16.0) handles it correctly.

If you define "correct" as the orientation that the image is intended to be, you might have to take a look at other Exif metadata and determine from what software it was generated and maybe the generation timestamp and then from that modify the orientation in the hope that it will be correct and not make things worse.

In my view, the best approach is to show images correctly according to the specification. If some of the images are then displayed wrong, there should be an easy way for the user to fix the file. This will put pressure on other software authors to write correct files.

lastzero commented 1 year ago

Thanks! I will check again. From what I remember, this mistake was made by Apple, which makes it difficult for small teams like ours to publicly blame them or successfully ask for improvements. Also, there appear to be many affected users and pictures, making it time consuming for us to provide support. I'm completely with you otherwise.

farindk commented 1 year ago

You could check Exif for Make=Apple and also the iOS version from Software and then handle it accordingly:

Make                            : Apple
Camera Model Name               : iPhone XS Max
Orientation                     : Rotate 90 CW
Software                        : 12.2

I have no overview which iOS versions behave in which way. You probably have a larger data basis than me.

lastzero commented 1 year ago

Our archive of sample files is not exhaustive. Feel free to look around and use what seems helpful to you:

I personally use Android and have very little time for HEIC in particular due to many other issues. If I learn more I will let you know!