loading/downscaleCanvas.js

function computeTargetSize(sourceWidth, sourceHeight, maxEdge) {
  const safeMaxEdge = Math.max(1, Math.round(maxEdge || 1));
  const sourceMaxEdge = Math.max(sourceWidth, sourceHeight);
  if (sourceMaxEdge <= safeMaxEdge) {
    return {
      width: Math.max(1, Math.round(sourceWidth)),
      height: Math.max(1, Math.round(sourceHeight)),
    };
  }
  const scale = safeMaxEdge / sourceMaxEdge;
  return {
    width: Math.max(1, Math.round(sourceWidth * scale)),
    height: Math.max(1, Math.round(sourceHeight * scale)),
  };
}

/**
 * Synchronously downscales a canvas image source on the main thread.
 *
 * @param {CanvasImageSource} source Source image/canvas/bitmap.
 * @param {number} maxEdge Maximum output edge in pixels.
 * @returns {HTMLCanvasElement|CanvasImageSource|null} Downscaled canvas, original source, or null.
 */
export function downscaleCanvasToMaxEdgeSync(source, maxEdge) {
  if (!source?.width || !source?.height) return null;

  const { width: targetWidth, height: targetHeight } = computeTargetSize(
    source.width,
    source.height,
    maxEdge
  );
  if (targetWidth === source.width && targetHeight === source.height) {
    return source;
  }

  const canvas = document.createElement("canvas");
  canvas.width = targetWidth;
  canvas.height = targetHeight;
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = "high";
  ctx.drawImage(source, 0, 0, targetWidth, targetHeight);
  return canvas;
}