diff options
Diffstat (limited to 'browser/extensions/screenshots/build/thumbnailGenerator.js')
-rw-r--r-- | browser/extensions/screenshots/build/thumbnailGenerator.js | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/browser/extensions/screenshots/build/thumbnailGenerator.js b/browser/extensions/screenshots/build/thumbnailGenerator.js new file mode 100644 index 0000000000..e51105fe94 --- /dev/null +++ b/browser/extensions/screenshots/build/thumbnailGenerator.js @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.thumbnailGenerator = (function () {let exports={}; // This is used in webextension/background/takeshot.js, +// server/src/pages/shot/controller.js, and +// server/scr/pages/shotindex/view.js. It is used in a browser +// environment. + +// Resize down 1/2 at a time produces better image quality. +// Not quite as good as using a third-party filter (which will be +// slower), but good enough. +const maxResizeScaleFactor = 0.5; + +// The shot will be scaled or cropped down to 210px on x, and cropped or +// scaled down to a maximum of 280px on y. +// x: 210 +// y: <= 280 +const maxThumbnailWidth = 210; +const maxThumbnailHeight = 280; + +/** + * @param {int} imageHeight Height in pixels of the original image. + * @param {int} imageWidth Width in pixels of the original image. + * @returns {width, height, scaledX, scaledY} + */ +function getThumbnailDimensions(imageWidth, imageHeight) { + const displayAspectRatio = 3 / 4; + const imageAspectRatio = imageWidth / imageHeight; + let thumbnailImageWidth, thumbnailImageHeight; + let scaledX, scaledY; + + if (imageAspectRatio > displayAspectRatio) { + // "Landscape" mode + // Scale on y, crop on x + const yScaleFactor = (imageHeight > maxThumbnailHeight) ? (maxThumbnailHeight / imageHeight) : 1.0; + thumbnailImageHeight = scaledY = Math.round(imageHeight * yScaleFactor); + scaledX = Math.round(imageWidth * yScaleFactor); + thumbnailImageWidth = Math.min(scaledX, maxThumbnailWidth); + } else { + // "Portrait" mode + // Scale on x, crop on y + const xScaleFactor = (imageWidth > maxThumbnailWidth) ? (maxThumbnailWidth / imageWidth) : 1.0; + thumbnailImageWidth = scaledX = Math.round(imageWidth * xScaleFactor); + scaledY = Math.round(imageHeight * xScaleFactor); + // The CSS could widen the image, in which case we crop more off of y. + thumbnailImageHeight = Math.min(scaledY, maxThumbnailHeight, + maxThumbnailHeight / (maxThumbnailWidth / imageWidth)); + } + + return { + width: thumbnailImageWidth, + height: thumbnailImageHeight, + scaledX, + scaledY, + }; +} + +/** + * @param {dataUrl} String Data URL of the original image. + * @param {int} imageHeight Height in pixels of the original image. + * @param {int} imageWidth Width in pixels of the original image. + * @param {String} urlOrBlob 'blob' for a blob, otherwise data url. + * @returns A promise that resolves to the data URL or blob of the thumbnail image, or null. + */ +function createThumbnail(dataUrl, imageWidth, imageHeight, urlOrBlob) { + // There's cost associated with generating, transmitting, and storing + // thumbnails, so we'll opt out if the image size is below a certain threshold + const thumbnailThresholdFactor = 1.20; + const thumbnailWidthThreshold = maxThumbnailWidth * thumbnailThresholdFactor; + const thumbnailHeightThreshold = maxThumbnailHeight * thumbnailThresholdFactor; + + if (imageWidth <= thumbnailWidthThreshold && + imageHeight <= thumbnailHeightThreshold) { + // Do not create a thumbnail. + return Promise.resolve(null); + } + + const thumbnailDimensions = getThumbnailDimensions(imageWidth, imageHeight); + + return new Promise((resolve, reject) => { + const thumbnailImage = new Image(); + let srcWidth = imageWidth; + let srcHeight = imageHeight; + let destWidth, destHeight; + + thumbnailImage.onload = function() { + destWidth = Math.round(srcWidth * maxResizeScaleFactor); + destHeight = Math.round(srcHeight * maxResizeScaleFactor); + if (destWidth <= thumbnailDimensions.scaledX || destHeight <= thumbnailDimensions.scaledY) { + srcWidth = Math.round(srcWidth * (thumbnailDimensions.width / thumbnailDimensions.scaledX)); + srcHeight = Math.round(srcHeight * (thumbnailDimensions.height / thumbnailDimensions.scaledY)); + destWidth = thumbnailDimensions.width; + destHeight = thumbnailDimensions.height; + } + + const thumbnailCanvas = document.createElement("canvas"); + thumbnailCanvas.width = destWidth; + thumbnailCanvas.height = destHeight; + const ctx = thumbnailCanvas.getContext("2d"); + ctx.imageSmoothingEnabled = false; + + ctx.drawImage( + thumbnailImage, + 0, 0, srcWidth, srcHeight, + 0, 0, destWidth, destHeight); + + if (thumbnailCanvas.width <= thumbnailDimensions.width || + thumbnailCanvas.height <= thumbnailDimensions.height) { + if (urlOrBlob === "blob") { + thumbnailCanvas.toBlob((blob) => { + resolve(blob); + }); + } else { + resolve(thumbnailCanvas.toDataURL("image/png")); + } + return; + } + + srcWidth = destWidth; + srcHeight = destHeight; + thumbnailImage.src = thumbnailCanvas.toDataURL(); + }; + thumbnailImage.src = dataUrl; + }); +} + +function createThumbnailUrl(shot) { + const image = shot.getClip(shot.clipNames()[0]).image; + if (!image.url) { + return Promise.resolve(null); + } + return createThumbnail( + image.url, image.dimensions.x, image.dimensions.y, "dataurl"); +} + +function createThumbnailBlobFromPromise(shot, blobToUrlPromise) { + return blobToUrlPromise.then(dataUrl => { + const image = shot.getClip(shot.clipNames()[0]).image; + return createThumbnail( + dataUrl, image.dimensions.x, image.dimensions.y, "blob"); + }); +} + +if (typeof exports !== "undefined") { + exports.getThumbnailDimensions = getThumbnailDimensions; + exports.createThumbnailUrl = createThumbnailUrl; + exports.createThumbnailBlobFromPromise = createThumbnailBlobFromPromise; +} + +return exports; +})(); +null; + |