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.js154
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;
+