Files
NianAIGC/lib/client/seedream-viewport.ts
2026-09-11 15:33:18 +08:00

40 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export type CanvasSize = { width: number; height: number };
export type CanvasView = { zoom: number; x: number; y: number };
export type CanvasPosition = { x: number; y: number };
export const MIN_CANVAS_ZOOM = 0.25;
export const MAX_CANVAS_ZOOM = 8;
export const FIT_CANVAS_VIEW: CanvasView = { zoom: 1, x: 0, y: 0 };
export function fitCanvasImage(image: CanvasSize, viewport: CanvasSize): CanvasSize {
if (image.width <= 0 || image.height <= 0 || viewport.width <= 0 || viewport.height <= 0) return { width: 0, height: 0 };
const scale = Math.min(Math.max(1, viewport.width - 20) / image.width, Math.max(1, viewport.height - 20) / image.height);
return { width: image.width * scale, height: image.height * scale };
}
export function constrainCanvasView(view: CanvasView, fittedImage: CanvasSize, viewport: CanvasSize): CanvasView {
const zoom = Math.max(MIN_CANVAS_ZOOM, Math.min(MAX_CANVAS_ZOOM, Number.isFinite(view.zoom) ? view.zoom : 1));
const maxX = Math.max(0, (fittedImage.width * zoom - viewport.width) / 2);
const maxY = Math.max(0, (fittedImage.height * zoom - viewport.height) / 2);
return {
zoom,
x: Math.max(-maxX, Math.min(maxX, Number.isFinite(view.x) ? view.x : 0)),
y: Math.max(-maxY, Math.min(maxY, Number.isFinite(view.y) ? view.y : 0))
};
}
// The anchor is relative to the viewport center. Preserve the image point
// under the cursor when zooming, except where an image edge limits panning.
export function zoomCanvasAt(view: CanvasView, requestedZoom: number, anchor: CanvasPosition, fittedImage: CanvasSize, viewport: CanvasSize): CanvasView {
const zoom = Math.max(MIN_CANVAS_ZOOM, Math.min(MAX_CANVAS_ZOOM, requestedZoom));
const factor = zoom / view.zoom;
return constrainCanvasView({ zoom, x: anchor.x - (anchor.x - view.x) * factor, y: anchor.y - (anchor.y - view.y) * factor }, fittedImage, viewport);
}
// Use the *transformed image bounds*, never the clipped viewport bounds.
// Persisted annotations and provider prompts remain in the 0–1000 space.
export function canvasAnnotationPoint(point: CanvasPosition, bounds: CanvasSize & { left: number; top: number }): CanvasPosition | null {
if (bounds.width <= 0 || bounds.height <= 0) return null;
const clamp = (value: number) => Math.max(0, Math.min(1000, Math.round(value)));
return { x: clamp((point.x - bounds.left) / bounds.width * 1000), y: clamp((point.y - bounds.top) / bounds.height * 1000) };
}