loading/imageLoader.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)),
  };
}

/**
 * Loads an image file as an ImageBitmap.
 *
 * @param {Blob} file Image file or blob.
 * @returns {Promise<ImageBitmap>} Loaded image bitmap.
 */
export async function loadImageFile(file) {
  return createImageBitmap(file);
}

/**
 * Loads a downscaled image preview.
 *
 * @param {Blob} file Image file or blob.
 * @param {number} maxEdge Maximum preview edge in pixels.
 * @returns {Promise<{canvas: ImageBitmap, width: number, height: number}>} Preview bitmap plus original dimensions.
 */
export async function loadImagePreview(file, maxEdge) {
  const bitmap = await createImageBitmap(file);
  const originalWidth = bitmap.width;
  const originalHeight = bitmap.height;
  const { width, height } = computeTargetSize(originalWidth, originalHeight, maxEdge);
  if (width === originalWidth && height === originalHeight) {
    return { canvas: bitmap, width: originalWidth, height: originalHeight };
  }
  const canvas = new OffscreenCanvas(width, height);
  const ctx = canvas.getContext("2d");
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = "high";
  ctx.drawImage(bitmap, 0, 0, width, height);
  bitmap.close();
  return {
    canvas: canvas.transferToImageBitmap(),
    width: originalWidth,
    height: originalHeight,
  };
}