From 54a03d0996d96a898567edc32d7b83045df0fb26 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Wed, 25 May 2016 14:28:11 -0500 Subject: [PATCH 01/27] feat(scaling): Start of feature to allow alternate scaling code #1525 --- client/js/uploader.basic.js | 3 ++- client/js/version.js | 2 +- docs/api/events.jmd | 26 ++++++++++++++++++++++ docs/features/async-tasks-and-promises.jmd | 13 ++++++----- package.json | 2 +- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/client/js/uploader.basic.js b/client/js/uploader.basic.js index 8d95425f5..c8ec57dfe 100644 --- a/client/js/uploader.basic.js +++ b/client/js/uploader.basic.js @@ -64,7 +64,8 @@ onDeleteComplete: function(id, xhrOrXdr, isError) {}, onPasteReceived: function(blob) {}, onStatusChange: function(id, oldStatus, newStatus) {}, - onSessionRequestComplete: function(response, success, xhrOrXdr) {} + onSessionRequestComplete: function(response, success, xhrOrXdr) {}, + onResizeImage: function(resizeInfo) {} }, messages: { diff --git a/client/js/version.js b/client/js/version.js index 87c08bb74..99d96c9a1 100644 --- a/client/js/version.js +++ b/client/js/version.js @@ -1,2 +1,2 @@ /*global qq */ -qq.version = "5.10.0"; +qq.version = "5.10.0-1"; diff --git a/docs/api/events.jmd b/docs/api/events.jmd index 6f94d3bbb..1e4b07dfa 100644 --- a/docs/api/events.jmd +++ b/docs/api/events.jmd @@ -263,6 +263,32 @@ Useful for implementing a progress bar. ]) }} +{{ api_event("resizeImage", "onUploadChunk", +""" Called just before an image is resized. This is a promissory handler. In fact, if a promise is _not_ returned, it is +assumed that the internal resize algorithm should be used instead. If you don't want to provide your own code to handle +image resizing, simply omit a handler for this event. + +If you _do_ want to use an alternate scaling library, you must contribute a handler for this event and return a `Promise`. +Once the resize is complete, your promise must be fulfilled, passing an argument of an `HTMLCanvasElement` with the resized +image drawn onto it. You may, of course, reject your returned `Promise` is the resize fails in some way. + +The `resizeInfo` object, which is passed into the event handler, has the following properties: + +* `id` - ID of the associated `Blob`/`File`. +* `canvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `height` - Desired height of the image after the resize operation. +* `width` - Desired width of the image after the resize operation. + +""", + [ + { + "name": "resizeInfo", + "type": "Object", + "description": "Information needed to delegate resizing of an image `Blob` to a custom/third-party scaling library or function." + }, + ]) +}} + {{ api_event("resume", "onResume", """ Called just before an upload is resumed. See the [`uploadChunk`](#uploadChunk) event for more info on the `chunkData` object. diff --git a/docs/features/async-tasks-and-promises.jmd b/docs/features/async-tasks-and-promises.jmd index 33e89ad74..1bfc4c390 100644 --- a/docs/features/async-tasks-and-promises.jmd +++ b/docs/features/async-tasks-and-promises.jmd @@ -26,17 +26,18 @@ For more information on promises in JavaScript, have a look at ## Promissory Callbacks Promises are acceptable return values in the following [event handlers](../api/events.html). -All of these callbacks can also prevent an associated action from being executed +Many of these callbacks can also prevent an associated action from being executed with a false return value (non-promise) or a call to `failure()` on a returned -promise instance: +promise instance (see individual event docs for more details): -* `onSubmit` * `onCancel` * `onCredentialsExpired` -* `onValidateBatch` -* `onValidate` -* `onSubmitDelete` * `onPasteReceived` +* `onResizeImage` +* `onSubmit` +* `onSubmitDelete` +* `onValidate` +* `onValidateBatch` You are not required to return a promise -- you can simply return `false` (or nothing). However, there are some instances where you may want to perform diff --git a/package.json b/package.json index 34757a8b5..f9472b1ec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "fine-uploader", "title": "Fine Uploader", "main": "lib/traditional.js", - "version": "5.10.0", + "version": "5.10.0-1", "description": "Multiple file upload plugin with progress-bar, drag-and-drop, direct-to-S3 & Azure uploading, client-side image scaling, preview generation, form support, chunking, auto-resume, and tons of other features.", "keywords": [ "amazon", From d86a600e1f12f0c76f802356237692cf808bc001 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:41:04 -0500 Subject: [PATCH 02/27] feat(scaling): Use of custom resize function now available Enabled for all operations that result in a resized image, such as drawing a preview thumbnail and generating a scaled image blob for upload. Still need to investigate orientation consequences. #1525 --- client/js/image-support/image.js | 13 ++-- client/js/image-support/megapix-image.js | 87 +++++++++++++++++++++--- client/js/image-support/scaler.js | 6 +- client/js/templating.js | 18 ++--- client/js/uploader.api.js | 6 +- client/js/uploader.basic.api.js | 7 +- client/js/uploader.basic.js | 5 +- client/js/uploader.js | 1 + docs/api/events.jmd | 26 ------- docs/api/methods.jmd | 26 ++++++- docs/api/options-ui.jmd | 11 +++ docs/api/options.jmd | 11 +++ package.json | 1 + 13 files changed, 158 insertions(+), 60 deletions(-) diff --git a/client/js/image-support/image.js b/client/js/image-support/image.js index 62e68227e..0c5ea2215 100644 --- a/client/js/image-support/image.js +++ b/client/js/image-support/image.js @@ -167,7 +167,8 @@ qq.ImageGenerator = function(log) { maxWidth: maxSize, maxHeight: maxSize, orientation: orientation, - mime: mime + mime: mime, + resize: options.customResizeFunction }); }, @@ -177,7 +178,8 @@ qq.ImageGenerator = function(log) { mpImg.render(container, { maxWidth: maxSize, maxHeight: maxSize, - mime: mime + mime: mime, + resize: options.customResizeFunction }); } ); @@ -193,7 +195,7 @@ qq.ImageGenerator = function(log) { return drawPreview; } - function drawOnCanvasOrImgFromUrl(url, canvasOrImg, draw, maxSize) { + function drawOnCanvasOrImgFromUrl(url, canvasOrImg, draw, maxSize, customResizeFunction) { var tempImg = new Image(), tempImgRender = new qq.Promise(); @@ -213,7 +215,8 @@ qq.ImageGenerator = function(log) { mpImg.render(canvasOrImg, { maxWidth: maxSize, maxHeight: maxSize, - mime: determineMimeOfFileName(url) + mime: determineMimeOfFileName(url), + resize: customResizeFunction }); }, @@ -287,7 +290,7 @@ qq.ImageGenerator = function(log) { * * @param fileBlobOrUrl a `File`, `Blob`, or a URL pointing to the image * @param container or to contain the preview - * @param options possible properties include `maxSize` (int), `orient` (bool - default true), and `resize` (bool - default true) + * @param options possible properties include `maxSize` (int), `orient` (bool - default true), resize` (bool - default true), and `customResizeFunction`. * @returns qq.Promise fulfilled when the preview has been drawn, or the attempt has failed */ generate: function(fileBlobOrUrl, container, options) { diff --git a/client/js/image-support/megapix-image.js b/client/js/image-support/megapix-image.js index 49a132ee1..d4a8ac033 100644 --- a/client/js/image-support/megapix-image.js +++ b/client/js/image-support/megapix-image.js @@ -72,12 +72,19 @@ /** * Rendering image element (with resizing) and get its data URL */ - function renderImageToDataURL(img, options, doSquash) { + function renderImageToDataURL(img, blob, options, doSquash) { var canvas = document.createElement("canvas"), - mime = options.mime || "image/jpeg"; + mime = options.mime || "image/jpeg", + promise = new qq.Promise(); - renderImageToCanvas(img, canvas, options, doSquash); - return canvas.toDataURL(mime, options.quality || 0.8); + renderImageToCanvas(img, blob, canvas, options, doSquash) + .then(function() { + promise.success( + canvas.toDataURL(mime, options.quality || 0.8) + ); + }) + + return promise; } function maybeCalculateDownsampledDimensions(spec) { @@ -98,14 +105,28 @@ /** * Rendering image element (with resizing) into the canvas element */ - function renderImageToCanvas(img, canvas, options, doSquash) { + function renderImageToCanvas(img, blob, canvas, options, doSquash) { var iw = img.naturalWidth, ih = img.naturalHeight, width = options.width, height = options.height, ctx = canvas.getContext("2d"), + promise = new qq.Promise(), modifiedDimensions; + if (options.resize) { + return renderImageToCanvasWithCustomResizer({ + blob: blob, + canvas: canvas, + image: img, + imageHeight: ih, + imageWidth: iw, + resize: options.resize, + targetHeight: height, + targetWidth: width + }) + } + ctx.save(); if (!qq.supportedFeatures.unlimitedScaledImageSize) { @@ -117,7 +138,7 @@ if (modifiedDimensions) { qq.log(qq.format("Had to reduce dimensions due to device limitations from {}w / {}h to {}w / {}h", width, height, modifiedDimensions.newWidth, modifiedDimensions.newHeight), - "warn"); + "warn"); width = modifiedDimensions.newWidth; height = modifiedDimensions.newHeight; @@ -149,7 +170,7 @@ while (sy < ih) { sx = 0, - dx = 0; + dx = 0; while (sx < iw) { tmpCtx.clearRect(0, 0, d, d); tmpCtx.drawImage(img, -sx, -sy); @@ -169,6 +190,49 @@ } canvas.qqImageRendered && canvas.qqImageRendered(); + promise.success(); + + return promise; + } + + function renderImageToCanvasWithCustomResizer(resizeInfo) { + var blob = resizeInfo.blob, + image = resizeInfo.image, + imageHeight = resizeInfo.imageHeight, + imageWidth = resizeInfo.imageWidth, + promise = new qq.Promise(), + resize = resizeInfo.resize, + sourceCanvas = document.createElement("canvas"), + sourceCanvasContext = sourceCanvas.getContext("2d"), + targetCanvas = resizeInfo.canvas, + targetHeight = resizeInfo.targetHeight, + targetWidth = resizeInfo.targetWidth; + + sourceCanvas.height = imageHeight; + sourceCanvas.width = imageWidth; + + targetCanvas.height = targetHeight; + targetCanvas.width = targetWidth; + + sourceCanvasContext.drawImage(image, 0, 0); + + resize({ + blob: blob, + height: targetHeight, + image: image, + sourceCanvas: sourceCanvas, + targetCanvas: targetCanvas, + width: targetWidth + }) + .then( + function success() { + targetCanvas.qqImageRendered && targetCanvas.qqImageRendered(); + promise.success(); + }, + promise.failure + ) + + return promise; } /** @@ -315,11 +379,14 @@ if (tagName === "img") { (function() { var oldTargetSrc = target.src; - target.src = renderImageToDataURL(self.srcImage, opt, doSquash); - oldTargetSrc === target.src && target.onload(); + renderImageToDataURL(self.srcImage, self.blob, opt, doSquash) + .then(function(dataUri) { + target.src = dataUri; + oldTargetSrc === target.src && target.onload(); + }); }()) } else if (tagName === "canvas") { - renderImageToCanvas(this.srcImage, target, opt, doSquash); + renderImageToCanvas(this.srcImage, this.blob, target, opt, doSquash); } if (typeof this.onrender === "function") { this.onrender(target); diff --git a/client/js/image-support/scaler.js b/client/js/image-support/scaler.js index aad71bdbf..26b94e03f 100644 --- a/client/js/image-support/scaler.js +++ b/client/js/image-support/scaler.js @@ -12,6 +12,7 @@ qq.Scaler = function(spec, log) { "use strict"; var self = this, + customResizeFunction = spec.customResizer, includeOriginal = spec.sendOriginal, orient = spec.orient, defaultType = spec.defaultType, @@ -51,6 +52,7 @@ qq.Scaler = function(spec, log) { }), blob: new qq.BlobProxy(originalBlob, qq.bind(self._generateScaledImage, self, { + customResizeFunction: customResizeFunction, maxSize: sizeRecord.maxSize, orient: orient, type: outputType, @@ -170,6 +172,7 @@ qq.extend(qq.Scaler.prototype, { name = uploadData && uploadData.name, uuid = uploadData && uploadData.uuid, scalingOptions = { + customResizer: specs.customResizer, sendOriginal: false, orient: specs.orient, defaultType: specs.type || null, @@ -290,6 +293,7 @@ qq.extend(qq.Scaler.prototype, { "use strict"; var self = this, + customResizeFunction = spec.customResizeFunction, log = spec.log, maxSize = spec.maxSize, orient = spec.orient, @@ -303,7 +307,7 @@ qq.extend(qq.Scaler.prototype, { log("Attempting to generate scaled version for " + sourceFile.name); - imageGenerator.generate(sourceFile, canvas, {maxSize: maxSize, orient: orient}).then(function() { + imageGenerator.generate(sourceFile, canvas, {maxSize: maxSize, orient: orient, customResizeFunction: customResizeFunction}).then(function() { var scaledImageDataUri = canvas.toDataURL(type, quality), signalSuccess = function() { log("Success generating scaled version for " + sourceFile.name); diff --git a/client/js/templating.js b/client/js/templating.js index aa6134193..2ca7686f7 100644 --- a/client/js/templating.js +++ b/client/js/templating.js @@ -486,9 +486,10 @@ qq.Templating = function(spec) { relatedThumbnailId = optFileOrBlob && optFileOrBlob.qqThumbnailId, thumbnail = getThumbnail(id), spec = { + customResizeFunction: queuedThumbRequest.customResizeFunction, maxSize: thumbnailMaxSize, - scale: true, - orient: true + orient: true, + scale: true }; if (qq.supportedFeatures.imagePreviews) { @@ -534,8 +535,9 @@ qq.Templating = function(spec) { showWaitingImg = queuedThumbRequest.showWaitingImg, thumbnail = getThumbnail(id), spec = { - maxSize: thumbnailMaxSize, - scale: serverScale + customResizeFunction: queuedThumbRequest.customResizeFunction, + scale: serverScale, + maxSize: thumbnailMaxSize }; if (thumbnail) { @@ -982,16 +984,16 @@ qq.Templating = function(spec) { show(getSpinner(id)); }, - generatePreview: function(id, optFileOrBlob) { + generatePreview: function(id, optFileOrBlob, customResizeFunction) { if (!this.isHiddenForever(id)) { - thumbGenerationQueue.push({id: id, optFileOrBlob: optFileOrBlob}); + thumbGenerationQueue.push({id: id, customResizeFunction: customResizeFunction, optFileOrBlob: optFileOrBlob}); !thumbnailQueueMonitorRunning && generateNextQueuedPreview(); } }, - updateThumbnail: function(id, thumbnailUrl, showWaitingImg) { + updateThumbnail: function(id, thumbnailUrl, showWaitingImg, customResizeFunction) { if (!this.isHiddenForever(id)) { - thumbGenerationQueue.push({update: true, id: id, thumbnailUrl: thumbnailUrl, showWaitingImg: showWaitingImg}); + thumbGenerationQueue.push({customResizeFunction: customResizeFunction, update: true, id: id, thumbnailUrl: thumbnailUrl, showWaitingImg: showWaitingImg}); !thumbnailQueueMonitorRunning && generateNextQueuedPreview(); } }, diff --git a/client/js/uploader.api.js b/client/js/uploader.api.js index 795748343..2603c4354 100644 --- a/client/js/uploader.api.js +++ b/client/js/uploader.api.js @@ -574,11 +574,11 @@ if (canned) { this._templating.addFileToCache(id, this._options.formatFileName(name), prependData, dontDisplay); - this._templating.updateThumbnail(id, this._thumbnailUrls[id], true); + this._templating.updateThumbnail(id, this._thumbnailUrls[id], true, this._options.thumbnails.customResizer); } else { this._templating.addFile(id, this._options.formatFileName(name), prependData, dontDisplay); - this._templating.generatePreview(id, this.getFile(id)); + this._templating.generatePreview(id, this.getFile(id), this._options.thumbnails.customResizer); } this._filesInBatchAddedToUi += 1; @@ -696,7 +696,7 @@ // This will replace the "waiting" placeholder with a "preview not available" placeholder // if called with a null thumbnailUrl. - this._templating.updateThumbnail(fileId, thumbnailUrl); + this._templating.updateThumbnail(fileId, thumbnailUrl, this._options.thumbnails.customResizer); } }, diff --git a/client/js/uploader.basic.api.js b/client/js/uploader.basic.api.js index bf1b7a92c..126aa65ea 100644 --- a/client/js/uploader.basic.api.js +++ b/client/js/uploader.basic.api.js @@ -163,15 +163,16 @@ // returning a promise that is fulfilled when the attempt completes. // Thumbnail can either be based off of a URL for an image returned // by the server in the upload response, or the associated `Blob`. - drawThumbnail: function(fileId, imgOrCanvas, maxSize, fromServer) { + drawThumbnail: function(fileId, imgOrCanvas, maxSize, fromServer, customResizeFunction) { var promiseToReturn = new qq.Promise(), fileOrUrl, options; if (this._imageGenerator) { fileOrUrl = this._thumbnailUrls[fileId]; options = { - scale: maxSize > 0, - maxSize: maxSize > 0 ? maxSize : null + customResizeFunction: customResizeFunction, + maxSize: maxSize > 0 ? maxSize : null, + scale: maxSize > 0 }; // If client-side preview generation is possible diff --git a/client/js/uploader.basic.js b/client/js/uploader.basic.js index c8ec57dfe..dfeae24f2 100644 --- a/client/js/uploader.basic.js +++ b/client/js/uploader.basic.js @@ -64,8 +64,7 @@ onDeleteComplete: function(id, xhrOrXdr, isError) {}, onPasteReceived: function(blob) {}, onStatusChange: function(id, oldStatus, newStatus) {}, - onSessionRequestComplete: function(response, success, xhrOrXdr) {}, - onResizeImage: function(resizeInfo) {} + onSessionRequestComplete: function(response, success, xhrOrXdr) {} }, messages: { @@ -195,6 +194,8 @@ // scale images client side, upload a new file for each scaled version scaling: { + customResizer: null, + // send the original file as well sendOriginal: true, diff --git a/client/js/uploader.js b/client/js/uploader.js index a22bd4dd4..79f72f668 100644 --- a/client/js/uploader.js +++ b/client/js/uploader.js @@ -80,6 +80,7 @@ qq.FineUploader = function(o, namespace) { }, thumbnails: { + customResizer: null, maxCount: 0, placeholders: { waitUntilResponse: false, diff --git a/docs/api/events.jmd b/docs/api/events.jmd index 1e4b07dfa..6f94d3bbb 100644 --- a/docs/api/events.jmd +++ b/docs/api/events.jmd @@ -263,32 +263,6 @@ Useful for implementing a progress bar. ]) }} -{{ api_event("resizeImage", "onUploadChunk", -""" Called just before an image is resized. This is a promissory handler. In fact, if a promise is _not_ returned, it is -assumed that the internal resize algorithm should be used instead. If you don't want to provide your own code to handle -image resizing, simply omit a handler for this event. - -If you _do_ want to use an alternate scaling library, you must contribute a handler for this event and return a `Promise`. -Once the resize is complete, your promise must be fulfilled, passing an argument of an `HTMLCanvasElement` with the resized -image drawn onto it. You may, of course, reject your returned `Promise` is the resize fails in some way. - -The `resizeInfo` object, which is passed into the event handler, has the following properties: - -* `id` - ID of the associated `Blob`/`File`. -* `canvas` - `HTMLCanvasElement` element containing the original image data (not resized). -* `height` - Desired height of the image after the resize operation. -* `width` - Desired width of the image after the resize operation. - -""", - [ - { - "name": "resizeInfo", - "type": "Object", - "description": "Information needed to delegate resizing of an image `Blob` to a custom/third-party scaling library or function." - }, - ]) -}} - {{ api_event("resume", "onResume", """ Called just before an upload is resumed. See the [`uploadChunk`](#uploadChunk) event for more info on the `chunkData` object. diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index fa9b60a78..996069ce3 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -113,7 +113,7 @@ A `CanvasWrapper` object: } ]) }} -{{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer]])", +{{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])", "Draws a thumbnail.", [ { @@ -136,6 +136,17 @@ A `CanvasWrapper` object: "type": "Boolean", "description": "`true` if the image data will come as a response from the server rather than be generated client-side." } + { + "name": "customResizer", + "type": "function", + "description": """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + * `blob` - The original `File` or `Blob` object, if available. + * `height` - Desired height of the image after the resize operation. + * `image` - The original `HTMLImageElement` object, if available. + * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). + * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + * `width` - Desired width of the image after the resize operation.""" + } ], [ { @@ -349,7 +360,7 @@ A `CanvasWrapper` object: } ], null) }} -{{ api_method("scaleImage", "scaleImage (id, options)", "Generates a scaled version of a submitted image file.", +{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file.", [ { "name": "id", @@ -360,6 +371,17 @@ A `CanvasWrapper` object: "name": "options", "type": "Object", "description": "Information about the scaled image to generate. The `maxSize` property is required (integer). Optional properties are: `orient` (boolean, defaults to true), `type` (string, defaults to the type of the reference image), and `quality` (number between 0 and 100, defaults to 80), and `includeExif` (boolean, defaults to `false`)." + }, + { + "name": "customResizer", + "type": "function", + "description": """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + * `blob` - The original `File` or `Blob` object, if available. + * `height` - Desired height of the image after the resize operation. + * `image` - The original `HTMLImageElement` object, if available. + * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). + * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + * `width` - Desired width of the image after the resize operation.""" } ], [ diff --git a/docs/api/options-ui.jmd b/docs/api/options-ui.jmd index 3f92666ba..e63801609 100644 --- a/docs/api/options-ui.jmd +++ b/docs/api/options-ui.jmd @@ -102,6 +102,17 @@ options for `messages`""", "info", "Note:") }} {{ api_parent_option("thumbnails", "thumbnails", "", ( + ("thumbnails.customResizer", "customResizer", """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + + * `blob` - The original `File` or `Blob` object, if available. + * `height` - Desired height of the image after the resize operation. + * `image` - The original `HTMLImageElement` object, if available. + * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). + * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + * `width` - Desired width of the image after the resize operation. + + """, + "Function", "undefined"), ("thumbnails.maxCount", "maxCount", "Maximum number of previews to render per Fine Uploader instance. A call to the reset method resets this value as well.", "Integer", "0",), ("thumbnails.timeBetweenThumbs", "timeBetweenThumbs", "The amount of time, in milliseconds, to pause between each preview generation process. This is in place to prevent the UI thread from locking up for a continuously long period of time, as preview generation can be a resource-intensive process.", "Integer", "750",), ) diff --git a/docs/api/options.jmd b/docs/api/options.jmd index ecee7ce5b..0e290c50d 100644 --- a/docs/api/options.jmd +++ b/docs/api/options.jmd @@ -187,6 +187,17 @@ alert("The `chunking.success.endpoint` option **only** applies to traditional up {{ api_parent_option("scaling", "scaling", "See the [Upload Scaled Images feature page](../features/scaling.html) for more details.", ( + ("scaling.customResizer", "customResizer", """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + + * `blob` - The original `File` or `Blob` object, if available. + * `height` - Desired height of the image after the resize operation. + * `image` - The original `HTMLImageElement` object, if available. + * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). + * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + * `width` - Desired width of the image after the resize operation. + + """, + "Function", "undefined"), ("scaling.defaultQuality", "defaultQuality", "A value between 1 and 100 that describes the requested quality of scaled images. Ignored unless the scaled image type target is image/jpeg.", "Integer", "80",), ("scaling.defaultType", "defaultType", "Scaled images will assume this image type if you don't specify a specific type in your size object, or if the type specified in the size object is not valid. You generally should not use any value other than image/jpeg or image/png here. The default value of `null` will ensure the scaled image type is PNG, unless the original file is a JPEG, in which case the scaled file will also be a JPEG. The default is probably the safest option.", "String", "null",), ("scaling.failureText", "failureText", "Text sent to your `complete` event handler as an `error` property of the `response` param if a scaled image could not be generated.", "String", "Failed to scale",), diff --git a/package.json b/package.json index f9472b1ec..6894e992b 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "karma-spec-reporter": "0.0.13", "npm": "^2.1.17", "optimist": "0.6.0", + "pica": "latest", "request": "2.21.0", "semver": "2.0.x", "string.prototype.endswith": "0.2.0", From 02f3675125c09c2987e4520a3c6b2967ab96cf79 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:42:23 -0500 Subject: [PATCH 03/27] fix(methods.jmd): Syntax error in docs #1525 --- docs/api/methods.jmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 996069ce3..535cd7f08 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -135,7 +135,7 @@ A `CanvasWrapper` object: "name": "fromServer", "type": "Boolean", "description": "`true` if the image data will come as a response from the server rather than be generated client-side." - } + }, { "name": "customResizer", "type": "function", From c3f6ee759059a50f3ceeea1126c939ded3e3db2b Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:51:53 -0500 Subject: [PATCH 04/27] fix(methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 535cd7f08..387cdaa29 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -139,13 +139,16 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - * `blob` - The original `File` or `Blob` object, if available. - * `height` - Desired height of the image after the resize operation. - * `image` - The original `HTMLImageElement` object, if available. - * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). - * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - * `width` - Desired width of the image after the resize operation.""" + "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + + | Property | Description | + | -------- | :---------- | + | blob | The original `File` or `Blob` object, if available. + | height | Desired height of the image after the resize operation. + | image | The original `HTMLImageElement` object, if available. + | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). + | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + | width | Desired width of the image after the resize operation. {: .table }" } ], [ @@ -375,13 +378,16 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - * `blob` - The original `File` or `Blob` object, if available. - * `height` - Desired height of the image after the resize operation. - * `image` - The original `HTMLImageElement` object, if available. - * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). - * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - * `width` - Desired width of the image after the resize operation.""" + "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + + | Property | Description | + | -------- | :---------- | + | blob | The original `File` or `Blob` object, if available. + | height | Desired height of the image after the resize operation. + | image | The original `HTMLImageElement` object, if available. + | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). + | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + | width | Desired width of the image after the resize operation. {: .table }" } ], [ From 187eb1344075cef821d1df41de788bbb320e29f2 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:55:46 -0500 Subject: [PATCH 05/27] fix(methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 50 +++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 387cdaa29..dc0f50952 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -114,7 +114,19 @@ A `CanvasWrapper` object: ]) }} {{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])", -"Draws a thumbnail.", +"Draws a thumbnail. + + A `resizeInfo` object: + + | Property | Description | + | -------- | :---------- | + | blob | The original `File` or `Blob` object, if available. + | height | Desired height of the image after the resize operation. + | image | The original `HTMLImageElement` object, if available. + | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). + | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + | width | Desired width of the image after the resize operation. | {: .table }" +", [ { "name": "id", @@ -139,16 +151,7 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - - | Property | Description | - | -------- | :---------- | - | blob | The original `File` or `Blob` object, if available. - | height | Desired height of the image after the resize operation. - | image | The original `HTMLImageElement` object, if available. - | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). - | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. {: .table }" + "description": " A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way." } ], [ @@ -363,7 +366,19 @@ A `CanvasWrapper` object: } ], null) }} -{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file.", +{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file. + + A `resizeInfo` object: + + | Property | Description | + | -------- | :---------- | + | blob | The original `File` or `Blob` object, if available. + | height | Desired height of the image after the resize operation. + | image | The original `HTMLImageElement` object, if available. + | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). + | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + | width | Desired width of the image after the resize operation. | {: .table }" +", [ { "name": "id", @@ -378,16 +393,7 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - - | Property | Description | - | -------- | :---------- | - | blob | The original `File` or `Blob` object, if available. - | height | Desired height of the image after the resize operation. - | image | The original `HTMLImageElement` object, if available. - | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). - | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. {: .table }" + "description": "A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way." } ], [ From e261c5bb70ab8ecbc3e259c45d6746b4b07556e9 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:56:50 -0500 Subject: [PATCH 06/27] fix(methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index dc0f50952..b6202cdac 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -125,7 +125,7 @@ A `CanvasWrapper` object: | image | The original `HTMLImageElement` object, if available. | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. | {: .table }" + | width | Desired width of the image after the resize operation. | {: .table } ", [ { @@ -377,7 +377,7 @@ A `CanvasWrapper` object: | image | The original `HTMLImageElement` object, if available. | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. | {: .table }" + | width | Desired width of the image after the resize operation. | {: .table } ", [ { From fcf5925f09ec7ffcfec9713613e92bcc0029c8c9 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 16:59:36 -0500 Subject: [PATCH 07/27] fix(methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index b6202cdac..1d719b880 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -114,19 +114,7 @@ A `CanvasWrapper` object: ]) }} {{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])", -"Draws a thumbnail. - - A `resizeInfo` object: - - | Property | Description | - | -------- | :---------- | - | blob | The original `File` or `Blob` object, if available. - | height | Desired height of the image after the resize operation. - | image | The original `HTMLImageElement` object, if available. - | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). - | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. | {: .table } -", +"Draws a thumbnail." [ { "name": "id", @@ -151,7 +139,19 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": " A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way." + "description": " A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. + +A `resizeInfo` object: + +| Property | Description | +| -------- | :---------- | +| blob | The original `File` or `Blob` object, if available. +| height | Desired height of the image after the resize operation. +| image | The original `HTMLImageElement` object, if available. +| sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). +| targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +| width | Desired width of the image after the resize operation. | {: .table } +" } ], [ @@ -366,19 +366,7 @@ A `CanvasWrapper` object: } ], null) }} -{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file. - - A `resizeInfo` object: - - | Property | Description | - | -------- | :---------- | - | blob | The original `File` or `Blob` object, if available. - | height | Desired height of the image after the resize operation. - | image | The original `HTMLImageElement` object, if available. - | sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). - | targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - | width | Desired width of the image after the resize operation. | {: .table } -", +{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file.", [ { "name": "id", @@ -393,7 +381,19 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": "A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way." + "description": "A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. + +A `resizeInfo` object: + +| Property | Description | +| -------- | :---------- | +| blob | The original `File` or `Blob` object, if available. +| height | Desired height of the image after the resize operation. +| image | The original `HTMLImageElement` object, if available. +| sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). +| targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +| width | Desired width of the image after the resize operation. | {: .table } +" } ], [ From 70c2ee8a68b42eb440ce2a37c627cef110dec352 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 17:01:31 -0500 Subject: [PATCH 08/27] fix(options.jmd): Format of resizeInfo object #1525 --- docs/api/options.jmd | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/api/options.jmd b/docs/api/options.jmd index 0e290c50d..69c5ff17b 100644 --- a/docs/api/options.jmd +++ b/docs/api/options.jmd @@ -187,16 +187,15 @@ alert("The `chunking.success.endpoint` option **only** applies to traditional up {{ api_parent_option("scaling", "scaling", "See the [Upload Scaled Images feature page](../features/scaling.html) for more details.", ( - ("scaling.customResizer", "customResizer", """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - - * `blob` - The original `File` or `Blob` object, if available. - * `height` - Desired height of the image after the resize operation. - * `image` - The original `HTMLImageElement` object, if available. - * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). - * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - * `width` - Desired width of the image after the resize operation. - - """, + ("scaling.customResizer", "customResizer", "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + +* `blob` - The original `File` or `Blob` object, if available. +* `height` - Desired height of the image after the resize operation. +* `image` - The original `HTMLImageElement` object, if available. +* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +* `width` - Desired width of the image after the resize operation. +", "Function", "undefined"), ("scaling.defaultQuality", "defaultQuality", "A value between 1 and 100 that describes the requested quality of scaled images. Ignored unless the scaled image type target is image/jpeg.", "Integer", "80",), ("scaling.defaultType", "defaultType", "Scaled images will assume this image type if you don't specify a specific type in your size object, or if the type specified in the size object is not valid. You generally should not use any value other than image/jpeg or image/png here. The default value of `null` will ensure the scaled image type is PNG, unless the original file is a JPEG, in which case the scaled file will also be a JPEG. The default is probably the safest option.", "String", "null",), From 785e7c3dcf80b76bcc72d2c64b63d5f933d2cc10 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 17:03:45 -0500 Subject: [PATCH 09/27] fix(options/methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 44 +++++++++++++++++++---------------------- docs/api/options-ui.jmd | 17 ++++++++-------- docs/api/options.jmd | 4 +++- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 1d719b880..0bbeb64f2 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -139,18 +139,16 @@ A `CanvasWrapper` object: { "name": "customResizer", "type": "function", - "description": " A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. - -A `resizeInfo` object: - -| Property | Description | -| -------- | :---------- | -| blob | The original `File` or `Blob` object, if available. -| height | Desired height of the image after the resize operation. -| image | The original `HTMLImageElement` object, if available. -| sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). -| targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. -| width | Desired width of the image after the resize operation. | {: .table } + "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. + +A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + +* `blob` - The original `File` or `Blob` object, if available. +* `height` - Desired height of the image after the resize operation. +* `image` - The original `HTMLImageElement` object, if available. +* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +* `width` - Desired width of the image after the resize operation. " } ], @@ -381,18 +379,16 @@ A `resizeInfo` object: { "name": "customResizer", "type": "function", - "description": "A `resizeInfo` object will be passed to the supplied function. Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. - -A `resizeInfo` object: - -| Property | Description | -| -------- | :---------- | -| blob | The original `File` or `Blob` object, if available. -| height | Desired height of the image after the resize operation. -| image | The original `HTMLImageElement` object, if available. -| sourceCanvas | `HTMLCanvasElement` element containing the original image data (not resized). -| targetCanvas | `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. -| width | Desired width of the image after the resize operation. | {: .table } + "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. + +A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + +* `blob` - The original `File` or `Blob` object, if available. +* `height` - Desired height of the image after the resize operation. +* `image` - The original `HTMLImageElement` object, if available. +* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +* `width` - Desired width of the image after the resize operation. " } ], diff --git a/docs/api/options-ui.jmd b/docs/api/options-ui.jmd index e63801609..2c5f40676 100644 --- a/docs/api/options-ui.jmd +++ b/docs/api/options-ui.jmd @@ -102,16 +102,17 @@ options for `messages`""", "info", "Note:") }} {{ api_parent_option("thumbnails", "thumbnails", "", ( - ("thumbnails.customResizer", "customResizer", """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + ("thumbnails.customResizer", "customResizer", """Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. - * `blob` - The original `File` or `Blob` object, if available. - * `height` - Desired height of the image after the resize operation. - * `image` - The original `HTMLImageElement` object, if available. - * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). - * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - * `width` - Desired width of the image after the resize operation. +A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - """, +* `blob` - The original `File` or `Blob` object, if available. +* `height` - Desired height of the image after the resize operation. +* `image` - The original `HTMLImageElement` object, if available. +* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +* `width` - Desired width of the image after the resize operation. +", "Function", "undefined"), ("thumbnails.maxCount", "maxCount", "Maximum number of previews to render per Fine Uploader instance. A call to the reset method resets this value as well.", "Integer", "0",), ("thumbnails.timeBetweenThumbs", "timeBetweenThumbs", "The amount of time, in milliseconds, to pause between each preview generation process. This is in place to prevent the UI thread from locking up for a continuously long period of time, as preview generation can be a resource-intensive process.", "Integer", "750",), diff --git a/docs/api/options.jmd b/docs/api/options.jmd index 69c5ff17b..3b6148232 100644 --- a/docs/api/options.jmd +++ b/docs/api/options.jmd @@ -187,7 +187,9 @@ alert("The `chunking.success.endpoint` option **only** applies to traditional up {{ api_parent_option("scaling", "scaling", "See the [Upload Scaled Images feature page](../features/scaling.html) for more details.", ( - ("scaling.customResizer", "customResizer", "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: + ("scaling.customResizer", "customResizer", "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. + + A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: * `blob` - The original `File` or `Blob` object, if available. * `height` - Desired height of the image after the resize operation. From dafd6c837e03c013ed26f95ef690150a04a995f0 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 17:06:15 -0500 Subject: [PATCH 10/27] fix(methods.jmd): Format of resizeInfo object #1525 --- docs/api/methods.jmd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 0bbeb64f2..d7b39b5fd 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -113,8 +113,7 @@ A `CanvasWrapper` object: } ]) }} -{{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])", -"Draws a thumbnail." +{{ api_method("drawThumbnail", "drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])", "Draws a thumbnail.", [ { "name": "id", From 0b701b75cce1185a12a9cb0f7210489b1f57c1dd Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 27 May 2016 17:24:25 -0500 Subject: [PATCH 11/27] docs(async.md): onResizeImage event no longer exists --- docs/features/async-tasks-and-promises.jmd | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/features/async-tasks-and-promises.jmd b/docs/features/async-tasks-and-promises.jmd index 1bfc4c390..d962d58e7 100644 --- a/docs/features/async-tasks-and-promises.jmd +++ b/docs/features/async-tasks-and-promises.jmd @@ -33,7 +33,6 @@ promise instance (see individual event docs for more details): * `onCancel` * `onCredentialsExpired` * `onPasteReceived` -* `onResizeImage` * `onSubmit` * `onSubmitDelete` * `onValidate` From a0177cdbd6fc3ffa61f4aac279dce57dc2c83165 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Tue, 31 May 2016 18:10:43 -0500 Subject: [PATCH 12/27] feat(megapix-image): Rotate images before custom resize #1525 --- client/js/image-support/megapix-image.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/js/image-support/megapix-image.js b/client/js/image-support/megapix-image.js index d4a8ac033..dc77a7ad6 100644 --- a/client/js/image-support/megapix-image.js +++ b/client/js/image-support/megapix-image.js @@ -114,6 +114,8 @@ promise = new qq.Promise(), modifiedDimensions; + ctx.save(); + if (options.resize) { return renderImageToCanvasWithCustomResizer({ blob: blob, @@ -121,14 +123,13 @@ image: img, imageHeight: ih, imageWidth: iw, + orientation: options.orientation, resize: options.resize, targetHeight: height, targetWidth: width }) } - ctx.save(); - if (!qq.supportedFeatures.unlimitedScaledImageSize) { modifiedDimensions = maybeCalculateDownsampledDimensions({ origWidth: width, @@ -169,8 +170,8 @@ tmpCtx = tmpCanvas.getContext("2d"); while (sy < ih) { - sx = 0, - dx = 0; + sx = 0; + dx = 0; while (sx < iw) { tmpCtx.clearRect(0, 0, d, d); tmpCtx.drawImage(img, -sx, -sy); @@ -200,6 +201,7 @@ image = resizeInfo.image, imageHeight = resizeInfo.imageHeight, imageWidth = resizeInfo.imageWidth, + orientation = resizeInfo.orientation, promise = new qq.Promise(), resize = resizeInfo.resize, sourceCanvas = document.createElement("canvas"), @@ -208,8 +210,7 @@ targetHeight = resizeInfo.targetHeight, targetWidth = resizeInfo.targetWidth; - sourceCanvas.height = imageHeight; - sourceCanvas.width = imageWidth; + transformCoordinate(sourceCanvas, imageWidth, imageHeight, orientation); targetCanvas.height = targetHeight; targetCanvas.width = targetWidth; From e61e36021736cd984e5a9a5da9621c984fb83023 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Thu, 2 Jun 2016 21:10:05 -0500 Subject: [PATCH 13/27] chore(devenv.js): updates to manual dev testing code [skip ci] --- test/dev/devenv.js | 34 +++++++++++++++++++++------------- test/dev/index.html | 1 + 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/test/dev/devenv.js b/test/dev/devenv.js index e00d423c5..b68ff952c 100644 --- a/test/dev/devenv.js +++ b/test/dev/devenv.js @@ -41,21 +41,35 @@ qq(window).attach("load", function() { enableAuto: true }, thumbnails: { + customResizer: !qq.ios() && function(resizeInfo) { + var promise = new qq.Promise(); + + pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() { + promise.success(); + }) + + return promise; + }, placeholders: { waitingPath: "/client/placeholders/waiting-generic.png", notAvailablePath: "/client/placeholders/not_available-generic.png" } }, - //scaling: { - // sizes: [{name: "small", maxSize: 300}] - //}, + scaling: { + customResizer: !qq.ios() && function(resizeInfo) { + var promise = new qq.Promise(); + + pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() { + promise.success(); + }) + + return promise; + }, + sizes: [{name: "small", maxSize: 800}] + }, session: { //endpoint: "/test/dev/handlers/vendor/fineuploader/php-traditional-server/endpoint.php?initial" }, - validation: { - //sizeLimit: 10000000, - //itemLimit: 4 - }, callbacks: { onError: errorHandler, onUpload: function (id, filename) { @@ -65,12 +79,6 @@ qq(window).attach("load", function() { }, id); } - //onStatusChange: function (id, oldS, newS) { - // qq.log("id: " + id + " " + newS); - //}, - //onComplete: function (id, name, response) { - // qq.log(response); - //} } }); diff --git a/test/dev/index.html b/test/dev/index.html index 7f64122c5..4a828e73e 100644 --- a/test/dev/index.html +++ b/test/dev/index.html @@ -207,6 +207,7 @@ + From 18205647f92c9ab5193e44d03e8ada77b5c98faa Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 3 Jun 2016 22:04:55 -0500 Subject: [PATCH 14/27] simpler, faster travis build --- .travis.yml | 12 +----------- package.json | 2 ++ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b7c9d48c..12d092782 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ --- addons: - firefox: "38.0" + firefox: "latest" sudo: false language: node_js @@ -21,16 +21,9 @@ env: awR42/q/Akos2eA8NWx5yU+hRC5rr+oQG5Eio0tzi9+y3a6VXDvgS1h2SaQz TR/MjA/29gFvV7bnp1LSs2TdZx+NGhLd4zHv01XZ+pQk/nQiW9w= -before_install: -- npm install -g grunt-cli -- git submodule update --init --recursive - before_script: - "sh -e /etc/init.d/xvfb start" -script: -- grunt travis - branches: only: - master @@ -38,6 +31,3 @@ branches: - /^feature.*$/ - /^.*fix.*$/ - /^release.*$/ -notifications: - slack: - secure: qb1LdOGlBVKCLxNi86tWrabIKs9TFa3ttpLIwu1vtEeh+R9XDeG32X89sM3a5CHRwLqkHwrs6JNcIC4qhTAKiUOiaPYPbv7PkZXX1GIuOPMBp20ghpnWA7QHv6SpmW4qDCTixZSzf0B0m97muzWm1VnotgRELbfKr9Cf/7h3jS0= diff --git a/package.json b/package.json index 6894e992b..45b16001c 100644 --- a/package.json +++ b/package.json @@ -81,9 +81,11 @@ }, "scripts": { "build": "grunt package", + "devbuild": "grunt dev", "setup-dev": "(cd test/dev/handlers; curl -sS https://getcomposer.org/installer | php; php composer.phar install)", "start-local-dev": "(. test/dev/handlers/s3keys.sh; php -S 0.0.0.0:9090 -t . -c test/dev/handlers/php.ini)", "start-c9-dev": "php -S $IP:$PORT -t . -c test/dev/handlers/php.ini", + "test": "if test \"$CI\" = \"true\" ; then grunt travis ; else grunt test:firefox ; fi", "update-dev": "(cd test/dev/handlers; php composer.phar update)" }, "engines" : { From a7060adf33298173cf361d82af46460b8bb2f4e7 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 3 Jun 2016 22:07:49 -0500 Subject: [PATCH 15/27] chore(build) only run grunt travis on travis VM --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45b16001c..0f9053e0d 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "setup-dev": "(cd test/dev/handlers; curl -sS https://getcomposer.org/installer | php; php composer.phar install)", "start-local-dev": "(. test/dev/handlers/s3keys.sh; php -S 0.0.0.0:9090 -t . -c test/dev/handlers/php.ini)", "start-c9-dev": "php -S $IP:$PORT -t . -c test/dev/handlers/php.ini", - "test": "if test \"$CI\" = \"true\" ; then grunt travis ; else grunt test:firefox ; fi", + "test": "if test \"$TRAVIS\" = \"true\" ; then grunt travis ; else grunt test:firefox ; fi", "update-dev": "(cd test/dev/handlers; php composer.phar update)" }, "engines" : { From 8d0734dbb9f7440787a3ae2b345d8cf09f64d57c Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 3 Jun 2016 22:34:28 -0500 Subject: [PATCH 16/27] test(scaling): start of third-party resizer unit tests #1576 --- lib/modules.js | 3 +- test/unit/scaling.js | 182 ++++++++++++++++++++++++++----------------- 2 files changed, 112 insertions(+), 73 deletions(-) diff --git a/lib/modules.js b/lib/modules.js index a2187323e..31a617d88 100644 --- a/lib/modules.js +++ b/lib/modules.js @@ -263,7 +263,8 @@ var //dependencies testHelperModules: [ "test/static/local/karma-runner.js", "test/static/local/blob-maker.js", - "test/static/third-party/q/q-1.0.1.js" + "test/static/third-party/q/q-1.0.1.js", + "node_modules/pica/dist/pica.js" ], fuSrcBuild: [ "_build/all!(@(*.min.js|*.gif|*.css))" diff --git a/test/unit/scaling.js b/test/unit/scaling.js index 435ddaa56..75f796094 100644 --- a/test/unit/scaling.js +++ b/test/unit/scaling.js @@ -13,6 +13,15 @@ if (qq.supportedFeatures.scaling) { } }); }, 10); + }, + typicalCustomResizer = function(resizeInfo) { + var promise = new qq.Promise(); + + pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() { + promise.success(); + }); + + return promise; }; it("is disabled if no sizes are specified", function() { @@ -148,14 +157,17 @@ if (qq.supportedFeatures.scaling) { }); describe("generates simple scaled image tests", function() { - function runScaleTest(orient, done) { - assert.expect(3, done); - + function runScaleTest(orient, customResizer, done) { var scalerContext = qq.extend({}, qq.Scaler.prototype), scale = qq.bind(qq.Scaler.prototype._generateScaledImage, scalerContext); qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { - scale({maxSize: 50, orient: orient, log: function(){}}, blob).then(function(scaledBlob) { + scale({ + maxSize: 50, + orient: orient, + log: function(){}, + customResizeFunction: customResizer + }, blob).then(function(scaledBlob) { var URL = window.URL && window.URL.createObjectURL ? window.URL : window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : null, @@ -167,6 +179,7 @@ if (qq.supportedFeatures.scaling) { img.onload = function() { assert.ok(this.width <= 50); assert.ok(this.height <= 50); + done(); }; img.onerror = function() { @@ -178,13 +191,27 @@ if (qq.supportedFeatures.scaling) { }); } - it("generates a properly scaled & oriented image for a reference image", function(done) { - runScaleTest(true, done); - }); + describe("using built-in resizer code", function() { + it("generates a properly scaled & oriented image for a reference image", function(done) { + runScaleTest(true, null, done); + }); - it("generates a properly scaled image for a reference image", function(done) { - runScaleTest(false, done); + it("generates a properly scaled image for a reference image", function(done) { + runScaleTest(false, null, done); + }); }); + + if (!qq.ios()) { + describe("using third-party resizer code", function() { + it("generates a properly scaled & oriented image for a reference image", function(done) { + runScaleTest(true, typicalCustomResizer, done); + }); + + it("generates a properly scaled image for a reference image", function(done) { + runScaleTest(false, typicalCustomResizer, done); + }); + }) + } }); @@ -257,74 +284,85 @@ if (qq.supportedFeatures.scaling) { }); }); - it("uploads scaled files as expected: non-chunked, default options", function(done) { - assert.expect(39, done); + describe("scaled files uploads (non-chunked, default options)", function() { + function runTest(customResizer, done) { + assert.expect(39, done); - var referenceFileSize, - sizes = [ - { - name: "small", - maxSize: 50 - }, - { - name: "medium", - maxSize: 400 - } - ], - expectedUploadCallbacks = [ - {id: 0, name: "up (small).jpeg"}, - {id: 1, name: "up (medium).jpeg"}, - {id: 2, name: "up.jpeg"}, - {id: 3, name: "up2 (small).jpeg"}, - {id: 4, name: "up2 (medium).jpeg"}, - {id: 5, name: "up2.jpeg"} - ], - actualUploadCallbacks = [], - uploader = new qq.FineUploaderBasic({ - request: {endpoint: "test/uploads"}, - scaling: { - sizes: sizes - }, - callbacks: { - onUpload: function(id, name) { - assert.ok(uploader.getSize(id) > 0, "Blob size is not greater than 0"); - assert.ok(qq.isBlob(uploader.getFile(id)), "file is not a Blob"); - assert.equal(uploader.getFile(id).size, referenceFileSize); - - actualUploadCallbacks.push({id: id, name: name}); - setTimeout(function() { - var req = fileTestHelper.getRequests()[id], - parentUuid = req.requestBody.fields.qqparentuuid, - parentSize = req.requestBody.fields.qqparentsize, - parentId = uploader.getParentId(id), - file = req.requestBody.fields.qqfile; - - assert.equal(file.type, "image/jpeg"); - - if (parentId !== null) { - assert.equal(parentUuid, uploader.getUuid(parentId)); - assert.equal(parentSize, uploader.getSize(parentId)); - } - else { - assert.equal(parentUuid, undefined); - assert.equal(parentSize, undefined); - } - - req.respond(200, null, JSON.stringify({success: true})); - }, 10); + var referenceFileSize, + sizes = [ + { + name: "small", + maxSize: 50 }, - onAllComplete: function(successful, failed) { - assert.equal(successful.length, 6); - assert.equal(failed.length, 0); - assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks); + { + name: "medium", + maxSize: 400 } - } + ], + expectedUploadCallbacks = [ + {id: 0, name: "up (small).jpeg"}, + {id: 1, name: "up (medium).jpeg"}, + {id: 2, name: "up.jpeg"}, + {id: 3, name: "up2 (small).jpeg"}, + {id: 4, name: "up2 (medium).jpeg"}, + {id: 5, name: "up2.jpeg"} + ], + actualUploadCallbacks = [], + uploader = new qq.FineUploaderBasic({ + request: {endpoint: "test/uploads"}, + scaling: { + sizes: sizes, + customResizer: customResizer + }, + callbacks: { + onUpload: function(id, name) { + assert.ok(uploader.getSize(id) > 0, "Blob size is not greater than 0"); + assert.ok(qq.isBlob(uploader.getFile(id)), "file is not a Blob"); + assert.equal(uploader.getFile(id).size, referenceFileSize); + + actualUploadCallbacks.push({id: id, name: name}); + setTimeout(function() { + var req = fileTestHelper.getRequests()[id], + parentUuid = req.requestBody.fields.qqparentuuid, + parentSize = req.requestBody.fields.qqparentsize, + parentId = uploader.getParentId(id), + file = req.requestBody.fields.qqfile; + + assert.equal(file.type, "image/jpeg"); + + if (parentId !== null) { + assert.equal(parentUuid, uploader.getUuid(parentId)); + assert.equal(parentSize, uploader.getSize(parentId)); + } + else { + assert.equal(parentUuid, undefined); + assert.equal(parentSize, undefined); + } + + req.respond(200, null, JSON.stringify({success: true})); + }, 100); + }, + onAllComplete: function(successful, failed) { + assert.equal(successful.length, 6); + assert.equal(failed.length, 0); + assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks); + } + } + }); + + qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { + fileTestHelper.mockXhr(); + referenceFileSize = blob.size; + uploader.addFiles([{blob: blob, name: "up.jpeg"}, {blob: blob, name: "up2.jpeg"}]); }); + }; - qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { - fileTestHelper.mockXhr(); - referenceFileSize = blob.size; - uploader.addFiles([{blob: blob, name: "up.jpeg"}, {blob: blob, name: "up2.jpeg"}]); + it("uploads as expected with internal resizer code", function(done) { + runTest(null, done); + }); + + it("uploads as expected with third-party resizer code", function(done) { + runTest(typicalCustomResizer, done); }); }); From 10be8a3909a1d4e8da701174f87bce7e51a3d756 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Fri, 3 Jun 2016 22:42:36 -0500 Subject: [PATCH 17/27] test(scaling): dont run 3rd-party scaling tests on iOS & obey the lint #1576 --- package.json | 1 + test/unit/scaling.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0f9053e0d..81a83748e 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "scripts": { "build": "grunt package", "devbuild": "grunt dev", + "lint": "grunt jshint:tests; grunt jshint:source; grunt jscs:tests; grunt jscs:src", "setup-dev": "(cd test/dev/handlers; curl -sS https://getcomposer.org/installer | php; php composer.phar install)", "start-local-dev": "(. test/dev/handlers/s3keys.sh; php -S 0.0.0.0:9090 -t . -c test/dev/handlers/php.ini)", "start-c9-dev": "php -S $IP:$PORT -t . -c test/dev/handlers/php.ini", diff --git a/test/unit/scaling.js b/test/unit/scaling.js index 75f796094..b8e2dcc16 100644 --- a/test/unit/scaling.js +++ b/test/unit/scaling.js @@ -1,4 +1,4 @@ -/* globals describe, it, qq, assert, qqtest, helpme */ +/* globals describe, it, qq, assert, qqtest, helpme, pica */ if (qq.supportedFeatures.scaling) { describe("scaling module tests", function() { "use strict"; @@ -210,7 +210,7 @@ if (qq.supportedFeatures.scaling) { it("generates a properly scaled image for a reference image", function(done) { runScaleTest(false, typicalCustomResizer, done); }); - }) + }); } }); @@ -355,15 +355,17 @@ if (qq.supportedFeatures.scaling) { referenceFileSize = blob.size; uploader.addFiles([{blob: blob, name: "up.jpeg"}, {blob: blob, name: "up2.jpeg"}]); }); - }; + } it("uploads as expected with internal resizer code", function(done) { runTest(null, done); }); - it("uploads as expected with third-party resizer code", function(done) { - runTest(typicalCustomResizer, done); - }); + if (!qq.ios()) { + it("uploads as expected with third-party resizer code", function (done) { + runTest(typicalCustomResizer, done); + }); + } }); it("ensure scaled versions of non-JPEGs are always PNGs", function(done) { From 62fdc218589e0d0bcb148cb4524be558ed80a2c1 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:24:34 -0500 Subject: [PATCH 18/27] test(scaling): completed additional scaling tests Also fixed the scaleImage method docs. #1576 --- docs/api/methods.jmd | 16 +-- test/unit/scaling.js | 332 ++++++++++++++++++++++++------------------- 2 files changed, 197 insertions(+), 151 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index d7b39b5fd..3c804d869 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -363,7 +363,7 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t } ], null) }} -{{ api_method("scaleImage", "scaleImage (id, options[, customResizer])", "Generates a scaled version of a submitted image file.", +{{ api_method("scaleImage", "scaleImage (id, options)", "Generates a scaled version of a submitted image file.", [ { "name": "id", @@ -373,13 +373,13 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t { "name": "options", "type": "Object", - "description": "Information about the scaled image to generate. The `maxSize` property is required (integer). Optional properties are: `orient` (boolean, defaults to true), `type` (string, defaults to the type of the reference image), and `quality` (number between 0 and 100, defaults to 80), and `includeExif` (boolean, defaults to `false`)." - }, - { - "name": "customResizer", - "type": "function", - "description": "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. - + "description": "Information about the scaled image to generate. The following properties are supported: +* `maxSize` (**required**) (integer). +* `orient` (boolean, defaults to true) +* `type` (string, defaults to the type of the reference image) +* `quality` (number between 0 and 100, defaults to 80) +* `includeExif` (boolean, defaults to `false`)." +* `customResizer` (function) - "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: * `blob` - The original `File` or `Blob` object, if available. diff --git a/test/unit/scaling.js b/test/unit/scaling.js index b8e2dcc16..d022c4d3b 100644 --- a/test/unit/scaling.js +++ b/test/unit/scaling.js @@ -368,48 +368,61 @@ if (qq.supportedFeatures.scaling) { } }); - it("ensure scaled versions of non-JPEGs are always PNGs", function(done) { - assert.expect(4, done); - - var expectedOutputTypes = [ - "image/png", - "image/png", - "image/png", - "image/gif" - ], - sizes = [ - { - name: "small", - maxSize: 50 - } - ], - actualUploadCallbacks = [], - uploader = new qq.FineUploaderBasic({ - request: {endpoint: "test/uploads"}, - scaling: { - sizes: sizes - }, - callbacks: { - onUpload: function(id, name) { - actualUploadCallbacks.push({id: id, name: name}); - setTimeout(function() { - var req = fileTestHelper.getRequests()[id], - file = req.requestBody.fields.qqfile; + describe("jpeg to PNG conversion behavior", function() { + function runTest(customResizer, done) { + assert.expect(4, done); + + var expectedOutputTypes = [ + "image/png", + "image/png", + "image/png", + "image/gif" + ], + sizes = [ + { + name: "small", + maxSize: 50 + } + ], + actualUploadCallbacks = [], + uploader = new qq.FineUploaderBasic({ + request: {endpoint: "test/uploads"}, + scaling: { + customResizer: customResizer, + sizes: sizes + }, + callbacks: { + onUpload: function(id, name) { + actualUploadCallbacks.push({id: id, name: name}); + setTimeout(function() { + var req = fileTestHelper.getRequests()[id], + file = req.requestBody.fields.qqfile; - assert.equal(file.type, expectedOutputTypes[id]); + assert.equal(file.type, expectedOutputTypes[id]); - req.respond(200, null, JSON.stringify({success: true})); - }, 10); + req.respond(200, null, JSON.stringify({success: true})); + }, 100); + } } - } - }); + }); - qqtest.downloadFileAsBlob("star.png", "image/png").then(function(star) { - qqtest.downloadFileAsBlob("drop-background.gif", "image/gif").then(function(drop) { - fileTestHelper.mockXhr(); - uploader.addFiles([{blob: star, name: "star.png"}, {blob: drop, name: "drop.gif"}]); + qqtest.downloadFileAsBlob("star.png", "image/png").then(function(star) { + qqtest.downloadFileAsBlob("drop-background.gif", "image/gif").then(function(drop) { + fileTestHelper.mockXhr(); + uploader.addFiles([{blob: star, name: "star.png"}, {blob: drop, name: "drop.gif"}]); + }); }); + } + + it("behaves as expected with internal resizer", function(done) { + runTest(null, done); }); + + if (!qq.ios()) { + it("behaves as expected with custom resizer", function (done) { + runTest(typicalCustomResizer, done); + }); + } }); it("uploads scaled files as expected: chunked, default options", function(done) { @@ -552,67 +565,80 @@ if (qq.supportedFeatures.scaling) { }); }); - it("generates a scaled Blob of the original file's type if the requested type is not specified or is not valid", function(done) { - assert.expect(7, done); - - var sizes = [ - { - name: "one", - maxSize: 100, - type: "image/jpeg" - }, - { - name: "two", - maxSize: 101, - type: "image/blah" - }, - { - name: "three", - maxSize: 102 - } - ], - expectedUploadCallbacks = [ - {id: 0, name: "test (one).jpeg"}, - {id: 1, name: "test (two).png"}, - {id: 2, name: "test (three).png"}, - {id: 3, name: "test.png"} - ], - expectedScaledBlobType = [ - "image/jpeg", - "image/png", - "image/png", - "image/png" - ], - actualUploadCallbacks = [], - uploader = new qq.FineUploaderBasic({ - request: {endpoint: "test/uploads"}, - scaling: { - defaultType: "image/png", - sizes: sizes - }, - callbacks: { - onUpload: function(id, name) { - actualUploadCallbacks.push({id: id, name: name}); - setTimeout(function() { - var req = fileTestHelper.getRequests()[id], - actualType = req.requestBody.fields.qqfile.type; + describe("generating a scaled Blob of the original file's type if the requested type is not specified or is not valid", function() { + function runTest(customResizer, done) { + assert.expect(7, done); - assert.equal(actualType, expectedScaledBlobType[id], "(" + id + ") Scaled blob type (" + actualType + ") is incorrect. Expected " + expectedScaledBlobType[id]); - req.respond(200, null, JSON.stringify({success: true})); - }, 10); + var sizes = [ + { + name: "one", + maxSize: 100, + type: "image/jpeg" }, - onAllComplete: function(successful, failed) { - assert.equal(successful.length, 4); - assert.equal(failed.length, 0); - assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks); + { + name: "two", + maxSize: 101, + type: "image/blah" + }, + { + name: "three", + maxSize: 102 } - } + ], + expectedUploadCallbacks = [ + {id: 0, name: "test (one).jpeg"}, + {id: 1, name: "test (two).png"}, + {id: 2, name: "test (three).png"}, + {id: 3, name: "test.png"} + ], + expectedScaledBlobType = [ + "image/jpeg", + "image/png", + "image/png", + "image/png" + ], + actualUploadCallbacks = [], + uploader = new qq.FineUploaderBasic({ + request: {endpoint: "test/uploads"}, + scaling: { + customResizer: customResizer, + defaultType: "image/png", + sizes: sizes + }, + callbacks: { + onUpload: function(id, name) { + actualUploadCallbacks.push({id: id, name: name}); + setTimeout(function() { + var req = fileTestHelper.getRequests()[id], + actualType = req.requestBody.fields.qqfile.type; + + assert.equal(actualType, expectedScaledBlobType[id], "(" + id + ") Scaled blob type (" + actualType + ") is incorrect. Expected " + expectedScaledBlobType[id]); + req.respond(200, null, JSON.stringify({success: true})); + }, 10); + }, + onAllComplete: function(successful, failed) { + assert.equal(successful.length, 4); + assert.equal(failed.length, 0); + assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks); + } + } + }); + + qqtest.downloadFileAsBlob("star.png", "image/png").then(function(blob) { + fileTestHelper.mockXhr(); + uploader.addFiles({blob: blob, name: "test.png"}); }); + } - qqtest.downloadFileAsBlob("star.png", "image/png").then(function(blob) { - fileTestHelper.mockXhr(); - uploader.addFiles({blob: blob, name: "test.png"}); + it("behaves as expected with internal resizer", function(done) { + runTest(null, done); }); + + if (!qq.ios()) { + it("behaves as expected with custom resizer", function (done) { + runTest(typicalCustomResizer, done); + }); + } }); it("uploads scaled files as expected, excluding the original: non-chunked, default options", function(done) { @@ -761,7 +787,7 @@ if (qq.supportedFeatures.scaling) { }); describe("scaleImage API method tests", function() { - it("return a scaled version of an existing image file, fail a request for a missing file, fail a request for a non-image file", function(done) { + function runTest(customResizer, done) { assert.expect(6, done); var referenceFileSize, @@ -771,7 +797,7 @@ if (qq.supportedFeatures.scaling) { onUpload: acknowledgeRequests, onAllComplete: function(successful, failed) { - uploader.scaleImage(0, {maxSize: 10}).then(function(scaledBlob) { + uploader.scaleImage(0, {customResizer: customResizer, maxSize: 10}).then(function(scaledBlob) { assert.ok(qq.isBlob(scaledBlob)); assert.ok(scaledBlob.size < referenceFileSize); assert.equal(scaledBlob.type, "image/jpeg"); @@ -784,13 +810,13 @@ if (qq.supportedFeatures.scaling) { }); // not an image - uploader.scaleImage(1, {maxSize: 10}).then(function() {}, + uploader.scaleImage(1, {customResizer: customResizer, maxSize: 10}).then(function() {}, function() { assert.ok(true); }); //missing - uploader.scaleImage(2, {maxSize: 10}).then(function() {}, + uploader.scaleImage(2, {customResizer: customResizer, maxSize: 10}).then(function() {}, function() { assert.ok(true); }); @@ -806,64 +832,84 @@ if (qq.supportedFeatures.scaling) { uploader.addFiles([{blob: up, name: "up.jpg"}, {blob: text, name: "text.txt"}]); }); }); + } + + it("return a scaled version of an existing image file, fail a request for a missing file, fail a request for a non-image file - internal resizer", function(done) { + runTest(null, done); + }); + + it("return a scaled version of an existing image file, fail a request for a missing file, fail a request for a non-image file - custom resizer", function(done) { + runTest(typicalCustomResizer, done); }); }); - it("includes EXIF data in scaled image (only if requested & appropriate)", function(done) { - assert.expect(8, done); + describe("EXIF data inclusion in scaled images", function() { + function runTest(customResizer, done) { + assert.expect(8, done); - var getReqFor = function(uuid) { - var theReq; + var getReqFor = function (uuid) { + var theReq; - qq.each(fileTestHelper.getRequests(), function(idx, req) { - if (req.requestBody.fields.qquuid === uuid) { - theReq = req; - return false; + qq.each(fileTestHelper.getRequests(), function (idx, req) { + if (req.requestBody.fields.qquuid === uuid) { + theReq = req; + return false; + } + }); + + return theReq; + }, + uploader = new qq.FineUploaderBasic({ + request: {endpoint: "test/uploads"}, + scaling: { + customResizer: customResizer, + includeExif: true, + sizes: [{name: "scaled", maxSize: 50}] + }, + callbacks: { + onUpload: function (id) { + setTimeout(function () { + var req = getReqFor(uploader.getUuid(id)), + blob = req.requestBody.fields.qqfile, + name = req.requestBody.fields.qqfilename; + + assert.ok(qq.isBlob(blob)); + new qq.Exif(blob, function () { + }).parse().then(function (tags) { + if (name.indexOf("left") === 0) { + assert.equal(tags.Orientation, 6); + } + else { + assert.fail(null, null, name + " contains EXIF data, unexpectedly"); + } + }, function () { + if (name.indexOf("star") === 0) { + assert.ok(true); + } + else { + assert.fail(null, null, name + " does not contains EXIF data, unexpectedly"); + } + }); + req.respond(200, null, JSON.stringify({success: true})); + }, 10); + } } }); - return theReq; - }, - uploader = new qq.FineUploaderBasic({ - request: {endpoint: "test/uploads"}, - scaling: { - includeExif: true, - sizes: [{name: "scaled", maxSize: 50}] - }, - callbacks: { - onUpload: function(id) { - setTimeout(function() { - var req = getReqFor(uploader.getUuid(id)), - blob = req.requestBody.fields.qqfile, - name = req.requestBody.fields.qqfilename; - - assert.ok(qq.isBlob(blob)); - new qq.Exif(blob, function(){}).parse().then(function(tags) { - if (name.indexOf("left") === 0) { - assert.equal(tags.Orientation, 6); - } - else { - assert.fail(null, null, name + " contains EXIF data, unexpectedly"); - } - }, function() { - if (name.indexOf("star") === 0) { - assert.ok(true); - } - else { - assert.fail(null, null, name + " does not contains EXIF data, unexpectedly"); - } - }); - req.respond(200, null, JSON.stringify({success: true})); - }, 10); - } - } + qqtest.downloadFileAsBlob("left.jpg", "image/jpeg").then(function (left) { + qqtest.downloadFileAsBlob("star.png", "image/png").then(function (star) { + fileTestHelper.mockXhr(); + uploader.addFiles([{blob: left, name: "left.jpg"}, {blob: star, name: "star.png"}]); + }); + }); + } + + it("includes EXIF data only if requested & appropriate - internal resizer", function(done) { + runTest(null, done); }); - qqtest.downloadFileAsBlob("left.jpg", "image/jpeg").then(function(left) { - qqtest.downloadFileAsBlob("star.png", "image/png").then(function(star) { - fileTestHelper.mockXhr(); - uploader.addFiles([{blob: left, name: "left.jpg"}, {blob: star, name: "star.png"}]); - }); + it("includes EXIF data only if requested & appropriate - custom resizer", function(done) { + runTest(typicalCustomResizer, done); }); }); }); From cbcd591e14597a490aff0d3d2eedc4ab7e657bfd Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:25:59 -0500 Subject: [PATCH 19/27] docs(methods): syntax error in scaleImage docs #1576 --- docs/api/methods.jmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 3c804d869..4dc5dc22a 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -379,7 +379,7 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t * `type` (string, defaults to the type of the reference image) * `quality` (number between 0 and 100, defaults to 80) * `includeExif` (boolean, defaults to `false`)." -* `customResizer` (function) - "Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. +* `customResizer` (function) - Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: * `blob` - The original `File` or `Blob` object, if available. From 3272d3e0bcaeb0a840f5e09554baa357f03e8cfa Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:27:01 -0500 Subject: [PATCH 20/27] docs(methods): _another_ syntax error in scaleImage docs #1576 --- docs/api/methods.jmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 4dc5dc22a..1bde20281 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -378,7 +378,7 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t * `orient` (boolean, defaults to true) * `type` (string, defaults to the type of the reference image) * `quality` (number between 0 and 100, defaults to 80) -* `includeExif` (boolean, defaults to `false`)." +* `includeExif` (boolean, defaults to `false`). * `customResizer` (function) - Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: From 1922a49fc9de4cdf9c4b3a1d68a5d72c5c0919ee Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:28:16 -0500 Subject: [PATCH 21/27] docs(methods): bad formatting in scaleImage docs #1576 --- docs/api/methods.jmd | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 1bde20281..d16846f02 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -374,6 +374,7 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t "name": "options", "type": "Object", "description": "Information about the scaled image to generate. The following properties are supported: + * `maxSize` (**required**) (integer). * `orient` (boolean, defaults to true) * `type` (string, defaults to the type of the reference image) From 00ae62a44106d335974d56116896b1be01b3ea33 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:29:02 -0500 Subject: [PATCH 22/27] docs(methods): bad formatting in scaleImage docs #1576 --- docs/api/methods.jmd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index d16846f02..315035e94 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -383,12 +383,12 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t * `customResizer` (function) - Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: -* `blob` - The original `File` or `Blob` object, if available. -* `height` - Desired height of the image after the resize operation. -* `image` - The original `HTMLImageElement` object, if available. -* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). -* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. -* `width` - Desired width of the image after the resize operation. + * `blob` - The original `File` or `Blob` object, if available. + * `height` - Desired height of the image after the resize operation. + * `image` - The original `HTMLImageElement` object, if available. + * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). + * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + * `width` - Desired width of the image after the resize operation. " } ], From 9934307ef911d9391a42e822a99e71042cea90e1 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Sat, 4 Jun 2016 22:31:02 -0500 Subject: [PATCH 23/27] docs(methods): bad formatting in scaleImage docs #1576 --- docs/api/methods.jmd | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 315035e94..755cbdd71 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -381,14 +381,15 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t * `quality` (number between 0 and 100, defaults to 80) * `includeExif` (boolean, defaults to `false`). * `customResizer` (function) - Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way. -A `resizeInfo` object, which will be passed to the supplied function, contains the following properties: - * `blob` - The original `File` or `Blob` object, if available. - * `height` - Desired height of the image after the resize operation. - * `image` - The original `HTMLImageElement` object, if available. - * `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). - * `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. - * `width` - Desired width of the image after the resize operation. +A `resizeInfo` object, which will be passed to your (optional) `customResizer` function, contains the following properties: + +* `blob` - The original `File` or `Blob` object, if available. +* `height` - Desired height of the image after the resize operation. +* `image` - The original `HTMLImageElement` object, if available. +* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized). +* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. +* `width` - Desired width of the image after the resize operation. " } ], From f1290bcd9a5d1348a4863d3730cc2d4a45dffbf8 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Wed, 15 Jun 2016 16:37:12 -0500 Subject: [PATCH 24/27] docs(thumbnails & scaling): feature docs for custom reszier #1576 --- docs/features/scaling.jmd | 41 ++++++++++++++++++++++++++++++++++++ docs/features/thumbnails.jmd | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/docs/features/scaling.jmd b/docs/features/scaling.jmd index d4b6e8a79..677dcc6d9 100644 --- a/docs/features/scaling.jmd +++ b/docs/features/scaling.jmd @@ -8,6 +8,8 @@ [s3]: s3.html [azure]: azure.html +[customResizer]: ../api/options.html#scaling.customResizer +[pica]: https://github.com/nodeca/pica [scaling]: ../api/options.html#scaling [sizes]: ../api/options.html#scaling.sizes [defaulttype]: ../api/options.html#scaling.defaultType @@ -16,10 +18,13 @@ [orient]: ../api/options.html#scaling.orient [hidescaled]: ../api/options-ui.html#scaling.hideScaled [getfile]: ../api/methods.html#getFile +[limby-resize]: https://github.com/danschumann/limby-resize [promise]: async-tasks-and-promises.html [data]: statistics-and-status-updates.html [api]: ../api/methods.html [itemlimit]: ../api/options.html#validation.itemLimit +[webworkers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers + # Generate and Upload Scaled Images {: .page-header } @@ -153,6 +158,42 @@ the parent images will include a "qquuid" request parameter instead. If you are [Azure][azure], these parameters will be associated with the file in your bucket or blob container. +### Using a third-party library to resize images + +Fine Uploader's internal image resize code delegates to the `drawImage` method on the browser's native `CanvasRenderingContext2D` object. +This object is used to manipulate a `` element, which represents a submitted image `File or `Blob`. +Most browsers use linear interpolation when resizing images. This leads to extreme aliasing and Moire patterns +which is a deal breaker for anyone resizing images for art/photo galleries, albums, etc. +These kinds of artifacts are impossible to remove after the fact. + +If speed is most important, and precise scaled image generation is _not_ paramount, you should continue to use Fine Uploader's +internal scaling implementation. However, if you want to generate the higher quality scaled images for upload, you should +instead use a third-party library to resize submitted image files, such as [pica] or [limby-resize]. As of version 5.10 of +Fine Uploader, it is extremely easy to integrate such a plug-in into this library. In fact, Fine Uploader will continue +to properly orient the submitted image file and then pass a properly sized `` to the image scaling library of +your choice to receive the resized image file. The only caveat is that, due to issues with scaling larger images in +iOS, you will need to continue to use Fine Uploader's internal scaling algorithm for that particular OS, as other +third-party scaling libraries most likely do _not_ continue logic to handle this complex case. Luckily, that is easy +to account for as well. + +If you'd like to, for example, use pica to generate higher-quality scaled images, simply pull pica into your project, +and contribute a [`scaling.customResizer` function][customResizer], like so: + +```javascript +scaling: { + customResizer: !qq.ios() && function(resizeInfo) { + return new Promise(function(resolve, reject) { + pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, resolve) + }) + }, + ... +} +``` + +That's it! The above code will result in a higher-quality scaled image, and pica even pushes resizing logic off to a +[web worker][webworker] to reduce strain on the UI thread. + + ### Notices * Do not set the [`scaling.hideScaled` option][hidescaled] to `true` AND the [`scaling.sendOriginal` option][sendoriginal] to `false` at the same time. This will result in no files being represented in the UI for images that are scalable. diff --git a/docs/features/thumbnails.jmd b/docs/features/thumbnails.jmd index 2ad7a9039..85d56b472 100644 --- a/docs/features/thumbnails.jmd +++ b/docs/features/thumbnails.jmd @@ -5,6 +5,10 @@ {% endblock %} {% block content %} {% markdown %} + +[customResizer]: ../api/options-ui.html#thumbnails.customResizer + + # Previews & Thumbnails {: .page-header } ## Summary @@ -122,6 +126,41 @@ placeholders will be treated the same way as cross-origin server-generated thumb section above for details. Re-orienting placeholder images is not supported, so, if you provide your own placeholder images, ensure they are already oriented correctly. +### Using a third-party library to resize images + +Fine Uploader's internal image resize code delegates to the `drawImage` method on the browser's native `CanvasRenderingContext2D` object. +This object is used to manipulate a `` element, which represents a submitted image `File or `Blob`. +Most browsers use linear interpolation when resizing images. This leads to extreme aliasing and Moire patterns +which may result in lower quality displayed thumbnails. + +If speed is most important, and precise scaled thumbnail generation is _not_ paramount, you should continue to use Fine Uploader's +internal scaling implementation. However, if you want to generate the higher quality thumbnail images for display, you should +instead use a third-party library to resize submitted image files, such as [pica] or [limby-resize]. As of version 5.10 of +Fine Uploader, it is extremely easy to integrate such a plug-in into this library. In fact, Fine Uploader will continue +to properly orient the submitted image file and then pass a properly sized `` to the image scaling library of +your choice to receive the resized image file. The only caveat is that, due to issues with scaling larger images in +iOS, you will need to continue to use Fine Uploader's internal scaling algorithm for that particular OS, as other +third-party scaling libraries most likely do _not_ continue logic to handle this complex case. Luckily, that is easy +to account for as well. + +If you'd like to, for example, use pica to generate higher-quality scaled images, simply pull pica into your project, +and contribute a [`thumbnails.customResizer` function][customResizer], like so: + +```javascript +thumbnails: { + customResizer: !qq.ios() && function(resizeInfo) { + return new Promise(function(resolve, reject) { + pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, resolve) + }) + }, + ... +} +``` + +That's it! The above code will result in a higher-quality scaled thumbnail, and pica even pushes resizing logic off to a +[web worker][webworker] to reduce strain on the UI thread. + + ### Core mode For Core mode users that need to create their own highly-customized UI, there is a [`drawThumbnail` API method](../api/methods.html) that will allow you to effortlessly render either a submitted image file's preview or a server-returned thumbnail From 0ffbb27486b4c646f035fd0089a45e8afc970f3c Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Wed, 15 Jun 2016 16:39:51 -0500 Subject: [PATCH 25/27] docs(thumbnails & scaling): broken link #1576 --- docs/features/scaling.jmd | 2 +- docs/features/thumbnails.jmd | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/features/scaling.jmd b/docs/features/scaling.jmd index 677dcc6d9..0e8be7bc3 100644 --- a/docs/features/scaling.jmd +++ b/docs/features/scaling.jmd @@ -191,7 +191,7 @@ scaling: { ``` That's it! The above code will result in a higher-quality scaled image, and pica even pushes resizing logic off to a -[web worker][webworker] to reduce strain on the UI thread. +[web worker][webworkers] to reduce strain on the UI thread. ### Notices diff --git a/docs/features/thumbnails.jmd b/docs/features/thumbnails.jmd index 85d56b472..f03fcf057 100644 --- a/docs/features/thumbnails.jmd +++ b/docs/features/thumbnails.jmd @@ -7,6 +7,7 @@ {% markdown %} [customResizer]: ../api/options-ui.html#thumbnails.customResizer +[webworkers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers # Previews & Thumbnails {: .page-header } @@ -158,7 +159,7 @@ thumbnails: { ``` That's it! The above code will result in a higher-quality scaled thumbnail, and pica even pushes resizing logic off to a -[web worker][webworker] to reduce strain on the UI thread. +[web worker][webworkers] to reduce strain on the UI thread. ### Core mode From 7b7dc56476d11a8749abfd59d10511c74dd73cb3 Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Wed, 15 Jun 2016 16:41:19 -0500 Subject: [PATCH 26/27] docs(thumbnails): wrong headline type for 3rd party scaler #1576 --- docs/features/thumbnails.jmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/thumbnails.jmd b/docs/features/thumbnails.jmd index f03fcf057..0c92ddcd8 100644 --- a/docs/features/thumbnails.jmd +++ b/docs/features/thumbnails.jmd @@ -127,7 +127,7 @@ placeholders will be treated the same way as cross-origin server-generated thumb section above for details. Re-orienting placeholder images is not supported, so, if you provide your own placeholder images, ensure they are already oriented correctly. -### Using a third-party library to resize images +#### Using a third-party library to resize images Fine Uploader's internal image resize code delegates to the `drawImage` method on the browser's native `CanvasRenderingContext2D` object. This object is used to manipulate a `` element, which represents a submitted image `File or `Blob`. From ca22c8ebeee21a15c85fe8c5b87461ee30e5f87c Mon Sep 17 00:00:00 2001 From: Ray Nicholus Date: Wed, 15 Jun 2016 16:49:38 -0500 Subject: [PATCH 27/27] chore(build): inc build num #1576 --- client/js/version.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/js/version.js b/client/js/version.js index 99d96c9a1..1071d1542 100644 --- a/client/js/version.js +++ b/client/js/version.js @@ -1,2 +1,2 @@ /*global qq */ -qq.version = "5.10.0-1"; +qq.version = "5.10.0-2"; diff --git a/package.json b/package.json index 81a83748e..88fc3e3fd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "fine-uploader", "title": "Fine Uploader", "main": "lib/traditional.js", - "version": "5.10.0-1", + "version": "5.10.0-2", "description": "Multiple file upload plugin with progress-bar, drag-and-drop, direct-to-S3 & Azure uploading, client-side image scaling, preview generation, form support, chunking, auto-resume, and tons of other features.", "keywords": [ "amazon",