OpenCut/apps/web/src/masks/definitions/ellipse.ts

82 lines
2.0 KiB
TypeScript

import type { MaskDefinition } from "@/masks/types";
import {
BOX_LIKE_MASK_PARAMS,
buildBoxMaskInteraction,
computeBoxMaskParamUpdate,
getBoxLikeGeometry,
getDefaultSquareMaskParams,
getStrokeOffset,
} from "./box-like";
export const ellipseMaskDefinition: MaskDefinition<"ellipse"> = {
type: "ellipse",
name: "Ellipse",
features: {
hasPosition: true,
hasRotation: true,
sizeMode: "width-height",
},
params: BOX_LIKE_MASK_PARAMS,
interaction: buildBoxMaskInteraction({
sizeMode: "width-height",
buildOverlayPath({ width, height }) {
const rx = Math.max((width - 1) / 2, 0);
const ry = Math.max((height - 1) / 2, 0);
const cx = width / 2;
const cy = height / 2;
return `M ${cx},${cy - ry} A ${rx},${ry} 0 1,1 ${cx},${cy + ry} A ${rx},${ry} 0 1,1 ${cx},${cy - ry} Z`;
},
}),
buildDefault(context) {
return {
type: "ellipse",
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 });
const path = new Path2D();
path.ellipse(
centerX,
centerY,
maskWidth / 2,
maskHeight / 2,
rotationRad,
0,
Math.PI * 2,
);
return path;
},
},
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,
});
const path = new Path2D();
path.ellipse(
centerX,
centerY,
Math.max(1, maskWidth / 2 + offset),
Math.max(1, maskHeight / 2 + offset),
rotationRad,
0,
Math.PI * 2,
);
return path;
},
},
},
};