92 lines
2.8 KiB
TypeScript
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)),
|
|
};
|
|
}
|