feat(canvas): add image upscaling and super resolution features with UI enhancements

This commit is contained in:
HouYunFei
2026-06-03 16:05:35 +08:00
parent 76a4e7c4a2
commit ceb7605a1e
5 changed files with 218 additions and 4 deletions
@@ -14,6 +14,15 @@ export type ImageAngleTransform = {
wideAngle: boolean;
};
export type ImageUpscaleAlgorithm = "nearest" | "bilinear" | "high";
export const MAX_UPSCALE_LONG_EDGE = 4096;
export type ImageUpscaleParams = {
targetLongEdge: number;
algorithm: ImageUpscaleAlgorithm;
};
export async function cropDataUrl(dataUrl: string, crop?: ImageCropRect) {
const image = await loadImage(dataUrl);
if (crop) {
@@ -65,6 +74,19 @@ export async function transformAngleDataUrl(dataUrl: string, params: ImageAngleT
return canvas.toDataURL("image/png");
}
export async function upscaleDataUrl(dataUrl: string, params: ImageUpscaleParams) {
const image = await loadImage(dataUrl);
const { width, height } = resolveUpscaleSize(image.width, image.height, params.targetLongEdge);
return params.algorithm === "high" ? drawStepUpscale(image, width, height) : drawResize(image, image.width, image.height, width, height, params.algorithm);
}
export function resolveUpscaleSize(width: number, height: number, targetLongEdge: number) {
const longEdge = Math.max(1, width, height);
const target = Math.min(MAX_UPSCALE_LONG_EDGE, Math.max(1, Math.round(targetLongEdge)));
const scale = target / longEdge;
return { width: Math.max(1, Math.round(width * scale)), height: Math.max(1, Math.round(height * scale)) };
}
function drawCrop(image: HTMLImageElement, sx: number, sy: number, sw: number, sh: number) {
const canvas = document.createElement("canvas");
canvas.width = Math.max(1, sw);
@@ -75,6 +97,39 @@ function drawCrop(image: HTMLImageElement, sx: number, sy: number, sw: number, s
return canvas.toDataURL("image/png");
}
function drawStepUpscale(image: HTMLImageElement, width: number, height: number) {
let source: CanvasImageSource = image;
let sourceWidth = image.width;
let sourceHeight = image.height;
while (sourceWidth * 2 < width && sourceHeight * 2 < height) {
const nextWidth = sourceWidth * 2;
const nextHeight = sourceHeight * 2;
const next = drawResizeCanvas(source, sourceWidth, sourceHeight, nextWidth, nextHeight, "high");
source = next;
sourceWidth = nextWidth;
sourceHeight = nextHeight;
}
return drawResize(source, sourceWidth, sourceHeight, width, height, "high");
}
function drawResize(source: CanvasImageSource, sourceWidth: number, sourceHeight: number, width: number, height: number, algorithm: ImageUpscaleAlgorithm) {
return drawResizeCanvas(source, sourceWidth, sourceHeight, width, height, algorithm).toDataURL("image/png");
}
function drawResizeCanvas(source: CanvasImageSource, sourceWidth: number, sourceHeight: number, width: number, height: number, algorithm: ImageUpscaleAlgorithm) {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const context = canvas.getContext("2d");
if (!context) return canvas;
context.imageSmoothingEnabled = algorithm !== "nearest";
context.imageSmoothingQuality = algorithm === "bilinear" ? "medium" : "high";
context.drawImage(source, 0, 0, sourceWidth, sourceHeight, 0, 0, width, height);
return canvas;
}
function loadImage(dataUrl: string) {
return new Promise<HTMLImageElement>((resolve) => {
const image = new Image();