From 89131dd1651f6636884547eec4f9e2470dfae719 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 2 Jul 2020 10:11:59 +0200 Subject: [PATCH] Strip exif information from loaded image in Resizer#loadFile Modern browsers take the Exif orientation into account when showing a JPEG within an HTMLImageElement. Unfortunately this orientation is not only visual, but extends to the blob received when reading this image into a canvas. The JavaScript based image resizer using within the attachment system takes care reinsert the original Exif data after fetching the resized blob from pica.js. This causes the image to be reoriented multiple times, ultimately leading to an incorrectly oriented image: 1. The browser rotates the image. 2. The server rotates the image again, because the original Exif information has been preserved. To fix this issue we strip the Exif information before handing the blob over to the HTMLImageElement, forcing the browser to use the raw pixels instead of pretending to be smart. When the Exif information is reinserted after resizing the image that will be uploaded will then be reoriented only once: On the server. During fixing of this bug it was also investigated whether one can find out whether the browser reoriented the image, it looks like one cannot. It was also tested whether setting `image-orientation: none` will have any effect. It only has in Firefox: When image-orientation: none is set you will get the behavior as if no Exif information is present. In Chrome the source image will not be be reoriented when rendered inside of the DOM. Reading the pixel values however still returns the reoriented garbage. Thus stripping the exif information is the best solution to combat web browsers attempting to be smart. Unfortunately it comes with an increased processing requirement, because the raw blob (possible multiple megabytes) will need to be processed to strip the Exif data. --- .../js/WoltLabSuite/Core/Image/Resizer.js | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js index 8d893dad5c..53687ba902 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js @@ -113,34 +113,41 @@ define([ */ loadFile: function (file) { var exif = undefined; + var fileData = Promise.resolve(file); if (file.type === 'image/jpeg') { // Extract EXIF data exif = ExifUtil.getExifBytesFromJpeg(file); + + // Strip EXIF data + fileData = fileData.then(ExifUtil.removeExifData.bind(ExifUtil)); } - var loader = new Promise(function (resolve, reject) { - var reader = new FileReader(); - var image = new Image(); - - reader.addEventListener('load', function () { - image.src = reader.result; - }); - - reader.addEventListener('error', function () { - reader.abort(); - reject(reader.error); - }); - - image.addEventListener('error', reject); - - image.addEventListener('load', function () { - resolve(image); + var fileData = fileData + .then(function (blob) { + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + var image = new Image(); + + reader.addEventListener('load', function () { + image.src = reader.result; + }); + + reader.addEventListener('error', function () { + reader.abort(); + reject(reader.error); + }); + + image.addEventListener('error', reject); + + image.addEventListener('load', function () { + resolve(image); + }); + + reader.readAsDataURL(blob); + }); }); - - reader.readAsDataURL(file); - }); - return Promise.all([ exif, loader ]) + return Promise.all([ exif, fileData ]) .then(function (result) { return { exif: result[0], image: result[1] }; }); -- 2.20.1