summaryrefslogtreecommitdiffstats
path: root/browser/extensions/screenshots/build/thumbnailGenerator.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/screenshots/build/thumbnailGenerator.js')
-rw-r--r--browser/extensions/screenshots/build/thumbnailGenerator.js190
1 files changed, 190 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..c80ccb6bac
--- /dev/null
+++ b/browser/extensions/screenshots/build/thumbnailGenerator.js
@@ -0,0 +1,190 @@
+/* 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.2;
+ 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;