Preserve file type if image contains transparent pixels
authorTim Düsterhus <duesterhus@woltlab.com>
Fri, 29 Mar 2019 16:22:23 +0000 (17:22 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Fri, 29 Mar 2019 16:23:10 +0000 (17:23 +0100)
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WoltLabSuite/Core/ExifUtil.js [deleted file]
wcfsetup/install/files/js/WoltLabSuite/Core/Image/ExifUtil.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Image/ImageUtil.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/ImageResizer.js [deleted file]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 8abb95b879f5e1602cafbc466c50227b3a079b3b..34cf0c472083b7de51af4e17179a521733edfaea 100644 (file)
@@ -296,9 +296,10 @@ if (COMPILER_TARGET_DEFAULT) {
                        
                        require([
                                'WoltLabSuite/Core/FileUtil',
-                               'WoltLabSuite/Core/ImageResizer',
+                               'WoltLabSuite/Core/Image/ImageUtil',
+                               'WoltLabSuite/Core/Image/Resizer',
                                'WoltLabSuite/Core/Ajax/Status'
-                       ], (function (FileUtil, ImageResizer, AjaxStatus) {
+                       ], (function (FileUtil, ImageUtil, ImageResizer, AjaxStatus) {
                                AjaxStatus.show();
                                
                                var files = [];
@@ -360,7 +361,7 @@ if (COMPILER_TARGET_DEFAULT) {
                                                                                        
                                                                                        var fileType = this._options.autoScale.fileType;
                                                                                        
-                                                                                       if (this._options.autoScale.fileType === 'keep') {
+                                                                                       if (this._options.autoScale.fileType === 'keep' || ImageUtil.containsTransparentPixels(resizedImage)) {
                                                                                                fileType = file.type;
                                                                                        }
                                                                                        
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/ExifUtil.js b/wcfsetup/install/files/js/WoltLabSuite/Core/ExifUtil.js
deleted file mode 100644 (file)
index 8db5931..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/**
- * Provides helper functions for Exif metadata handling.
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/ExifUtil
- */
-define([], function() {
-       "use strict";
-       
-       var _tagNames = {
-               'SOI':   0xD8, // Start of image
-               'APP0':  0xE0, // JFIF tag
-               'APP1':  0xE1, // EXIF / XMP
-               'APP2':  0xE2, // General purpose tag
-               'APP3':  0xE3, // General purpose tag
-               'APP4':  0xE4, // General purpose tag
-               'APP5':  0xE5, // General purpose tag
-               'APP6':  0xE6, // General purpose tag
-               'APP7':  0xE7, // General purpose tag
-               'APP8':  0xE8, // General purpose tag
-               'APP9':  0xE9, // General purpose tag
-               'APP10': 0xEA, // General purpose tag
-               'APP11': 0xEB, // General purpose tag
-               'APP12': 0xEC, // General purpose tag
-               'APP13': 0xED, // General purpose tag
-               'APP14': 0xEE, // Often used to store copyright information
-               'COM':   0xFE, // Comments
-       };
-       
-       // Known sequence signatures
-       var _signatureEXIF = 'Exif';
-       var _signatureXMP  = 'http://ns.adobe.com/xap/1.0/';
-       
-       return {
-               /**
-                * Extracts the EXIF / XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                                  JPEG blob
-                * @returns             {Promise<Uint8Array | TypeError>}       Promise resolving with the EXIF / XMP sections
-                */
-               getExifBytesFromJpeg: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function() {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       var exif = new Uint8Array();
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only copy Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               // append the found EXIF sequence, usually only a single EXIF (APP1) sequence should be defined
-                                                               var sequence = Array.prototype.slice.call(bytes, i, length + i); // IE11 does not have slice in the Uint8Array prototype
-                                                               var concat = new Uint8Array(exif.length + sequence.length);
-                                                               concat.set(exif);
-                                                               concat.set(sequence, exif.length);
-                                                               exif = concat;
-                                                       }
-                                               }
-                                               
-                                               i += length
-                                       }
-                                       
-                                       // No EXIF data found
-                                       resolve(exif);
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Removes all EXIF and XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                          JPEG blob
-                * @returns             {Promise<Blob | TypeError>}     Promise resolving with the altered JPEG blob
-                */
-               removeExifData: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function () {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only remove Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               var start = Array.prototype.slice.call(bytes, 0, i);
-                                                               var end = Array.prototype.slice.call(bytes, i + length);
-                                                               bytes = new Uint8Array(start.length + end.length);
-                                                               bytes.set(start, 0);
-                                                               bytes.set(end, start.length);
-                                                       }
-                                               }
-                                               else {
-                                                       i += length;
-                                               }
-                                       }
-                                       
-                                       resolve(new Blob([bytes], {type: blob.type}));
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Overrides the APP1 (EXIF / XMP) sections of a JPEG blob with the given data.
-                *
-                * @param       blob    {Blob}                  JPEG blob
-                * @param       exif    {Uint8Array}            APP1 sections
-                * @returns             {Promise<Blob | never>} Promise resolving with the altered JPEG blob
-                */
-               setExifData: function (blob, exif) {
-                       return this.removeExifData(blob).then(function (blob) {
-                               return new Promise(function (resolve) {
-                                       var reader = new FileReader();
-                                       
-                                       reader.addEventListener('error', function () {
-                                               reader.abort();
-                                               reject(reader.error);
-                                       });
-                                       
-                                       reader.addEventListener('load', function () {
-                                               var buffer = reader.result;
-                                               var bytes = new Uint8Array(buffer);
-                                               var offset = 2;
-                                               
-                                               // check if the second tag is the JFIF tag
-                                               if (bytes[2] === 0xFF && bytes[3] === _tagNames.APP0) {
-                                                       offset += 2 + ((bytes[4] << 8) | bytes[5]);
-                                               }
-                                               
-                                               var start = Array.prototype.slice.call(bytes, 0, offset);
-                                               var end = Array.prototype.slice.call(bytes, offset);
-                                               
-                                               bytes = new Uint8Array(start.length + exif.length + end.length);
-                                               bytes.set(start);
-                                               bytes.set(exif, offset);
-                                               bytes.set(end, offset + exif.length);
-                                               
-                                               resolve(new Blob([bytes], {type: blob.type}));
-                                       });
-                                       
-                                       reader.readAsArrayBuffer(blob);
-                               });
-                       });
-               }
-       };
-});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Image/ExifUtil.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Image/ExifUtil.js
new file mode 100644 (file)
index 0000000..618446c
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * Provides helper functions for Exif metadata handling.
+ *
+ * @author     Maximilian Mader
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Image/ExifUtil
+ */
+define([], function() {
+       "use strict";
+       
+       var _tagNames = {
+               'SOI':   0xD8, // Start of image
+               'APP0':  0xE0, // JFIF tag
+               'APP1':  0xE1, // EXIF / XMP
+               'APP2':  0xE2, // General purpose tag
+               'APP3':  0xE3, // General purpose tag
+               'APP4':  0xE4, // General purpose tag
+               'APP5':  0xE5, // General purpose tag
+               'APP6':  0xE6, // General purpose tag
+               'APP7':  0xE7, // General purpose tag
+               'APP8':  0xE8, // General purpose tag
+               'APP9':  0xE9, // General purpose tag
+               'APP10': 0xEA, // General purpose tag
+               'APP11': 0xEB, // General purpose tag
+               'APP12': 0xEC, // General purpose tag
+               'APP13': 0xED, // General purpose tag
+               'APP14': 0xEE, // Often used to store copyright information
+               'COM':   0xFE, // Comments
+       };
+       
+       // Known sequence signatures
+       var _signatureEXIF = 'Exif';
+       var _signatureXMP  = 'http://ns.adobe.com/xap/1.0/';
+       
+       return {
+               /**
+                * Extracts the EXIF / XMP sections of a JPEG blob.
+                *
+                * @param       blob    {Blob}                                  JPEG blob
+                * @returns             {Promise<Uint8Array | TypeError>}       Promise resolving with the EXIF / XMP sections
+                */
+               getExifBytesFromJpeg: function (blob) {
+                       return new Promise(function (resolve, reject) {
+                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
+                                       return reject(new TypeError('The argument must be a Blob or a File'));
+                               }
+                               
+                               var reader = new FileReader();
+                               
+                               reader.addEventListener('error', function () {
+                                       reader.abort();
+                                       reject(reader.error);
+                               });
+                               
+                               reader.addEventListener('load', function() {
+                                       var buffer = reader.result;
+                                       var bytes = new Uint8Array(buffer);
+                                       var exif = new Uint8Array();
+                                       
+                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
+                                               return reject(new Error('Not a JPEG'));
+                                       }
+                                       
+                                       for (var i = 2; i < bytes.length;) {
+                                               // each sequence starts with 0xFF
+                                               if (bytes[i] !== 0xFF) break;
+                                               
+                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
+                                               
+                                               // Check if the next byte indicates an EXIF sequence
+                                               if (bytes[i + 1] === _tagNames.APP1) {
+                                                       var signature = '';
+                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
+                                                               signature += String.fromCharCode(bytes[j]);
+                                                       }
+                                                       
+                                                       // Only copy Exif and XMP data
+                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
+                                                               // append the found EXIF sequence, usually only a single EXIF (APP1) sequence should be defined
+                                                               var sequence = Array.prototype.slice.call(bytes, i, length + i); // IE11 does not have slice in the Uint8Array prototype
+                                                               var concat = new Uint8Array(exif.length + sequence.length);
+                                                               concat.set(exif);
+                                                               concat.set(sequence, exif.length);
+                                                               exif = concat;
+                                                       }
+                                               }
+                                               
+                                               i += length
+                                       }
+                                       
+                                       // No EXIF data found
+                                       resolve(exif);
+                               });
+                               
+                               reader.readAsArrayBuffer(blob);
+                       });
+               },
+               
+               /**
+                * Removes all EXIF and XMP sections of a JPEG blob.
+                *
+                * @param       blob    {Blob}                          JPEG blob
+                * @returns             {Promise<Blob | TypeError>}     Promise resolving with the altered JPEG blob
+                */
+               removeExifData: function (blob) {
+                       return new Promise(function (resolve, reject) {
+                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
+                                       return reject(new TypeError('The argument must be a Blob or a File'));
+                               }
+                               
+                               var reader = new FileReader();
+                               
+                               reader.addEventListener('error', function () {
+                                       reader.abort();
+                                       reject(reader.error);
+                               });
+                               
+                               reader.addEventListener('load', function () {
+                                       var buffer = reader.result;
+                                       var bytes = new Uint8Array(buffer);
+                                       
+                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
+                                               return reject(new Error('Not a JPEG'));
+                                       }
+                                       
+                                       for (var i = 2; i < bytes.length;) {
+                                               // each sequence starts with 0xFF
+                                               if (bytes[i] !== 0xFF) break;
+                                               
+                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
+                                               
+                                               // Check if the next byte indicates an EXIF sequence
+                                               if (bytes[i + 1] === _tagNames.APP1) {
+                                                       var signature = '';
+                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
+                                                               signature += String.fromCharCode(bytes[j]);
+                                                       }
+                                                       
+                                                       // Only remove Exif and XMP data
+                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
+                                                               var start = Array.prototype.slice.call(bytes, 0, i);
+                                                               var end = Array.prototype.slice.call(bytes, i + length);
+                                                               bytes = new Uint8Array(start.length + end.length);
+                                                               bytes.set(start, 0);
+                                                               bytes.set(end, start.length);
+                                                       }
+                                               }
+                                               else {
+                                                       i += length;
+                                               }
+                                       }
+                                       
+                                       resolve(new Blob([bytes], {type: blob.type}));
+                               });
+                               
+                               reader.readAsArrayBuffer(blob);
+                       });
+               },
+               
+               /**
+                * Overrides the APP1 (EXIF / XMP) sections of a JPEG blob with the given data.
+                *
+                * @param       blob    {Blob}                  JPEG blob
+                * @param       exif    {Uint8Array}            APP1 sections
+                * @returns             {Promise<Blob | never>} Promise resolving with the altered JPEG blob
+                */
+               setExifData: function (blob, exif) {
+                       return this.removeExifData(blob).then(function (blob) {
+                               return new Promise(function (resolve) {
+                                       var reader = new FileReader();
+                                       
+                                       reader.addEventListener('error', function () {
+                                               reader.abort();
+                                               reject(reader.error);
+                                       });
+                                       
+                                       reader.addEventListener('load', function () {
+                                               var buffer = reader.result;
+                                               var bytes = new Uint8Array(buffer);
+                                               var offset = 2;
+                                               
+                                               // check if the second tag is the JFIF tag
+                                               if (bytes[2] === 0xFF && bytes[3] === _tagNames.APP0) {
+                                                       offset += 2 + ((bytes[4] << 8) | bytes[5]);
+                                               }
+                                               
+                                               var start = Array.prototype.slice.call(bytes, 0, offset);
+                                               var end = Array.prototype.slice.call(bytes, offset);
+                                               
+                                               bytes = new Uint8Array(start.length + exif.length + end.length);
+                                               bytes.set(start);
+                                               bytes.set(exif, offset);
+                                               bytes.set(end, offset + exif.length);
+                                               
+                                               resolve(new Blob([bytes], {type: blob.type}));
+                                       });
+                                       
+                                       reader.readAsArrayBuffer(blob);
+                               });
+                       });
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Image/ImageUtil.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Image/ImageUtil.js
new file mode 100644 (file)
index 0000000..cd558c4
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Provides helper functions for Image metadata handling.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Image/ImageUtil
+ */
+define([], function() {
+       "use strict";
+       
+       return {
+               /**
+                * Returns whether the given canvas contains transparent pixels.
+                *
+                * @param       image   {Canvas}  Canvas to check
+                * @returns             {bool}
+                */
+               containsTransparentPixels: function (canvas) {
+                       var imageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
+                       
+                       for (var i = 3, max = imageData.data.length; i < max; i += 4) {
+                               if (imageData.data[i] !== 255) return true;
+                       }
+                       
+                       return false;
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Image/Resizer.js
new file mode 100644 (file)
index 0000000..f427f61
--- /dev/null
@@ -0,0 +1,206 @@
+/**
+ * This module allows resizing and conversion of HTMLImageElements to Blob and File objects
+ *
+ * @author     Maximilian Mader
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Image/Resizer
+ */
+define([
+       'WoltLabSuite/Core/FileUtil',
+       'WoltLabSuite/Core/Image/ExifUtil',
+       'Pica'
+], function(FileUtil, ExifUtil, Pica) {
+       "use strict";
+       
+       var pica = new Pica({features: ['js', 'wasm', 'ww']});
+       
+       /**
+        * @constructor
+        */
+       function ImageResizer() { }
+       ImageResizer.prototype = {
+               maxWidth: 800,
+               maxHeight: 600,
+               quality: 0.8,
+               fileType: 'image/jpeg',
+               
+               /**
+                * Sets the default maximum width for this instance
+                *
+                * @param       {Number}        value   the new default maximum width
+                * @returns     {ImageResizer}          this ImageResizer instance
+                */
+               setMaxWidth: function (value) {
+                       if (value == null) value = ImageResizer.prototype.maxWidth;
+                       
+                       this.maxWidth = value;
+                       return this;
+               },
+               
+               /**
+                * Sets the default maximum height for this instance
+                *
+                * @param       {Number}        value   the new default maximum height
+                * @returns     {ImageResizer}          this ImageResizer instance
+                */
+               setMaxHeight: function (value) {
+                       if (value == null) value = ImageResizer.prototype.maxHeight;
+                       
+                       this.maxHeight = value;
+                       return this;
+               },
+               
+               /**
+                * Sets the default quality for this instance
+                *
+                * @param       {Number}        value   the new default quality
+                * @returns     {ImageResizer}          this ImageResizer instance
+                */
+               setQuality: function (value) {
+                       if (value == null) value = ImageResizer.prototype.quality;
+                       
+                       this.quality = value;
+                       return this;
+               },
+               
+               /**
+                * Sets the default file type for this instance
+                *
+                * @param       {Number}        value   the new default file type
+                * @returns     {ImageResizer}          this ImageResizer instance
+                */
+               setFileType: function (value) {
+                       if (value == null) value = ImageResizer.prototype.fileType;
+                       
+                       this.fileType = value;
+                       return this;
+               },
+               
+               /**
+                * Converts the given object of exif data and image data into a File.
+                *
+                * @param       {Object{exif: Uint8Array|undefined, image: Canvas} data  object containing exif data and image data
+                * @param       {String}        fileName        the name of the returned file
+                * @param       {String}        [fileType]      the type of the returned image
+                * @param       {Number}        [quality]       quality setting, currently only effective for "image/jpeg"
+                * @returns     {Promise<File>} the File object
+                */
+               saveFile: function (data, fileName, fileType, quality) {
+                       fileType = fileType || this.fileType;
+                       quality = quality || this.quality;
+                       
+                       var basename = fileName.match(/(.+)(\..+?)$/);
+                       
+                       return pica.toBlob(data.image, fileType, quality)
+                               .then(function (blob) {
+                                       if (fileType === 'image/jpeg' && typeof data.exif !== 'undefined') {
+                                               return ExifUtil.setExifData(blob, data.exif);
+                                       }
+                                       
+                                       return blob;
+                               })
+                               .then(function (blob) {
+                                       return FileUtil.blobToFile(blob, basename[1] + '_autoscaled');
+                               });
+               },
+               
+               /**
+                * Loads the given file into an image object and parses Exif information.
+                * 
+                * @param   {File}    file the file to load
+                * @returns {Promise} resulting image data
+                */
+               loadFile: function (file) {
+                       var exif = undefined;
+                       if (file.type === 'image/jpeg') {
+                               // Extract EXIF data
+                               exif = ExifUtil.getExifBytesFromJpeg(file);
+                       }
+                       
+                       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);
+                               });
+                               
+                               reader.readAsDataURL(file);
+                       });
+                       
+                       return Promise.all([ exif, loader ])
+                               .then(function (result) {
+                                       return { exif: result[0], image: result[1] };
+                               });
+               },
+               
+               /**
+                * Downscales an image given as File object.
+                *
+                * @param       {Image}       image             the image to resize
+                * @param       {Number}      [maxWidth]        maximum width
+                * @param       {Number}      [maxHeight]       maximum height
+                * @param       {Number}      [quality]         quality in percent
+                * @param       {boolean}     [force]           whether to force scaling even if unneeded (thus re-encoding with a possibly smaller file size)
+                * @param       {Promise}     cancelPromise     a Promise used to cancel pica's operation when it resolves
+                * @returns     {Promise<Blob | undefined>}     a Promise resolving with the resized image as a {Canvas} or undefined if no resizing happened
+                */
+               resize: function (image, maxWidth, maxHeight, quality, force, cancelPromise) {
+                       maxWidth = maxWidth || this.maxWidth;
+                       maxHeight = maxHeight || this.maxHeight;
+                       quality = quality || this.quality;
+                       force = force || false;
+                       
+                       var canvas = document.createElement('canvas');
+                       
+                       // Prevent upscaling
+                       var newWidth = Math.min(maxWidth, image.width);
+                       var newHeight = Math.min(maxHeight, image.height);
+                       
+                       if (image.width <= newWidth && image.height <= newHeight && !force) {
+                               return Promise.resolve(undefined);
+                       }
+                       
+                       // Keep image ratio
+                       if (newWidth >= newHeight) {
+                               canvas.width = newWidth;
+                               canvas.height = newWidth * (image.height / image.width);
+                       }
+                       else {
+                               canvas.width = newHeight * (image.width / image.height);
+                               canvas.height = newHeight;
+                       }
+                       
+                       // Map to Pica's quality
+                       var resizeQuality = 1;
+                       if (quality >= 0.8) {
+                               resizeQuality = 3;
+                       }
+                       else if (quality >= 0.4) {
+                               resizeQuality = 2;
+                       }
+                       
+                       var options = {
+                               quality: resizeQuality,
+                               cancelToken: cancelPromise,
+                               alpha: true
+                       };
+                       
+                       return pica.resize(image, canvas, options);
+               }
+       };
+       
+       return ImageResizer;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/ImageResizer.js b/wcfsetup/install/files/js/WoltLabSuite/Core/ImageResizer.js
deleted file mode 100644 (file)
index 3765407..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * This module allows resizing and conversion of HTMLImageElements to Blob and File objects
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/ImageResizer
- */
-define([
-       'WoltLabSuite/Core/FileUtil',
-       'WoltLabSuite/Core/ExifUtil',
-       'Pica'
-], function(FileUtil, ExifUtil, Pica) {
-       "use strict";
-       
-       var pica = new Pica({features: ['js', 'wasm', 'ww']});
-       
-       /**
-        * @constructor
-        */
-       function ImageResizer() { }
-       ImageResizer.prototype = {
-               maxWidth: 800,
-               maxHeight: 600,
-               quality: 0.8,
-               fileType: 'image/jpeg',
-               
-               /**
-                * Sets the default maximum width for this instance
-                *
-                * @param       {Number}        value   the new default maximum width
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxWidth: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxWidth;
-                       
-                       this.maxWidth = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default maximum height for this instance
-                *
-                * @param       {Number}        value   the new default maximum height
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxHeight: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxHeight;
-                       
-                       this.maxHeight = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default quality for this instance
-                *
-                * @param       {Number}        value   the new default quality
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setQuality: function (value) {
-                       if (value == null) value = ImageResizer.prototype.quality;
-                       
-                       this.quality = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default file type for this instance
-                *
-                * @param       {Number}        value   the new default file type
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setFileType: function (value) {
-                       if (value == null) value = ImageResizer.prototype.fileType;
-                       
-                       this.fileType = value;
-                       return this;
-               },
-               
-               /**
-                * Converts the given object of exif data and image data into a File.
-                *
-                * @param       {Object{exif: Uint8Array|undefined, image: Canvas} data  object containing exif data and image data
-                * @param       {String}        fileName        the name of the returned file
-                * @param       {String}        [fileType]      the type of the returned image
-                * @param       {Number}        [quality]       quality setting, currently only effective for "image/jpeg"
-                * @returns     {Promise<File>} the File object
-                */
-               saveFile: function (data, fileName, fileType, quality) {
-                       fileType = fileType || this.fileType;
-                       quality = quality || this.quality;
-                       
-                       var basename = fileName.match(/(.+)(\..+?)$/);
-                       
-                       return pica.toBlob(data.image, fileType, quality)
-                               .then(function (blob) {
-                                       if (fileType === 'image/jpeg' && typeof data.exif !== 'undefined') {
-                                               return ExifUtil.setExifData(blob, data.exif);
-                                       }
-                                       
-                                       return blob;
-                               })
-                               .then(function (blob) {
-                                       return FileUtil.blobToFile(blob, basename[1] + '_autoscaled');
-                               });
-               },
-               
-               /**
-                * Loads the given file into an image object and parses Exif information.
-                * 
-                * @param   {File}    file the file to load
-                * @returns {Promise} resulting image data
-                */
-               loadFile: function (file) {
-                       var exif = undefined;
-                       if (file.type === 'image/jpeg') {
-                               // Extract EXIF data
-                               exif = ExifUtil.getExifBytesFromJpeg(file);
-                       }
-                       
-                       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);
-                               });
-                               
-                               reader.readAsDataURL(file);
-                       });
-                       
-                       return Promise.all([ exif, loader ])
-                               .then(function (result) {
-                                       return { exif: result[0], image: result[1] };
-                               });
-               },
-               
-               /**
-                * Downscales an image given as File object.
-                *
-                * @param       {Image}       image             the image to resize
-                * @param       {Number}      [maxWidth]        maximum width
-                * @param       {Number}      [maxHeight]       maximum height
-                * @param       {Number}      [quality]         quality in percent
-                * @param       {boolean}     [force]           whether to force scaling even if unneeded (thus re-encoding with a possibly smaller file size)
-                * @param       {Promise}     cancelPromise     a Promise used to cancel pica's operation when it resolves
-                * @returns     {Promise<Blob | undefined>}     a Promise resolving with the resized image as a {Canvas} or undefined if no resizing happened
-                */
-               resize: function (image, maxWidth, maxHeight, quality, force, cancelPromise) {
-                       maxWidth = maxWidth || this.maxWidth;
-                       maxHeight = maxHeight || this.maxHeight;
-                       quality = quality || this.quality;
-                       force = force || false;
-                       
-                       var canvas = document.createElement('canvas');
-                       
-                       // Prevent upscaling
-                       var newWidth = Math.min(maxWidth, image.width);
-                       var newHeight = Math.min(maxHeight, image.height);
-                       
-                       if (image.width <= newWidth && image.height <= newHeight && !force) {
-                               return Promise.resolve(undefined);
-                       }
-                       
-                       // Keep image ratio
-                       if (newWidth >= newHeight) {
-                               canvas.width = newWidth;
-                               canvas.height = newWidth * (image.height / image.width);
-                       }
-                       else {
-                               canvas.width = newHeight * (image.width / image.height);
-                               canvas.height = newHeight;
-                       }
-                       
-                       // Map to Pica's quality
-                       var resizeQuality = 1;
-                       if (quality >= 0.8) {
-                               resizeQuality = 3;
-                       }
-                       else if (quality >= 0.4) {
-                               resizeQuality = 2;
-                       }
-                       
-                       var options = {
-                               quality: resizeQuality,
-                               cancelToken: cancelPromise
-                       };
-                       
-                       return pica.resize(image, canvas, options);
-               }
-       };
-       
-       return ImageResizer;
-});
index a1421fcdc0592eb594aae704f838b9d8874154d9..30d47dd5e7aedeeac599e35c35c0729f6aed0de5 100644 (file)
@@ -1392,7 +1392,7 @@ Das Fehlerprotokoll enthält {$data[count]} neue Einträge. Die ersten drei, in
                <item name="wcf.acp.option.attachment_image_autoscale_quality"><![CDATA[Qualitätsstufe]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_quality.description"><![CDATA[Bestimmt die Qualität des skalierten Bildes.]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_file_type"><![CDATA[Dateityp]]></item>
-               <item name="wcf.acp.option.attachment_image_autoscale_file_type.jpeg"><![CDATA[JPEG (verlustbehaftet, keine Transparenz, erzeugt in der Regel kleine Dateien)]]></item>
+               <item name="wcf.acp.option.attachment_image_autoscale_file_type.jpeg"><![CDATA[JPEG (verlustbehaftet, erzeugt in der Regel kleine Dateien)]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_file_type.keep"><![CDATA[Dateityp beibehalten]]></item>
                <item name="wcf.acp.option.module_attachment"><![CDATA[Dateianhänge]]></item>
                <item name="wcf.acp.option.module_smiley"><![CDATA[Smileys]]></item>
index 0ba9e5857ad757916651ab868981b3702057be9d..3f3eb676e6e0371bfc31d12bd1a1cdff23959f1a 100644 (file)
@@ -1369,7 +1369,7 @@ This protocol file contains {$data[count]} new entries. The first three error me
                <item name="wcf.acp.option.attachment_image_autoscale_quality"><![CDATA[Quality]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_quality.description"><![CDATA[Defines the quality of the scaled image.]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_file_type"><![CDATA[File type]]></item>
-               <item name="wcf.acp.option.attachment_image_autoscale_file_type.jpeg"><![CDATA[JPEG (lossy, does not support transparency, usually the generated files are small)]]></item>
+               <item name="wcf.acp.option.attachment_image_autoscale_file_type.jpeg"><![CDATA[JPEG (lossy, usually the generated files are small)]]></item>
                <item name="wcf.acp.option.attachment_image_autoscale_file_type.keep"><![CDATA[Keep the file type]]></item>
                <item name="wcf.acp.option.module_attachment"><![CDATA[Attachments]]></item>
                <item name="wcf.acp.option.module_smiley"><![CDATA[Smilies]]></item>