aboutsummaryrefslogtreecommitdiffstats
path: root/web/js/dropzone.js.patch
diff options
context:
space:
mode:
authorMatthew Somerville <matthew@mysociety.org>2020-06-30 16:46:29 +0100
committerMatthew Somerville <matthew@mysociety.org>2020-07-01 16:55:27 +0100
commit47df99daf6371eb3d7cdbe1ac9170a7f88749dc1 (patch)
tree4b4600c40ffffaea4f471457901965ad2b269aa3 /web/js/dropzone.js.patch
parente623594f10d74a8dfb5eaea801c43f4996b15a1e (diff)
Fix photo orientation in modern browsers.
We use Dropzone (the photo upload library) to shrink photos client-side before uploading in the background and display thumbnails. For the resized upload, Dropzone restores the original Exif data, including orientation, so it can be correctly oriented server-side; for a thumbnail, it orients the image itself for immediate display. Recently, browsers have started honouring Exif orientation much more widely (Chrome 81+ and Firefox 77+ both now do it by default). This means the data Dropzone gets from a resize has already been oriented according to the Exif orientation data. Then Dropzone either looks at the orientation to correct for display (thumbnail), or adds back the Exif orientation data (upload) – in both cases, this leads to a double implementation of the orientation, and an incorrect display. To fix this, if we detect we are on a modern browser, we do not try and fix orientation ourself [1], and in all cases we do not add any Exif data back in (we only strip it server-side anyway). Conversely, that means on a non-modern browser, we always perform a manual orientation because no Exif data will be being sent server-side. Also includes a fix to the orientation code [2] which wouldn't be noticed in thumbnail generation as they are square, but could be now we may be orienting full size photos. [1] https://gitlab.com/meno/dropzone/-/merge_requests/80 [2] https://gitlab.com/meno/dropzone/-/merge_requests/45
Diffstat (limited to 'web/js/dropzone.js.patch')
-rw-r--r--web/js/dropzone.js.patch287
1 files changed, 248 insertions, 39 deletions
diff --git a/web/js/dropzone.js.patch b/web/js/dropzone.js.patch
index b325b45d8..60a82709a 100644
--- a/web/js/dropzone.js.patch
+++ b/web/js/dropzone.js.patch
@@ -1,17 +1,92 @@
---- dropzone.5.1.1.js 2017-06-30 09:46:43.000000000 +0100
-+++ dropzone.exiffixes.js 2017-06-30 18:25:27.000000000 +0100
-@@ -1175,9 +1175,7 @@
- };
- if ((typeof EXIF !== "undefined" && EXIF !== null) && fixOrientation) {
- loadExif = function(callback) {
+--- dropzone.5.1.1.js 2020-06-30 15:56:05.557790000 +0100
++++ dropzone.exiffixes.js 2020-06-30 16:40:22.794951100 +0100
+@@ -26,7 +26,7 @@
+ */
+
+ (function() {
+- var Dropzone, Emitter, ExifRestore, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without,
++ var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without,
+ slice = [].slice,
+ extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+@@ -1123,7 +1123,7 @@
+ };
+
+ Dropzone.prototype.resizeImage = function(file, width, height, resizeMethod, callback) {
+- return this.createThumbnail(file, width, height, resizeMethod, false, (function(_this) {
++ return this.createThumbnail(file, width, height, resizeMethod, true, (function(_this) {
+ return function(dataUrl, canvas) {
+ var resizeMimeType, resizedDataURL;
+ if (canvas === null) {
+@@ -1134,9 +1134,6 @@
+ resizeMimeType = file.type;
+ }
+ resizedDataURL = canvas.toDataURL(resizeMimeType, _this.options.resizeQuality);
+- if (resizeMimeType === 'image/jpeg' || resizeMimeType === 'image/jpg') {
+- resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL);
+- }
+ return callback(Dropzone.dataURItoBlob(resizedDataURL));
+ }
+ };
+@@ -1164,23 +1161,17 @@
+ Dropzone.prototype.createThumbnailFromUrl = function(file, width, height, resizeMethod, fixOrientation, callback, crossOrigin) {
+ var img;
+ img = document.createElement("img");
++
++ // FixOrientation not needed anymore with browsers handling imageOrientation
++ fixOrientation = (getComputedStyle(document.body)['imageOrientation'] == 'from-image') ? false : fixOrientation;
++
+ if (crossOrigin) {
+ img.crossOrigin = crossOrigin;
+ }
+ img.onload = (function(_this) {
+ return function() {
+- var loadExif;
+- loadExif = function(callback) {
+- return callback(1);
+- };
+- if ((typeof EXIF !== "undefined" && EXIF !== null) && fixOrientation) {
+- loadExif = function(callback) {
- return EXIF.getData(img, function() {
- return callback(EXIF.getTag(this, 'Orientation'));
- });
-+ return callback(EXIF.getData(img));
- };
- }
- return loadExif(function(orientation) {
-@@ -1601,7 +1599,7 @@
+- };
+- }
+- return loadExif(function(orientation) {
++ var orientation = fixOrientation ? EXIF.getData(img) : 1;
++
+ var canvas, ctx, ref, ref1, ref2, ref3, resizeInfo, thumbnail;
+ file.width = img.width;
+ file.height = img.height;
+@@ -1212,23 +1203,23 @@
+ break;
+ case 6:
+ ctx.rotate(0.5 * Math.PI);
+- ctx.translate(0, -canvas.height);
++ ctx.translate(0, -canvas.width);
+ break;
+ case 7:
+ ctx.rotate(0.5 * Math.PI);
+- ctx.translate(canvas.width, -canvas.height);
++ ctx.translate(canvas.height, -canvas.width);
+ ctx.scale(-1, 1);
+ break;
+ case 8:
+ ctx.rotate(-0.5 * Math.PI);
+- ctx.translate(-canvas.width, 0);
++ ctx.translate(-canvas.height, 0);
+ }
+ drawImageIOSFix(ctx, img, (ref = resizeInfo.srcX) != null ? ref : 0, (ref1 = resizeInfo.srcY) != null ? ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (ref2 = resizeInfo.trgX) != null ? ref2 : 0, (ref3 = resizeInfo.trgY) != null ? ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight);
+ thumbnail = canvas.toDataURL("image/png");
+ if (callback != null) {
+ return callback(thumbnail, canvas);
+ }
+- });
++
+ };
+ })(this);
+ if (callback != null) {
+@@ -1601,7 +1592,7 @@
return results;
};
@@ -20,31 +95,165 @@
Dropzone.isBrowserSupported = function() {
var capableBrowser, j, len, ref, regex;
-@@ -1904,6 +1902,27 @@
- var array, ato, buf, imageData, mae, separatePoint;
- imageData = resizedFileBase64.replace('data:image/jpeg;base64,', '');
- buf = this.decode64(imageData);
-+
-+ // Certain browsers (I'm looking at you, Safari) 'helpfully' provide their
-+ // own EXIF data in the JPEG returned from HTMLCanvasElement.toDataURL.
-+ // Dropzone doesn't take this into account when restoring the original
-+ // file's EXIF, meaning the final uploaded file has two sets of EXIF.
-+ // Certain JPEG tools (I'm looking at you, jhead) don't really handle this
-+ // very well, either ignoring the duplicate EXIF, picking the wrong one
-+ // or refusing to process the file entirely.
-+ // Seems like the best way out of this mess is to make sure the uploaded
-+ // JPEG only ever has one EXIF header. In this case, we want to keep the
-+ // EXIF from the original file.
-+ // This little loop inspects the new JPEG from the toDataURL call and
-+ // strips out any existing EXIF headers (technically any APP1 headers,
-+ // but same difference in this case).
-+ for (var i = 0; i < buf.length; i++) {
-+ if (buf[i] === 255 && buf[i+1] === 225) {
-+ var length = buf[i + 2] * 256 + buf[i + 3] + 2;
-+ buf.splice(i, length);
-+ }
-+ }
-+
- separatePoint = buf.indexOf(255, 3);
- mae = buf.slice(0, separatePoint);
- ato = buf.slice(separatePoint);
+@@ -1828,161 +1819,6 @@
+ return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
+ };
+
+- ExifRestore = (function() {
+- function ExifRestore() {}
+-
+- ExifRestore.KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+-
+- ExifRestore.encode64 = function(input) {
+- var chr1, chr2, chr3, enc1, enc2, enc3, enc4, i, output;
+- output = '';
+- chr1 = void 0;
+- chr2 = void 0;
+- chr3 = '';
+- enc1 = void 0;
+- enc2 = void 0;
+- enc3 = void 0;
+- enc4 = '';
+- i = 0;
+- while (true) {
+- chr1 = input[i++];
+- chr2 = input[i++];
+- chr3 = input[i++];
+- enc1 = chr1 >> 2;
+- enc2 = (chr1 & 3) << 4 | chr2 >> 4;
+- enc3 = (chr2 & 15) << 2 | chr3 >> 6;
+- enc4 = chr3 & 63;
+- if (isNaN(chr2)) {
+- enc3 = enc4 = 64;
+- } else if (isNaN(chr3)) {
+- enc4 = 64;
+- }
+- output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4);
+- chr1 = chr2 = chr3 = '';
+- enc1 = enc2 = enc3 = enc4 = '';
+- if (!(i < input.length)) {
+- break;
+- }
+- }
+- return output;
+- };
+-
+- ExifRestore.restore = function(origFileBase64, resizedFileBase64) {
+- var image, rawImage, segments;
+- if (!origFileBase64.match('data:image/jpeg;base64,')) {
+- return resizedFileBase64;
+- }
+- rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', ''));
+- segments = this.slice2Segments(rawImage);
+- image = this.exifManipulation(resizedFileBase64, segments);
+- return 'data:image/jpeg;base64,' + this.encode64(image);
+- };
+-
+- ExifRestore.exifManipulation = function(resizedFileBase64, segments) {
+- var aBuffer, exifArray, newImageArray;
+- exifArray = this.getExifArray(segments);
+- newImageArray = this.insertExif(resizedFileBase64, exifArray);
+- aBuffer = new Uint8Array(newImageArray);
+- return aBuffer;
+- };
+-
+- ExifRestore.getExifArray = function(segments) {
+- var seg, x;
+- seg = void 0;
+- x = 0;
+- while (x < segments.length) {
+- seg = segments[x];
+- if (seg[0] === 255 & seg[1] === 225) {
+- return seg;
+- }
+- x++;
+- }
+- return [];
+- };
+-
+- ExifRestore.insertExif = function(resizedFileBase64, exifArray) {
+- var array, ato, buf, imageData, mae, separatePoint;
+- imageData = resizedFileBase64.replace('data:image/jpeg;base64,', '');
+- buf = this.decode64(imageData);
+- separatePoint = buf.indexOf(255, 3);
+- mae = buf.slice(0, separatePoint);
+- ato = buf.slice(separatePoint);
+- array = mae;
+- array = array.concat(exifArray);
+- array = array.concat(ato);
+- return array;
+- };
+-
+- ExifRestore.slice2Segments = function(rawImageArray) {
+- var endPoint, head, length, seg, segments;
+- head = 0;
+- segments = [];
+- while (true) {
+- if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {
+- break;
+- }
+- if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {
+- head += 2;
+- } else {
+- length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3];
+- endPoint = head + length + 2;
+- seg = rawImageArray.slice(head, endPoint);
+- segments.push(seg);
+- head = endPoint;
+- }
+- if (head > rawImageArray.length) {
+- break;
+- }
+- }
+- return segments;
+- };
+-
+- ExifRestore.decode64 = function(input) {
+- var base64test, buf, chr1, chr2, chr3, enc1, enc2, enc3, enc4, i, output;
+- output = '';
+- chr1 = void 0;
+- chr2 = void 0;
+- chr3 = '';
+- enc1 = void 0;
+- enc2 = void 0;
+- enc3 = void 0;
+- enc4 = '';
+- i = 0;
+- buf = [];
+- base64test = /[^A-Za-z0-9\+\/\=]/g;
+- if (base64test.exec(input)) {
+- console.warning('There were invalid base64 characters in the input text.\n' + 'Valid base64 characters are A-Z, a-z, 0-9, \'+\', \'/\',and \'=\'\n' + 'Expect errors in decoding.');
+- }
+- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+- while (true) {
+- enc1 = this.KEY_STR.indexOf(input.charAt(i++));
+- enc2 = this.KEY_STR.indexOf(input.charAt(i++));
+- enc3 = this.KEY_STR.indexOf(input.charAt(i++));
+- enc4 = this.KEY_STR.indexOf(input.charAt(i++));
+- chr1 = enc1 << 2 | enc2 >> 4;
+- chr2 = (enc2 & 15) << 4 | enc3 >> 2;
+- chr3 = (enc3 & 3) << 6 | enc4;
+- buf.push(chr1);
+- if (enc3 !== 64) {
+- buf.push(chr2);
+- }
+- if (enc4 !== 64) {
+- buf.push(chr3);
+- }
+- chr1 = chr2 = chr3 = '';
+- enc1 = enc2 = enc3 = enc4 = '';
+- if (!(i < input.length)) {
+- break;
+- }
+- }
+- return buf;
+- };
+-
+- return ExifRestore;
+-
+- })();
+-
+-
+ /*
+ * contentloaded.js
+ *