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 = [];
var fileType = this._options.autoScale.fileType;
- if (this._options.autoScale.fileType === 'keep') {
+ if (this._options.autoScale.fileType === 'keep' || ImageUtil.containsTransparentPixels(resizedImage)) {
fileType = file.type;
}
+++ /dev/null
-/**
- * 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);
- });
- });
- }
- };
-});
--- /dev/null
+/**
+ * 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);
+ });
+ });
+ }
+ };
+});
--- /dev/null
+/**
+ * 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;
+ }
+ };
+});
--- /dev/null
+/**
+ * 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;
+});
+++ /dev/null
-/**
- * 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;
-});
<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>
<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>