blueimp / JavaScript-Load-Image

Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled, cropped or rotated HTML img or canvas element. Use methods to parse image metadata to extract IPTC and Exif tags as well as embedded thumbnail images, to overwrite the Exif Orientation value and to restore the complete image header after resizing.
https://blueimp.github.io/JavaScript-Load-Image/
MIT License
4.46k stars 923 forks source link

Orientation correction not working in Safari 13.1 (including iOS) #97

Closed gbresciano closed 4 years ago

gbresciano commented 4 years ago

Hi, first, I want to thank you for this library. It's been very useful for me.

It seems that there's a problem with the last version of safari. When loading a portrait image (orientation = 6) it shows up as a landscape image You can reproduce it with this image in the demo and you'll notice that the result is rotated as in this screenshot:

Screenshot 2020-04-06 at 13 44 44

This doesn't happen in chrome.

Can you help?

Thanks!

aogaili commented 4 years ago

+1 I just ran into the same issue as well.

gbresciano commented 4 years ago

It seems that the latest version of safari autorotates based on EXIF data and then the image loader rotates again

aogaili commented 4 years ago

Yes, I can confirm that this is the case, I've removed the orientation attribute and indeed the correct orientation is rendered!

gbresciano commented 4 years ago

I suspect this might be linked to the implementation of a new css property called image-orientation (https://webkit.org/css-status/#property-image-orientation)

As a temporary workaround, I'm only enabling the orientation option if the image-orientation css property is not available

{ orientation: document.createElement('img').style.imageOrientation === undefined }

aogaili commented 4 years ago

Thanks @gbresciano for this research and workaround!

gbresciano commented 4 years ago

I just noticed firefox has css image-orientation but doesn't rotate the photo automatically, so the workaround is not good

hartdn commented 4 years ago

I was going to work-around this by checking for the version of Safari, but I have noticed that if you choose to resize on iOS or iPados then it strips the exif data. So now I don't now what to do.

beavis commented 4 years ago

What about checking if default-orientation computed value is "from-image" and disabling orientation if that is the case?

{ orientation: getComputedStyle(document.body).imageOrientation !== 'from-image' }

Would it be possible for the library to just force to use image-orientation: 'none' when generating the image? Does canvas drawImage respect that?

Edit: I'm afraid on safari canvas doesn't care about that and uses the default image-from behaviour: check this url on safari: https://mcc.id.au/2020/image-orientation/ Related chromium issue: https://github.com/w3c/csswg-drafts/issues/4666

blueimp commented 4 years ago

Thanks for the report @gbresciano and everyone's comments. As visible from @beavis chromium issue link, the same problem affects the next major version of Chrome (v81).

I have not found a good workaround using the style.imageOrientation property on either the img or document.body elements. I was originally hoping that I can set img.style.imageOrientation = 'none' before drawing into the canvas element from the img source, but this had no effect.

I do have a workaround testing EXIF image orientation support the same as in the unit tests: https://github.com/blueimp/JavaScript-Load-Image/blob/452adbfc9f4980e0bed0d97d19fc2343475c447f/test/test.js#L839-L854 But that means including a 713 Bytes JPEG image (might be possible to make it smaller) in the library itself.

Will test a bit further before pushing a fix.

beavis commented 4 years ago

I was originally hoping that I can set img.style.imageOrientation = 'none' before drawing into the canvas element from the img source, but this had no effect.

That was my initial thought and in chrome canary looks like it's actually working, either setting it on the image or in the canvas element makes it render in the original orientation, but seems safari does not support that, you can check it on last row of images here: https://mcc.id.au/2020/image-orientation/

But that means including a 713 Bytes JPEG image (might be possible to make it smaller) in the library itself.

I was testing checking against computed value of imageOrientation property, not sure how safe or future proof this is. Would this 329 bytes jpg work for the check? https://raw.githubusercontent.com/beavis/orientation/master/orientation6.jpg

blueimp commented 4 years ago

Yes, that looks usable.

I was thinking about basing a minimum file on https://github.com/mathiasbynens/small/blob/master/jpeg.jpg, but I've only ever researched JPEG image headers, not JPEG image content.

beavis commented 4 years ago

I can't decode that one, seems it's an arithmetic JPG but it that works for your test it will be smaller for sure. I just created a sample 2x1 black image with an image editor and ran jpegoptim against it then added the exif field. In case you need it I just added an MIT license readme to that repo, but I don't mind about that at all, take the file as yours.

beavis commented 4 years ago

By the way, looks like safari ignoring image-context: none on canvas drawImage is a bug that has already been patched: https://bugs.webkit.org/show_bug.cgi?id=209849

blueimp commented 4 years ago

Good news about the safari patch, that would allow a workaround without a test image.

Didn't know that jpegoptim works that well for minimal files, I'll use the same approach, thanks. Which tool did you use to add the EXIF info? I have a 350 bytes file that is black/white 1x2 pixels and has the Orientation flag, transplanted via jhead, but was wondering if there's a better tool.

beavis commented 4 years ago

Which tool did you use to add the EXIF info?

I just used a GUI for exiftool, not sure if that works any better than your approach.

blueimp commented 4 years ago

Thanks! Edit: I've found a way to only write the minimal headers with exiftool. For reference, here's the command-line I used to create a 350 bytes version with EXIF Orientation and IPTC ObjectName properties:

exiftool -all= -Orientation#=6 -YCbCrPositioning= -ResolutionUnit= -YResolution= -XResolution= -ObjectName=test image.jpg
blueimp commented 4 years ago

I've released blueimp-load-image@2.29.0 that fixes this orientation issue.

gbresciano commented 4 years ago

thanks @blueimp!

yarosdev commented 4 years ago

Orientation is fixed but something wrong with data.originalHeight and data.originalWidth; On desktop it returns width and height as before orientation fix, on mobile chrome it returns viseverse

blueimp commented 4 years ago

The originalWidth/originalHeight properties are set on image load: https://github.com/blueimp/JavaScript-Load-Image/blob/ff22e11daa25d0cff328124abf1cca22ffaa4be5/js/load-image.js#L132-L140

For browsers with automatic image orientation support, those properties will reflect the dimensions of the image after orientation correction.

yarosdev commented 4 years ago

Yes, but there is no consistency, I can not relay on originalWidth/originalHeight any more, I have an image A.jpg and it has vary dimensions from browser to browser, so only one solution I found it is using width and height from exif but it is strange

blueimp commented 4 years ago

That inconsistency comes from the difference in browser support. If exif parsing is enabled, we could normalize the originalWidth/originalHeight properties, but then we have an inconsistency between loading an image with exif parsing enabled and loading one without. It might still be worth it though, so I'll think about introducing this normalization.

yarosdev commented 4 years ago

Now I need to do like this (also I need to swap w/h if it is rotated):

const orient = data.exif ? data.exif.get('Orientation') : null;

const oWidth  = data.exif ? (data.exif[256] || data.originalWidth) : data.originalWidth;
const oHeight = data.exif ? (data.exif[257] || data.originalHeight) : data.originalHeight;

let width  = oWidth;
let height = oHeight;

if (4 < orient && orient < 9) {
    width = oHeight;
    height = oWidth;
}
blueimp commented 4 years ago

I've added original width+height dimensions normalization in this commit: https://github.com/blueimp/JavaScript-Load-Image/commit/61761388e0b9fff19acef3c4d5c1413c11b8c843 It will be part of the next release.