mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			190 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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 => {
 | 
						|
      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;
 |