ai-chatbot/lib/video-canvas/video-crop.ts

92 lines
2.8 KiB
TypeScript

export type VideoCropRect = {
x: number;
y: number;
width: number;
height: number;
};
export type VideoCropHandle = "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
export const DEFAULT_VIDEO_CROP_RECT: VideoCropRect = {
x: 0.1,
y: 0.1,
width: 0.8,
height: 0.8,
};
const MIN_VIDEO_CROP_SIZE = 0.05;
function clampUnit(value: number) {
return Math.min(1, Math.max(0, value));
}
function roundUnit(value: number) {
return Number(clampUnit(value).toFixed(6));
}
export function normalizeVideoCropRect(value: unknown): VideoCropRect {
if (!value || typeof value !== "object") return DEFAULT_VIDEO_CROP_RECT;
const candidate = value as Partial<VideoCropRect>;
const x = Number(candidate.x);
const y = Number(candidate.y);
const width = Number(candidate.width);
const height = Number(candidate.height);
if (![x, y, width, height].every(Number.isFinite)) return DEFAULT_VIDEO_CROP_RECT;
const normalizedX = clampUnit(x);
const normalizedY = clampUnit(y);
return {
x: roundUnit(Math.min(normalizedX, 1 - MIN_VIDEO_CROP_SIZE)),
y: roundUnit(Math.min(normalizedY, 1 - MIN_VIDEO_CROP_SIZE)),
width: roundUnit(Math.max(MIN_VIDEO_CROP_SIZE, Math.min(width, 1 - normalizedX))),
height: roundUnit(Math.max(MIN_VIDEO_CROP_SIZE, Math.min(height, 1 - normalizedY))),
};
}
export function moveVideoCropRect(rect: VideoCropRect, deltaX: number, deltaY: number) {
const normalized = normalizeVideoCropRect(rect);
return {
...normalized,
x: roundUnit(Math.max(0, Math.min(1 - normalized.width, normalized.x + deltaX))),
y: roundUnit(Math.max(0, Math.min(1 - normalized.height, normalized.y + deltaY))),
};
}
export function resizeVideoCropRect(
rect: VideoCropRect,
handle: VideoCropHandle,
pointX: number,
pointY: number,
) {
const normalized = normalizeVideoCropRect(rect);
let left = normalized.x;
let top = normalized.y;
let right = normalized.x + normalized.width;
let bottom = normalized.y + normalized.height;
const x = clampUnit(pointX);
const y = clampUnit(pointY);
if (handle.includes("w")) left = Math.min(x, right - MIN_VIDEO_CROP_SIZE);
if (handle.includes("e")) right = Math.max(x, left + MIN_VIDEO_CROP_SIZE);
if (handle.includes("n")) top = Math.min(y, bottom - MIN_VIDEO_CROP_SIZE);
if (handle.includes("s")) bottom = Math.max(y, top + MIN_VIDEO_CROP_SIZE);
return normalizeVideoCropRect({
x: left,
y: top,
width: right - left,
height: bottom - top,
});
}
export function getVideoCropOutputDimensions(
rect: VideoCropRect,
sourceWidth: number,
sourceHeight: number,
) {
const normalized = normalizeVideoCropRect(rect);
return {
width: Math.max(2, Math.round(Math.max(2, sourceWidth) * normalized.width)),
height: Math.max(2, Math.round(Math.max(2, sourceHeight) * normalized.height)),
};
}