OpenCut/apps/web/src/masks/builtin/definitions/rectangle.ts

104 lines
2.4 KiB
TypeScript

import type { MaskDefinition } from "@/masks/types";
import {
BOX_LIKE_MASK_PARAMS,
buildBoxMaskInteraction,
computeBoxMaskParamUpdate,
getBoxLikeGeometry,
getDefaultSquareMaskParams,
getStrokeOffset,
rotatePoint,
} from "../box-like";
function buildRectanglePath({
centerX,
centerY,
halfWidth,
halfHeight,
rotationRad,
}: {
centerX: number;
centerY: number;
halfWidth: number;
halfHeight: number;
rotationRad: number;
}): Path2D {
const corners = [
{ x: centerX - halfWidth, y: centerY - halfHeight },
{ x: centerX + halfWidth, y: centerY - halfHeight },
{ x: centerX + halfWidth, y: centerY + halfHeight },
{ x: centerX - halfWidth, y: centerY + halfHeight },
].map((point) =>
rotatePoint({
...point,
centerX,
centerY,
rotationRad,
}),
);
const path = new Path2D();
path.moveTo(corners[0].x, corners[0].y);
for (const corner of corners.slice(1)) {
path.lineTo(corner.x, corner.y);
}
path.closePath();
return path;
}
export const rectangleMaskDefinition: MaskDefinition<"rectangle"> = {
type: "rectangle",
name: "Rectangle",
features: {
hasPosition: true,
hasRotation: true,
sizeMode: "width-height",
},
params: BOX_LIKE_MASK_PARAMS,
interaction: buildBoxMaskInteraction({
sizeMode: "width-height",
}),
buildDefault(context) {
return {
type: "rectangle",
params: getDefaultSquareMaskParams(context),
};
},
computeParamUpdate: computeBoxMaskParamUpdate,
renderer: {
body: {
kind: "fillPath",
buildPath({ resolvedParams, width, height }) {
const params = resolvedParams;
const { centerX, centerY, maskWidth, maskHeight, rotationRad } =
getBoxLikeGeometry({ params, width, height });
return buildRectanglePath({
centerX,
centerY,
halfWidth: maskWidth / 2,
halfHeight: maskHeight / 2,
rotationRad,
});
},
},
stroke: {
kind: "strokeFromPath",
buildStrokePath({ resolvedParams, width, height }) {
const params = resolvedParams;
const { centerX, centerY, maskWidth, maskHeight, rotationRad } =
getBoxLikeGeometry({ params, width, height });
const offset = getStrokeOffset({
strokeAlign: params.strokeAlign,
strokeWidth: params.strokeWidth,
});
return buildRectanglePath({
centerX,
centerY,
halfWidth: Math.max(1, maskWidth / 2 + offset),
halfHeight: Math.max(1, maskHeight / 2 + offset),
rotationRad,
});
},
},
},
};