From 6832110af46e31831788c599b70ac047c7512e16 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 9 May 2018 15:41:07 +0900 Subject: [PATCH] Correct rotate of image using EXIF (#7422) --- app/javascript/mastodon/utils/resize_image.js | 107 ++++++++++++++---- package.json | 1 + yarn.lock | 4 + 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js index 6442eda38c3..54459de3ecc 100644 --- a/app/javascript/mastodon/utils/resize_image.js +++ b/app/javascript/mastodon/utils/resize_image.js @@ -1,3 +1,5 @@ +import EXIF from 'exif-js'; + const MAX_IMAGE_DIMENSION = 1280; const getImageUrl = inputFile => new Promise((resolve, reject) => { @@ -28,6 +30,84 @@ const loadImage = inputFile => new Promise((resolve, reject) => { }).catch(reject); }); +const getOrientation = (img, type = 'image/png') => new Promise(resolve => { + if (type !== 'image/jpeg') { + resolve(1); + return; + } + + EXIF.getData(img, () => { + const orientation = EXIF.getTag(img, 'Orientation'); + resolve(orientation); + }); +}); + +const processImage = (img, { width, height, orientation, type = 'image/png' }) => new Promise(resolve => { + const canvas = document.createElement('canvas'); + [canvas.width, canvas.height] = orientation < 5 ? [width, height] : [height, width]; + + const context = canvas.getContext('2d'); + + switch (orientation) { + case 2: + context.translate(width, 0); + break; + case 3: + context.translate(width, height); + break; + case 4: + context.translate(0, height); + break; + case 5: + context.rotate(0.5 * Math.PI); + context.translate(1, -1); + break; + case 6: + context.rotate(0.5 * Math.PI); + context.translate(0, -height); + break; + case 7: + context.rotate(0.5, Math.PI); + context.translate(width, -height); + break; + case 8: + context.rotate(-0.5, Math.PI); + context.translate(-width, 0); + break; + } + + context.drawImage(img, 0, 0, width, height); + + canvas.toBlob(resolve, type); +}); + +const resizeImage = (img, type = 'image/png') => new Promise((resolve, reject) => { + const { width, height } = img; + + let newWidth, newHeight; + + if (width > height) { + newHeight = height * MAX_IMAGE_DIMENSION / width; + newWidth = MAX_IMAGE_DIMENSION; + } else if (height > width) { + newWidth = width * MAX_IMAGE_DIMENSION / height; + newHeight = MAX_IMAGE_DIMENSION; + } else { + newWidth = MAX_IMAGE_DIMENSION; + newHeight = MAX_IMAGE_DIMENSION; + } + + getOrientation(img, type) + .then(orientation => processImage(img, { + width: newWidth, + height: newHeight, + orientation, + type, + })) + .then(resolve) + .catch(reject); +}); + export default inputFile => new Promise((resolve, reject) => { if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') { resolve(inputFile); @@ -35,32 +115,13 @@ export default inputFile => new Promise((resolve, reject) => { } loadImage(inputFile).then(img => { - const canvas = document.createElement('canvas'); - const { width, height } = img; - - let newWidth, newHeight; - - if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) { + if (img.width < MAX_IMAGE_DIMENSION && img.height < MAX_IMAGE_DIMENSION) { resolve(inputFile); return; } - if (width > height) { - newHeight = height * MAX_IMAGE_DIMENSION / width; - newWidth = MAX_IMAGE_DIMENSION; - } else if (height > width) { - newWidth = width * MAX_IMAGE_DIMENSION / height; - newHeight = MAX_IMAGE_DIMENSION; - } else { - newWidth = MAX_IMAGE_DIMENSION; - newHeight = MAX_IMAGE_DIMENSION; - } - - canvas.width = newWidth; - canvas.height = newHeight; - - canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight); - - canvas.toBlob(resolve, inputFile.type); + resizeImage(img, inputFile.type) + .then(resolve) + .catch(() => resolve(inputFile)); }).catch(reject); }); diff --git a/package.json b/package.json index 75e9e9f6dc2..61f38409c04 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "emoji-mart": "Gargron/emoji-mart#build", "es6-symbol": "^3.1.1", "escape-html": "^1.0.3", + "exif-js": "^2.3.0", "express": "^4.16.2", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^0.11.2", diff --git a/yarn.lock b/yarn.lock index c5a49a497e8..50c88557d2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2601,6 +2601,10 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exif-js@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/exif-js/-/exif-js-2.3.0.tgz#9d10819bf571f873813e7640241255ab9ce1a814" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"