feat: Add canvas style customization
- 왜곡 영역 원 레벨 스타일, 중심점, 포인트 핸들, 영역 외곽선 등 캔버스 스타일을 커스터마이징할 수 있도록 `EditorCanvasStyle` 타입을 추가했습니다. - `DistortionEditorProps`에 `canvasStyle` prop을 추가하여 외부에서 캔버스 스타일을 전달받을 수 있도록 했습니다. - `EditorCanvas` 컴포넌트에서 `useMemo`를 사용하여 기본 스타일과 사용자 정의 스타일을 병합하고, 이를 렌더링에 반영하도록 수정했습니다.
This commit is contained in:
parent
d621d5b691
commit
0c3c0b606e
81
dist/index.d.mts
vendored
81
dist/index.d.mts
vendored
@ -149,6 +149,85 @@ interface EditorState {
|
|||||||
/** 드래그 중인 포인트 인덱스 (0-3) */
|
/** 드래그 중인 포인트 인덱스 (0-3) */
|
||||||
draggingPointIndex: number | null;
|
draggingPointIndex: number | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 왜곡 영역 원 레벨 스타일
|
||||||
|
*/
|
||||||
|
interface CircleLevelStyle {
|
||||||
|
/** 반지름 (0.0 - 1.0, UV 좌표) */
|
||||||
|
radius: number;
|
||||||
|
/** 투명도 (0.0 - 1.0) */
|
||||||
|
opacity: number;
|
||||||
|
/** 선 두께 (픽셀) */
|
||||||
|
lineWidth: number;
|
||||||
|
/** 선 색상 (CSS color) */
|
||||||
|
color?: string;
|
||||||
|
/** 대시 패턴 [dash, gap] */
|
||||||
|
dashPattern?: [number, number];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 중심점 스타일
|
||||||
|
*/
|
||||||
|
interface CenterPointStyle {
|
||||||
|
/** 반지름 (픽셀) */
|
||||||
|
radius?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 포인트 핸들 스타일
|
||||||
|
*/
|
||||||
|
interface PointHandleStyle {
|
||||||
|
/** 핸들 크기 (픽셀) */
|
||||||
|
size?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
/** 레이블 색상 */
|
||||||
|
labelColor?: string;
|
||||||
|
/** 레이블 폰트 크기 */
|
||||||
|
labelFontSize?: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 영역 외곽선 스타일
|
||||||
|
*/
|
||||||
|
interface AreaOutlineStyle {
|
||||||
|
/** 선택된 영역 색상 */
|
||||||
|
selectedColor?: string;
|
||||||
|
/** 선택되지 않은 영역 색상 */
|
||||||
|
unselectedColor?: string;
|
||||||
|
/** 선택된 영역 선 두께 */
|
||||||
|
selectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 선 두께 */
|
||||||
|
unselectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 대시 패턴 */
|
||||||
|
unselectedDashPattern?: [number, number];
|
||||||
|
/** 선택된 영역 배경 채우기 색상 */
|
||||||
|
selectedFillColor?: string;
|
||||||
|
/** 선택되지 않은 영역 배경 채우기 색상 */
|
||||||
|
unselectedFillColor?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
interface EditorCanvasStyle {
|
||||||
|
/** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */
|
||||||
|
circleLevels?: CircleLevelStyle[];
|
||||||
|
/** 왜곡 영역 내부 채우기 색상 */
|
||||||
|
circleFillColor?: string;
|
||||||
|
/** 중심점 스타일 */
|
||||||
|
centerPoint?: CenterPointStyle;
|
||||||
|
/** 포인트 핸들 스타일 */
|
||||||
|
pointHandle?: PointHandleStyle;
|
||||||
|
/** 영역 외곽선 스타일 */
|
||||||
|
areaOutline?: AreaOutlineStyle;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 에디터 Props
|
* 에디터 Props
|
||||||
*/
|
*/
|
||||||
@ -165,6 +244,8 @@ interface DistortionEditorProps {
|
|||||||
width?: number;
|
width?: number;
|
||||||
/** 캔버스 높이 */
|
/** 캔버스 높이 */
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
canvasStyle?: EditorCanvasStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const DistortionEditor: React.FC<DistortionEditorProps>;
|
declare const DistortionEditor: React.FC<DistortionEditorProps>;
|
||||||
|
|||||||
81
dist/index.d.ts
vendored
81
dist/index.d.ts
vendored
@ -149,6 +149,85 @@ interface EditorState {
|
|||||||
/** 드래그 중인 포인트 인덱스 (0-3) */
|
/** 드래그 중인 포인트 인덱스 (0-3) */
|
||||||
draggingPointIndex: number | null;
|
draggingPointIndex: number | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 왜곡 영역 원 레벨 스타일
|
||||||
|
*/
|
||||||
|
interface CircleLevelStyle {
|
||||||
|
/** 반지름 (0.0 - 1.0, UV 좌표) */
|
||||||
|
radius: number;
|
||||||
|
/** 투명도 (0.0 - 1.0) */
|
||||||
|
opacity: number;
|
||||||
|
/** 선 두께 (픽셀) */
|
||||||
|
lineWidth: number;
|
||||||
|
/** 선 색상 (CSS color) */
|
||||||
|
color?: string;
|
||||||
|
/** 대시 패턴 [dash, gap] */
|
||||||
|
dashPattern?: [number, number];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 중심점 스타일
|
||||||
|
*/
|
||||||
|
interface CenterPointStyle {
|
||||||
|
/** 반지름 (픽셀) */
|
||||||
|
radius?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 포인트 핸들 스타일
|
||||||
|
*/
|
||||||
|
interface PointHandleStyle {
|
||||||
|
/** 핸들 크기 (픽셀) */
|
||||||
|
size?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
/** 레이블 색상 */
|
||||||
|
labelColor?: string;
|
||||||
|
/** 레이블 폰트 크기 */
|
||||||
|
labelFontSize?: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 영역 외곽선 스타일
|
||||||
|
*/
|
||||||
|
interface AreaOutlineStyle {
|
||||||
|
/** 선택된 영역 색상 */
|
||||||
|
selectedColor?: string;
|
||||||
|
/** 선택되지 않은 영역 색상 */
|
||||||
|
unselectedColor?: string;
|
||||||
|
/** 선택된 영역 선 두께 */
|
||||||
|
selectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 선 두께 */
|
||||||
|
unselectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 대시 패턴 */
|
||||||
|
unselectedDashPattern?: [number, number];
|
||||||
|
/** 선택된 영역 배경 채우기 색상 */
|
||||||
|
selectedFillColor?: string;
|
||||||
|
/** 선택되지 않은 영역 배경 채우기 색상 */
|
||||||
|
unselectedFillColor?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
interface EditorCanvasStyle {
|
||||||
|
/** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */
|
||||||
|
circleLevels?: CircleLevelStyle[];
|
||||||
|
/** 왜곡 영역 내부 채우기 색상 */
|
||||||
|
circleFillColor?: string;
|
||||||
|
/** 중심점 스타일 */
|
||||||
|
centerPoint?: CenterPointStyle;
|
||||||
|
/** 포인트 핸들 스타일 */
|
||||||
|
pointHandle?: PointHandleStyle;
|
||||||
|
/** 영역 외곽선 스타일 */
|
||||||
|
areaOutline?: AreaOutlineStyle;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 에디터 Props
|
* 에디터 Props
|
||||||
*/
|
*/
|
||||||
@ -165,6 +244,8 @@ interface DistortionEditorProps {
|
|||||||
width?: number;
|
width?: number;
|
||||||
/** 캔버스 높이 */
|
/** 캔버스 높이 */
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
canvasStyle?: EditorCanvasStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const DistortionEditor: React.FC<DistortionEditorProps>;
|
declare const DistortionEditor: React.FC<DistortionEditorProps>;
|
||||||
|
|||||||
262
dist/index.js
vendored
262
dist/index.js
vendored
@ -583,6 +583,66 @@ var useDistortionEditor = (initialAreas = []) => {
|
|||||||
|
|
||||||
// src/editor/components/EditorCanvas.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
var import_react4 = require("react");
|
var import_react4 = require("react");
|
||||||
|
|
||||||
|
// src/editor/constants.ts
|
||||||
|
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
||||||
|
// 3단계 원 스타일 (외부 -> 내부)
|
||||||
|
circleLevels: [
|
||||||
|
{
|
||||||
|
radius: 0.5,
|
||||||
|
opacity: 0.3,
|
||||||
|
lineWidth: 2,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.33,
|
||||||
|
opacity: 0.6,
|
||||||
|
lineWidth: 2.5,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.167,
|
||||||
|
opacity: 0.9,
|
||||||
|
lineWidth: 3,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 원 내부 채우기
|
||||||
|
circleFillColor: "rgba(255, 200, 0, 0.08)",
|
||||||
|
// 중심점
|
||||||
|
centerPoint: {
|
||||||
|
radius: 5,
|
||||||
|
fillColor: "rgba(255, 200, 0, 1)",
|
||||||
|
strokeColor: "rgba(255, 255, 255, 0.8)",
|
||||||
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
// 포인트 핸들
|
||||||
|
pointHandle: {
|
||||||
|
size: 16,
|
||||||
|
fillColor: "#00aaff",
|
||||||
|
strokeColor: "white",
|
||||||
|
strokeWidth: 2,
|
||||||
|
labelColor: "#00aaff",
|
||||||
|
labelFontSize: 11
|
||||||
|
},
|
||||||
|
// 영역 외곽선
|
||||||
|
areaOutline: {
|
||||||
|
selectedColor: "#00aaff",
|
||||||
|
unselectedColor: "#888",
|
||||||
|
selectedWidth: 2,
|
||||||
|
unselectedWidth: 1,
|
||||||
|
unselectedDashPattern: [5, 5],
|
||||||
|
selectedFillColor: "rgba(0, 170, 255, 0.08)",
|
||||||
|
// 선택된 영역 배경 (연한 파란색)
|
||||||
|
unselectedFillColor: "rgba(136, 136, 136, 0.03)"
|
||||||
|
// 선택 안된 영역 배경 (연한 회색)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/editor/components/EditorCanvas.tsx
|
||||||
var import_jsx_runtime2 = require("react/jsx-runtime");
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
||||||
var EditorCanvas = ({
|
var EditorCanvas = ({
|
||||||
areas,
|
areas,
|
||||||
@ -594,12 +654,30 @@ var EditorCanvas = ({
|
|||||||
onUpdateArea,
|
onUpdateArea,
|
||||||
draggingPointIndex,
|
draggingPointIndex,
|
||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging
|
onStopDragging,
|
||||||
|
style: customStyle
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = (0, import_react4.useRef)(null);
|
const containerRef = (0, import_react4.useRef)(null);
|
||||||
const [canvasSize, setCanvasSize] = (0, import_react4.useState)({ width: 0, height: 0 });
|
const [canvasSize, setCanvasSize] = (0, import_react4.useState)({ width: 0, height: 0 });
|
||||||
const [isDraggingArea, setIsDraggingArea] = (0, import_react4.useState)(false);
|
const [isDraggingArea, setIsDraggingArea] = (0, import_react4.useState)(false);
|
||||||
const [dragStartPos, setDragStartPos] = (0, import_react4.useState)(null);
|
const [dragStartPos, setDragStartPos] = (0, import_react4.useState)(null);
|
||||||
|
const editorStyle = (0, import_react4.useMemo)(() => ({
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
...customStyle,
|
||||||
|
circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,
|
||||||
|
centerPoint: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,
|
||||||
|
...customStyle?.centerPoint
|
||||||
|
},
|
||||||
|
pointHandle: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,
|
||||||
|
...customStyle?.pointHandle
|
||||||
|
},
|
||||||
|
areaOutline: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,
|
||||||
|
...customStyle?.areaOutline
|
||||||
|
}
|
||||||
|
}), [customStyle]);
|
||||||
(0, import_react4.useEffect)(() => {
|
(0, import_react4.useEffect)(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
@ -690,63 +768,57 @@ var EditorCanvas = ({
|
|||||||
y: posY * canvasHeight
|
y: posY * canvasHeight
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const drawDistortionCircle = (ctx, points, canvasWidth, canvasHeight) => {
|
const drawDistortionCircle = (0, import_react4.useCallback)((ctx, points, canvasWidth, canvasHeight) => {
|
||||||
const segments = 128;
|
const segments = 128;
|
||||||
const centerU = 0.5;
|
const centerU = 0.5;
|
||||||
const centerV = 0.5;
|
const centerV = 0.5;
|
||||||
const maxRadius = 0.5;
|
const circleLevels = editorStyle.circleLevels || [];
|
||||||
const circlePoints = [];
|
circleLevels.forEach((level, index) => {
|
||||||
for (let i = 0; i <= segments; i++) {
|
const levelPoints = [];
|
||||||
const theta = i / segments * 2 * Math.PI;
|
|
||||||
const u = centerU - maxRadius * Math.sin(theta);
|
|
||||||
const v = centerV + maxRadius * Math.cos(theta);
|
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
|
||||||
circlePoints.push(pixelPos);
|
|
||||||
}
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(circlePoints[0].x, circlePoints[0].y);
|
|
||||||
for (let i = 1; i < circlePoints.length; i++) {
|
|
||||||
ctx.lineTo(circlePoints[i].x, circlePoints[i].y);
|
|
||||||
}
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.strokeStyle = "rgba(255, 200, 0, 0.9)";
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.setLineDash([8, 4]);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
ctx.fillStyle = "rgba(255, 200, 0, 0.12)";
|
|
||||||
ctx.fill();
|
|
||||||
for (const r of [0.25, 0.375]) {
|
|
||||||
const gradientPoints = [];
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
for (let i = 0; i <= segments; i++) {
|
||||||
const theta = i / segments * 2 * Math.PI;
|
const theta = i / segments * 2 * Math.PI;
|
||||||
const u = centerU - r * Math.sin(theta);
|
const u = centerU - level.radius * Math.sin(theta);
|
||||||
const v = centerV + r * Math.cos(theta);
|
const v = centerV + level.radius * Math.cos(theta);
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
||||||
gradientPoints.push(pixelPos);
|
levelPoints.push(pixelPos);
|
||||||
}
|
}
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y);
|
ctx.moveTo(levelPoints[0].x, levelPoints[0].y);
|
||||||
for (let i = 1; i < gradientPoints.length; i++) {
|
for (let i = 1; i < levelPoints.length; i++) {
|
||||||
ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y);
|
ctx.lineTo(levelPoints[i].x, levelPoints[i].y);
|
||||||
}
|
}
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
const alpha = r / maxRadius;
|
const baseColor = level.color || "rgba(255, 200, 0, 1)";
|
||||||
ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`;
|
const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => {
|
||||||
ctx.lineWidth = 1;
|
const parts = rgb.split(",").map((p) => p.trim());
|
||||||
ctx.setLineDash([3, 3]);
|
return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;
|
||||||
|
});
|
||||||
|
ctx.strokeStyle = colorWithOpacity;
|
||||||
|
ctx.lineWidth = level.lineWidth;
|
||||||
|
if (level.dashPattern) {
|
||||||
|
ctx.setLineDash(level.dashPattern);
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
}
|
if (index === 0 && editorStyle.circleFillColor) {
|
||||||
|
ctx.fillStyle = editorStyle.circleFillColor;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const centerPointStyle = editorStyle.centerPoint || {};
|
||||||
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI);
|
ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = "rgba(255, 200, 0, 1)";
|
if (centerPointStyle.fillColor) {
|
||||||
ctx.fill();
|
ctx.fillStyle = centerPointStyle.fillColor;
|
||||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
|
ctx.fill();
|
||||||
ctx.lineWidth = 2;
|
}
|
||||||
ctx.stroke();
|
if (centerPointStyle.strokeColor) {
|
||||||
};
|
ctx.strokeStyle = centerPointStyle.strokeColor;
|
||||||
|
ctx.lineWidth = centerPointStyle.strokeWidth || 2;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}, [editorStyle]);
|
||||||
const getCursorStyle = () => {
|
const getCursorStyle = () => {
|
||||||
if (draggingPointIndex !== null) return "grabbing";
|
if (draggingPointIndex !== null) return "grabbing";
|
||||||
if (isDraggingArea) return "grabbing";
|
if (isDraggingArea) return "grabbing";
|
||||||
@ -761,7 +833,7 @@ var EditorCanvas = ({
|
|||||||
onMouseDown: handleCanvasMouseDown,
|
onMouseDown: handleCanvasMouseDown,
|
||||||
onMouseMove: handleMouseMove,
|
onMouseMove: handleMouseMove,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas, width, height }),
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -776,14 +848,15 @@ var EditorCanvas = ({
|
|||||||
children: areas.map((area) => {
|
children: areas.map((area) => {
|
||||||
const isSelected = area.id === selectedAreaId;
|
const isSelected = area.id === selectedAreaId;
|
||||||
const points = area.basePoints;
|
const points = area.basePoints;
|
||||||
|
const outlineStyle = editorStyle.areaOutline || {};
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
"polygon",
|
"polygon",
|
||||||
{
|
{
|
||||||
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
||||||
fill: "none",
|
fill: isSelected ? outlineStyle.selectedFillColor || "rgba(0, 170, 255, 0.08)" : outlineStyle.unselectedFillColor || "rgba(136, 136, 136, 0.03)",
|
||||||
stroke: isSelected ? "#00aaff" : "#888",
|
stroke: isSelected ? outlineStyle.selectedColor || "#00aaff" : outlineStyle.unselectedColor || "#888",
|
||||||
strokeWidth: isSelected ? 2 : 1,
|
strokeWidth: isSelected ? outlineStyle.selectedWidth || 2 : outlineStyle.unselectedWidth || 1,
|
||||||
strokeDasharray: isSelected ? "0" : "5,5",
|
strokeDasharray: isSelected ? "0" : outlineStyle.unselectedDashPattern?.join(",") || "5,5",
|
||||||
opacity: isSelected ? 1 : 0.5
|
opacity: isSelected ? 1 : 0.5
|
||||||
}
|
}
|
||||||
) }, area.id);
|
) }, area.id);
|
||||||
@ -814,48 +887,51 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && selectedArea.basePoints.map((point, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
selectedArea && selectedArea.basePoints.map((point, index) => {
|
||||||
"div",
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
{
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
"div",
|
||||||
style: {
|
{
|
||||||
position: "absolute",
|
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
||||||
left: `${point.x * 100}%`,
|
style: {
|
||||||
top: `${point.y * 100}%`,
|
position: "absolute",
|
||||||
transform: "translate(-50%, -50%)",
|
left: `${point.x * 100}%`,
|
||||||
width: 16,
|
top: `${point.y * 100}%`,
|
||||||
height: 16,
|
transform: "translate(-50%, -50%)",
|
||||||
borderRadius: "50%",
|
width: handleStyle.size || 16,
|
||||||
backgroundColor: "#00aaff",
|
height: handleStyle.size || 16,
|
||||||
border: "2px solid white",
|
borderRadius: "50%",
|
||||||
cursor: "grab",
|
backgroundColor: handleStyle.fillColor || "#00aaff",
|
||||||
pointerEvents: "auto",
|
border: `${handleStyle.strokeWidth || 2}px solid ${handleStyle.strokeColor || "white"}`,
|
||||||
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
|
cursor: "grab",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
|
||||||
|
},
|
||||||
|
onMouseDown: handleMouseDown(index),
|
||||||
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
position: "absolute",
|
||||||
|
top: -24,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
fontSize: handleStyle.labelFontSize || 11,
|
||||||
|
color: handleStyle.labelColor || "#00aaff",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textShadow: "1px 1px 2px rgba(0,0,0,0.8)",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
"P",
|
||||||
|
index + 1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onMouseDown: handleMouseDown(index),
|
index
|
||||||
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
);
|
||||||
"div",
|
})
|
||||||
{
|
|
||||||
style: {
|
|
||||||
position: "absolute",
|
|
||||||
top: -24,
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
fontSize: 11,
|
|
||||||
color: "#00aaff",
|
|
||||||
fontWeight: "bold",
|
|
||||||
textShadow: "1px 1px 2px rgba(0,0,0,0.8)",
|
|
||||||
whiteSpace: "nowrap"
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
"P",
|
|
||||||
index + 1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
index
|
|
||||||
))
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1059,7 +1135,8 @@ var DistortionEditor = ({
|
|||||||
onAreasChange,
|
onAreasChange,
|
||||||
onSelectedAreaChange,
|
onSelectedAreaChange,
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 600
|
height = 600,
|
||||||
|
canvasStyle
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@ -1118,7 +1195,8 @@ var DistortionEditor = ({
|
|||||||
onUpdateArea: updateArea,
|
onUpdateArea: updateArea,
|
||||||
draggingPointIndex: state.draggingPointIndex,
|
draggingPointIndex: state.draggingPointIndex,
|
||||||
onStartDragging: startDragging,
|
onStartDragging: startDragging,
|
||||||
onStopDragging: stopDragging
|
onStopDragging: stopDragging,
|
||||||
|
style: canvasStyle
|
||||||
}
|
}
|
||||||
) }),
|
) }),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [
|
||||||
|
|||||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
264
dist/index.mjs
vendored
264
dist/index.mjs
vendored
@ -536,7 +536,67 @@ var useDistortionEditor = (initialAreas = []) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/components/EditorCanvas.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
import { useRef as useRef3, useEffect as useEffect3, useState as useState3, useCallback as useCallback3 } from "react";
|
import { useRef as useRef3, useEffect as useEffect3, useState as useState3, useCallback as useCallback3, useMemo } from "react";
|
||||||
|
|
||||||
|
// src/editor/constants.ts
|
||||||
|
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
||||||
|
// 3단계 원 스타일 (외부 -> 내부)
|
||||||
|
circleLevels: [
|
||||||
|
{
|
||||||
|
radius: 0.5,
|
||||||
|
opacity: 0.3,
|
||||||
|
lineWidth: 2,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.33,
|
||||||
|
opacity: 0.6,
|
||||||
|
lineWidth: 2.5,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.167,
|
||||||
|
opacity: 0.9,
|
||||||
|
lineWidth: 3,
|
||||||
|
color: "rgba(255, 200, 0, 1)",
|
||||||
|
dashPattern: [8, 4]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 원 내부 채우기
|
||||||
|
circleFillColor: "rgba(255, 200, 0, 0.08)",
|
||||||
|
// 중심점
|
||||||
|
centerPoint: {
|
||||||
|
radius: 5,
|
||||||
|
fillColor: "rgba(255, 200, 0, 1)",
|
||||||
|
strokeColor: "rgba(255, 255, 255, 0.8)",
|
||||||
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
// 포인트 핸들
|
||||||
|
pointHandle: {
|
||||||
|
size: 16,
|
||||||
|
fillColor: "#00aaff",
|
||||||
|
strokeColor: "white",
|
||||||
|
strokeWidth: 2,
|
||||||
|
labelColor: "#00aaff",
|
||||||
|
labelFontSize: 11
|
||||||
|
},
|
||||||
|
// 영역 외곽선
|
||||||
|
areaOutline: {
|
||||||
|
selectedColor: "#00aaff",
|
||||||
|
unselectedColor: "#888",
|
||||||
|
selectedWidth: 2,
|
||||||
|
unselectedWidth: 1,
|
||||||
|
unselectedDashPattern: [5, 5],
|
||||||
|
selectedFillColor: "rgba(0, 170, 255, 0.08)",
|
||||||
|
// 선택된 영역 배경 (연한 파란색)
|
||||||
|
unselectedFillColor: "rgba(136, 136, 136, 0.03)"
|
||||||
|
// 선택 안된 영역 배경 (연한 회색)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/editor/components/EditorCanvas.tsx
|
||||||
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
||||||
var EditorCanvas = ({
|
var EditorCanvas = ({
|
||||||
areas,
|
areas,
|
||||||
@ -548,12 +608,30 @@ var EditorCanvas = ({
|
|||||||
onUpdateArea,
|
onUpdateArea,
|
||||||
draggingPointIndex,
|
draggingPointIndex,
|
||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging
|
onStopDragging,
|
||||||
|
style: customStyle
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef3(null);
|
const containerRef = useRef3(null);
|
||||||
const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 });
|
const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 });
|
||||||
const [isDraggingArea, setIsDraggingArea] = useState3(false);
|
const [isDraggingArea, setIsDraggingArea] = useState3(false);
|
||||||
const [dragStartPos, setDragStartPos] = useState3(null);
|
const [dragStartPos, setDragStartPos] = useState3(null);
|
||||||
|
const editorStyle = useMemo(() => ({
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
...customStyle,
|
||||||
|
circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,
|
||||||
|
centerPoint: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,
|
||||||
|
...customStyle?.centerPoint
|
||||||
|
},
|
||||||
|
pointHandle: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,
|
||||||
|
...customStyle?.pointHandle
|
||||||
|
},
|
||||||
|
areaOutline: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,
|
||||||
|
...customStyle?.areaOutline
|
||||||
|
}
|
||||||
|
}), [customStyle]);
|
||||||
useEffect3(() => {
|
useEffect3(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
@ -644,63 +722,57 @@ var EditorCanvas = ({
|
|||||||
y: posY * canvasHeight
|
y: posY * canvasHeight
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const drawDistortionCircle = (ctx, points, canvasWidth, canvasHeight) => {
|
const drawDistortionCircle = useCallback3((ctx, points, canvasWidth, canvasHeight) => {
|
||||||
const segments = 128;
|
const segments = 128;
|
||||||
const centerU = 0.5;
|
const centerU = 0.5;
|
||||||
const centerV = 0.5;
|
const centerV = 0.5;
|
||||||
const maxRadius = 0.5;
|
const circleLevels = editorStyle.circleLevels || [];
|
||||||
const circlePoints = [];
|
circleLevels.forEach((level, index) => {
|
||||||
for (let i = 0; i <= segments; i++) {
|
const levelPoints = [];
|
||||||
const theta = i / segments * 2 * Math.PI;
|
|
||||||
const u = centerU - maxRadius * Math.sin(theta);
|
|
||||||
const v = centerV + maxRadius * Math.cos(theta);
|
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
|
||||||
circlePoints.push(pixelPos);
|
|
||||||
}
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(circlePoints[0].x, circlePoints[0].y);
|
|
||||||
for (let i = 1; i < circlePoints.length; i++) {
|
|
||||||
ctx.lineTo(circlePoints[i].x, circlePoints[i].y);
|
|
||||||
}
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.strokeStyle = "rgba(255, 200, 0, 0.9)";
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.setLineDash([8, 4]);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
ctx.fillStyle = "rgba(255, 200, 0, 0.12)";
|
|
||||||
ctx.fill();
|
|
||||||
for (const r of [0.25, 0.375]) {
|
|
||||||
const gradientPoints = [];
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
for (let i = 0; i <= segments; i++) {
|
||||||
const theta = i / segments * 2 * Math.PI;
|
const theta = i / segments * 2 * Math.PI;
|
||||||
const u = centerU - r * Math.sin(theta);
|
const u = centerU - level.radius * Math.sin(theta);
|
||||||
const v = centerV + r * Math.cos(theta);
|
const v = centerV + level.radius * Math.cos(theta);
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
||||||
gradientPoints.push(pixelPos);
|
levelPoints.push(pixelPos);
|
||||||
}
|
}
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y);
|
ctx.moveTo(levelPoints[0].x, levelPoints[0].y);
|
||||||
for (let i = 1; i < gradientPoints.length; i++) {
|
for (let i = 1; i < levelPoints.length; i++) {
|
||||||
ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y);
|
ctx.lineTo(levelPoints[i].x, levelPoints[i].y);
|
||||||
}
|
}
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
const alpha = r / maxRadius;
|
const baseColor = level.color || "rgba(255, 200, 0, 1)";
|
||||||
ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`;
|
const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => {
|
||||||
ctx.lineWidth = 1;
|
const parts = rgb.split(",").map((p) => p.trim());
|
||||||
ctx.setLineDash([3, 3]);
|
return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;
|
||||||
|
});
|
||||||
|
ctx.strokeStyle = colorWithOpacity;
|
||||||
|
ctx.lineWidth = level.lineWidth;
|
||||||
|
if (level.dashPattern) {
|
||||||
|
ctx.setLineDash(level.dashPattern);
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
}
|
if (index === 0 && editorStyle.circleFillColor) {
|
||||||
|
ctx.fillStyle = editorStyle.circleFillColor;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const centerPointStyle = editorStyle.centerPoint || {};
|
||||||
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI);
|
ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = "rgba(255, 200, 0, 1)";
|
if (centerPointStyle.fillColor) {
|
||||||
ctx.fill();
|
ctx.fillStyle = centerPointStyle.fillColor;
|
||||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
|
ctx.fill();
|
||||||
ctx.lineWidth = 2;
|
}
|
||||||
ctx.stroke();
|
if (centerPointStyle.strokeColor) {
|
||||||
};
|
ctx.strokeStyle = centerPointStyle.strokeColor;
|
||||||
|
ctx.lineWidth = centerPointStyle.strokeWidth || 2;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}, [editorStyle]);
|
||||||
const getCursorStyle = () => {
|
const getCursorStyle = () => {
|
||||||
if (draggingPointIndex !== null) return "grabbing";
|
if (draggingPointIndex !== null) return "grabbing";
|
||||||
if (isDraggingArea) return "grabbing";
|
if (isDraggingArea) return "grabbing";
|
||||||
@ -715,7 +787,7 @@ var EditorCanvas = ({
|
|||||||
onMouseDown: handleCanvasMouseDown,
|
onMouseDown: handleCanvasMouseDown,
|
||||||
onMouseMove: handleMouseMove,
|
onMouseMove: handleMouseMove,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas, width, height }),
|
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }),
|
||||||
/* @__PURE__ */ jsx2(
|
/* @__PURE__ */ jsx2(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -730,14 +802,15 @@ var EditorCanvas = ({
|
|||||||
children: areas.map((area) => {
|
children: areas.map((area) => {
|
||||||
const isSelected = area.id === selectedAreaId;
|
const isSelected = area.id === selectedAreaId;
|
||||||
const points = area.basePoints;
|
const points = area.basePoints;
|
||||||
|
const outlineStyle = editorStyle.areaOutline || {};
|
||||||
return /* @__PURE__ */ jsx2("g", { children: /* @__PURE__ */ jsx2(
|
return /* @__PURE__ */ jsx2("g", { children: /* @__PURE__ */ jsx2(
|
||||||
"polygon",
|
"polygon",
|
||||||
{
|
{
|
||||||
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
||||||
fill: "none",
|
fill: isSelected ? outlineStyle.selectedFillColor || "rgba(0, 170, 255, 0.08)" : outlineStyle.unselectedFillColor || "rgba(136, 136, 136, 0.03)",
|
||||||
stroke: isSelected ? "#00aaff" : "#888",
|
stroke: isSelected ? outlineStyle.selectedColor || "#00aaff" : outlineStyle.unselectedColor || "#888",
|
||||||
strokeWidth: isSelected ? 2 : 1,
|
strokeWidth: isSelected ? outlineStyle.selectedWidth || 2 : outlineStyle.unselectedWidth || 1,
|
||||||
strokeDasharray: isSelected ? "0" : "5,5",
|
strokeDasharray: isSelected ? "0" : outlineStyle.unselectedDashPattern?.join(",") || "5,5",
|
||||||
opacity: isSelected ? 1 : 0.5
|
opacity: isSelected ? 1 : 0.5
|
||||||
}
|
}
|
||||||
) }, area.id);
|
) }, area.id);
|
||||||
@ -768,48 +841,51 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && selectedArea.basePoints.map((point, index) => /* @__PURE__ */ jsx2(
|
selectedArea && selectedArea.basePoints.map((point, index) => {
|
||||||
"div",
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
{
|
return /* @__PURE__ */ jsx2(
|
||||||
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
"div",
|
||||||
style: {
|
{
|
||||||
position: "absolute",
|
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
||||||
left: `${point.x * 100}%`,
|
style: {
|
||||||
top: `${point.y * 100}%`,
|
position: "absolute",
|
||||||
transform: "translate(-50%, -50%)",
|
left: `${point.x * 100}%`,
|
||||||
width: 16,
|
top: `${point.y * 100}%`,
|
||||||
height: 16,
|
transform: "translate(-50%, -50%)",
|
||||||
borderRadius: "50%",
|
width: handleStyle.size || 16,
|
||||||
backgroundColor: "#00aaff",
|
height: handleStyle.size || 16,
|
||||||
border: "2px solid white",
|
borderRadius: "50%",
|
||||||
cursor: "grab",
|
backgroundColor: handleStyle.fillColor || "#00aaff",
|
||||||
pointerEvents: "auto",
|
border: `${handleStyle.strokeWidth || 2}px solid ${handleStyle.strokeColor || "white"}`,
|
||||||
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
|
cursor: "grab",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
|
||||||
|
},
|
||||||
|
onMouseDown: handleMouseDown(index),
|
||||||
|
children: /* @__PURE__ */ jsxs(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
position: "absolute",
|
||||||
|
top: -24,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
fontSize: handleStyle.labelFontSize || 11,
|
||||||
|
color: handleStyle.labelColor || "#00aaff",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textShadow: "1px 1px 2px rgba(0,0,0,0.8)",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
"P",
|
||||||
|
index + 1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onMouseDown: handleMouseDown(index),
|
index
|
||||||
children: /* @__PURE__ */ jsxs(
|
);
|
||||||
"div",
|
})
|
||||||
{
|
|
||||||
style: {
|
|
||||||
position: "absolute",
|
|
||||||
top: -24,
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
fontSize: 11,
|
|
||||||
color: "#00aaff",
|
|
||||||
fontWeight: "bold",
|
|
||||||
textShadow: "1px 1px 2px rgba(0,0,0,0.8)",
|
|
||||||
whiteSpace: "nowrap"
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
"P",
|
|
||||||
index + 1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
index
|
|
||||||
))
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1013,7 +1089,8 @@ var DistortionEditor = ({
|
|||||||
onAreasChange,
|
onAreasChange,
|
||||||
onSelectedAreaChange,
|
onSelectedAreaChange,
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 600
|
height = 600,
|
||||||
|
canvasStyle
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@ -1072,7 +1149,8 @@ var DistortionEditor = ({
|
|||||||
onUpdateArea: updateArea,
|
onUpdateArea: updateArea,
|
||||||
draggingPointIndex: state.draggingPointIndex,
|
draggingPointIndex: state.draggingPointIndex,
|
||||||
onStartDragging: startDragging,
|
onStartDragging: startDragging,
|
||||||
onStopDragging: stopDragging
|
onStopDragging: stopDragging,
|
||||||
|
style: canvasStyle
|
||||||
}
|
}
|
||||||
) }),
|
) }),
|
||||||
/* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [
|
/* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [
|
||||||
|
|||||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -14,6 +14,7 @@ export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|||||||
onSelectedAreaChange,
|
onSelectedAreaChange,
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 600,
|
height = 600,
|
||||||
|
canvasStyle,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@ -85,6 +86,7 @@ export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|||||||
draggingPointIndex={state.draggingPointIndex}
|
draggingPointIndex={state.draggingPointIndex}
|
||||||
onStartDragging={startDragging}
|
onStartDragging={startDragging}
|
||||||
onStopDragging={stopDragging}
|
onStopDragging={stopDragging}
|
||||||
|
style={canvasStyle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import React, {useRef, useEffect, useState, useCallback} from 'react';
|
import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';
|
||||||
import {DistortionArea, Point} from '../../types/area';
|
import {DistortionArea, Point} from '../../types/area';
|
||||||
import {ImageDistortion} from '../../components/ImageDistortion';
|
import {ImageDistortion} from '../../components/ImageDistortion';
|
||||||
|
import {EditorCanvasStyle} from '../types';
|
||||||
|
import {DEFAULT_EDITOR_CANVAS_STYLE} from '../constants';
|
||||||
|
|
||||||
interface EditorCanvasProps {
|
interface EditorCanvasProps {
|
||||||
areas: DistortionArea[];
|
areas: DistortionArea[];
|
||||||
@ -13,6 +15,8 @@ interface EditorCanvasProps {
|
|||||||
draggingPointIndex: number | null;
|
draggingPointIndex: number | null;
|
||||||
onStartDragging: (pointIndex: number) => void;
|
onStartDragging: (pointIndex: number) => void;
|
||||||
onStopDragging: () => void;
|
onStopDragging: () => void;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
style?: EditorCanvasStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||||
@ -26,12 +30,32 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
draggingPointIndex,
|
draggingPointIndex,
|
||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging,
|
onStopDragging,
|
||||||
|
style: customStyle,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
|
const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
|
||||||
const [isDraggingArea, setIsDraggingArea] = useState(false);
|
const [isDraggingArea, setIsDraggingArea] = useState(false);
|
||||||
const [dragStartPos, setDragStartPos] = useState<Point | null>(null);
|
const [dragStartPos, setDragStartPos] = useState<Point | null>(null);
|
||||||
|
|
||||||
|
// 스타일 병합 (커스텀 스타일 우선)
|
||||||
|
const editorStyle = useMemo(() => ({
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
...customStyle,
|
||||||
|
circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,
|
||||||
|
centerPoint: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,
|
||||||
|
...customStyle?.centerPoint,
|
||||||
|
},
|
||||||
|
pointHandle: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,
|
||||||
|
...customStyle?.pointHandle,
|
||||||
|
},
|
||||||
|
areaOutline: {
|
||||||
|
...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,
|
||||||
|
...customStyle?.areaOutline,
|
||||||
|
},
|
||||||
|
}), [customStyle]);
|
||||||
|
|
||||||
// 컨테이너 크기 측정
|
// 컨테이너 크기 측정
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
@ -167,7 +191,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)
|
// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)
|
||||||
const drawDistortionCircle = (
|
const drawDistortionCircle = useCallback((
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
points: [Point, Point, Point, Point],
|
points: [Point, Point, Point, Point],
|
||||||
canvasWidth: number,
|
canvasWidth: number,
|
||||||
@ -176,74 +200,64 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
const segments = 128; // 원을 128개 세그먼트로 촘촘히 분할
|
const segments = 128; // 원을 128개 세그먼트로 촘촘히 분할
|
||||||
const centerU = 0.5;
|
const centerU = 0.5;
|
||||||
const centerV = 0.5;
|
const centerV = 0.5;
|
||||||
const maxRadius = 0.5; // UV 좌표계에서 최대 반지름 0.5 (셰이더의 maxUvRadius)
|
|
||||||
|
|
||||||
// 원 위의 점들을 UV 좌표로 샘플링 후 픽셀 좌표로 변환
|
const circleLevels = editorStyle.circleLevels || [];
|
||||||
// 4가지 조합을 모두 테스트 (사용자가 이미지에서 P1-P3 대각선으로 늘렸을 때 왜곡도 같은 방향이어야 함)
|
|
||||||
const circlePoints: { x: number; y: number }[] = [];
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
|
||||||
const theta = (i / segments) * 2 * Math.PI;
|
|
||||||
|
|
||||||
// 테스트: u=-sin, v=cos (-90도 회전, P1-P3 방향에 맞춤)
|
// 원 레벨별로 그리기 (외부 -> 내부 순)
|
||||||
const u = centerU - maxRadius * Math.sin(theta);
|
circleLevels.forEach((level, index) => {
|
||||||
const v = centerV + maxRadius * Math.cos(theta);
|
const levelPoints: { x: number; y: number }[] = [];
|
||||||
|
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
|
||||||
circlePoints.push(pixelPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 찌그러진 원 그리기 (실제 왜곡 적용 경계)
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(circlePoints[0].x, circlePoints[0].y);
|
|
||||||
for (let i = 1; i < circlePoints.length; i++) {
|
|
||||||
ctx.lineTo(circlePoints[i].x, circlePoints[i].y);
|
|
||||||
}
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.strokeStyle = 'rgba(255, 200, 0, 0.9)';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.setLineDash([8, 4]);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
|
|
||||||
// 내부를 반투명하게 채우기
|
|
||||||
ctx.fillStyle = 'rgba(255, 200, 0, 0.12)';
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
// 영향력 그라디언트를 나타내는 추가 원들 (0.25, 0.375 반지름)
|
|
||||||
for (const r of [0.25, 0.375]) {
|
|
||||||
const gradientPoints: { x: number; y: number }[] = [];
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
for (let i = 0; i <= segments; i++) {
|
||||||
const theta = (i / segments) * 2 * Math.PI;
|
const theta = (i / segments) * 2 * Math.PI;
|
||||||
const u = centerU - r * Math.sin(theta);
|
const u = centerU - level.radius * Math.sin(theta);
|
||||||
const v = centerV + r * Math.cos(theta);
|
const v = centerV + level.radius * Math.cos(theta);
|
||||||
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);
|
||||||
gradientPoints.push(pixelPos);
|
levelPoints.push(pixelPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y);
|
ctx.moveTo(levelPoints[0].x, levelPoints[0].y);
|
||||||
for (let i = 1; i < gradientPoints.length; i++) {
|
for (let i = 1; i < levelPoints.length; i++) {
|
||||||
ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y);
|
ctx.lineTo(levelPoints[i].x, levelPoints[i].y);
|
||||||
}
|
}
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
const alpha = r / maxRadius;
|
|
||||||
ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`;
|
// 원 테두리
|
||||||
ctx.lineWidth = 1;
|
const baseColor = level.color || 'rgba(255, 200, 0, 1)';
|
||||||
ctx.setLineDash([3, 3]);
|
// baseColor에서 RGB 추출하고 opacity 적용
|
||||||
|
const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => {
|
||||||
|
const parts = rgb.split(',').map((p: string) => p.trim());
|
||||||
|
return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;
|
||||||
|
});
|
||||||
|
ctx.strokeStyle = colorWithOpacity;
|
||||||
|
ctx.lineWidth = level.lineWidth;
|
||||||
|
if (level.dashPattern) {
|
||||||
|
ctx.setLineDash(level.dashPattern);
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
}
|
|
||||||
|
// 가장 외부 원만 내부 채우기
|
||||||
|
if (index === 0 && editorStyle.circleFillColor) {
|
||||||
|
ctx.fillStyle = editorStyle.circleFillColor;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 중심점 표시
|
// 중심점 표시
|
||||||
|
const centerPointStyle = editorStyle.centerPoint || {};
|
||||||
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI);
|
ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = 'rgba(255, 200, 0, 1)';
|
if (centerPointStyle.fillColor) {
|
||||||
ctx.fill();
|
ctx.fillStyle = centerPointStyle.fillColor;
|
||||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
|
ctx.fill();
|
||||||
ctx.lineWidth = 2;
|
}
|
||||||
ctx.stroke();
|
if (centerPointStyle.strokeColor) {
|
||||||
};
|
ctx.strokeStyle = centerPointStyle.strokeColor;
|
||||||
|
ctx.lineWidth = centerPointStyle.strokeWidth || 2;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}, [editorStyle]);
|
||||||
|
|
||||||
// 커서 스타일 결정
|
// 커서 스타일 결정
|
||||||
const getCursorStyle = () => {
|
const getCursorStyle = () => {
|
||||||
@ -261,7 +275,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
>
|
>
|
||||||
{/* ImageDistortion 컴포넌트 */}
|
{/* ImageDistortion 컴포넌트 */}
|
||||||
<ImageDistortion imageSrc={imageSrc} areas={areas} width={width} height={height}/>
|
<ImageDistortion imageSrc={imageSrc} areas={areas}/>
|
||||||
|
|
||||||
{/* 오버레이 SVG */}
|
{/* 오버레이 SVG */}
|
||||||
<svg
|
<svg
|
||||||
@ -278,17 +292,18 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
{areas.map((area) => {
|
{areas.map((area) => {
|
||||||
const isSelected = area.id === selectedAreaId;
|
const isSelected = area.id === selectedAreaId;
|
||||||
const points = area.basePoints;
|
const points = area.basePoints;
|
||||||
|
const outlineStyle = editorStyle.areaOutline || {};
|
||||||
return (
|
return (
|
||||||
<g key={area.id}>
|
<g key={area.id}>
|
||||||
{/* 사각형 경계선 */}
|
{/* 사각형 배경 및 경계선 */}
|
||||||
<polygon
|
<polygon
|
||||||
points={points
|
points={points
|
||||||
.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`)
|
.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`)
|
||||||
.join(' ')}
|
.join(' ')}
|
||||||
fill="none"
|
fill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')}
|
||||||
stroke={isSelected ? '#00aaff' : '#888'}
|
stroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}
|
||||||
strokeWidth={isSelected ? 2 : 1}
|
strokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}
|
||||||
strokeDasharray={isSelected ? '0' : '5,5'}
|
strokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}
|
||||||
opacity={isSelected ? 1 : 0.5}
|
opacity={isSelected ? 1 : 0.5}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
@ -323,43 +338,46 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
|
|
||||||
{/* 선택된 영역의 포인트 핸들 */}
|
{/* 선택된 영역의 포인트 핸들 */}
|
||||||
{selectedArea &&
|
{selectedArea &&
|
||||||
selectedArea.basePoints.map((point, index) => (
|
selectedArea.basePoints.map((point, index) => {
|
||||||
<div
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
key={index}
|
return (
|
||||||
className={`point-handle ${draggingPointIndex === index ? 'dragging' : ''}`}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: `${point.x * 100}%`,
|
|
||||||
top: `${point.y * 100}%`,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: '#00aaff',
|
|
||||||
border: '2px solid white',
|
|
||||||
cursor: 'grab',
|
|
||||||
pointerEvents: 'auto',
|
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
|
||||||
}}
|
|
||||||
onMouseDown={handleMouseDown(index)}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`point-handle ${draggingPointIndex === index ? 'dragging' : ''}`}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: -24,
|
left: `${point.x * 100}%`,
|
||||||
left: '50%',
|
top: `${point.y * 100}%`,
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
fontSize: 11,
|
width: handleStyle.size || 16,
|
||||||
color: '#00aaff',
|
height: handleStyle.size || 16,
|
||||||
fontWeight: 'bold',
|
borderRadius: '50%',
|
||||||
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
backgroundColor: handleStyle.fillColor || '#00aaff',
|
||||||
whiteSpace: 'nowrap',
|
border: `${handleStyle.strokeWidth || 2}px solid ${handleStyle.strokeColor || 'white'}`,
|
||||||
|
cursor: 'grab',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
||||||
}}
|
}}
|
||||||
|
onMouseDown={handleMouseDown(index)}
|
||||||
>
|
>
|
||||||
P{index + 1}
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: -24,
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
fontSize: handleStyle.labelFontSize || 11,
|
||||||
|
color: handleStyle.labelColor || '#00aaff',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
P{index + 1}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,14 @@
|
|||||||
export { DistortionEditor } from './DistortionEditor';
|
export { DistortionEditor } from './DistortionEditor';
|
||||||
export type { DistortionEditorProps, EditorState, EditMode } from './types';
|
export type {
|
||||||
|
DistortionEditorProps,
|
||||||
|
EditorState,
|
||||||
|
EditMode,
|
||||||
|
EditorCanvasStyle,
|
||||||
|
CircleLevelStyle,
|
||||||
|
CenterPointStyle,
|
||||||
|
PointHandleStyle,
|
||||||
|
AreaOutlineStyle,
|
||||||
|
} from './types';
|
||||||
export { useDistortionEditor } from './hooks/useDistortionEditor';
|
export { useDistortionEditor } from './hooks/useDistortionEditor';
|
||||||
|
export { DEFAULT_EDITOR_CANVAS_STYLE } from './constants';
|
||||||
import './editor.css';
|
import './editor.css';
|
||||||
|
|||||||
@ -32,6 +32,90 @@ export interface PointHandle {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 왜곡 영역 원 레벨 스타일
|
||||||
|
*/
|
||||||
|
export interface CircleLevelStyle {
|
||||||
|
/** 반지름 (0.0 - 1.0, UV 좌표) */
|
||||||
|
radius: number;
|
||||||
|
/** 투명도 (0.0 - 1.0) */
|
||||||
|
opacity: number;
|
||||||
|
/** 선 두께 (픽셀) */
|
||||||
|
lineWidth: number;
|
||||||
|
/** 선 색상 (CSS color) */
|
||||||
|
color?: string;
|
||||||
|
/** 대시 패턴 [dash, gap] */
|
||||||
|
dashPattern?: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 중심점 스타일
|
||||||
|
*/
|
||||||
|
export interface CenterPointStyle {
|
||||||
|
/** 반지름 (픽셀) */
|
||||||
|
radius?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 포인트 핸들 스타일
|
||||||
|
*/
|
||||||
|
export interface PointHandleStyle {
|
||||||
|
/** 핸들 크기 (픽셀) */
|
||||||
|
size?: number;
|
||||||
|
/** 채우기 색상 */
|
||||||
|
fillColor?: string;
|
||||||
|
/** 테두리 색상 */
|
||||||
|
strokeColor?: string;
|
||||||
|
/** 테두리 두께 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
/** 레이블 색상 */
|
||||||
|
labelColor?: string;
|
||||||
|
/** 레이블 폰트 크기 */
|
||||||
|
labelFontSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 영역 외곽선 스타일
|
||||||
|
*/
|
||||||
|
export interface AreaOutlineStyle {
|
||||||
|
/** 선택된 영역 색상 */
|
||||||
|
selectedColor?: string;
|
||||||
|
/** 선택되지 않은 영역 색상 */
|
||||||
|
unselectedColor?: string;
|
||||||
|
/** 선택된 영역 선 두께 */
|
||||||
|
selectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 선 두께 */
|
||||||
|
unselectedWidth?: number;
|
||||||
|
/** 선택되지 않은 영역 대시 패턴 */
|
||||||
|
unselectedDashPattern?: [number, number];
|
||||||
|
/** 선택된 영역 배경 채우기 색상 */
|
||||||
|
selectedFillColor?: string;
|
||||||
|
/** 선택되지 않은 영역 배경 채우기 색상 */
|
||||||
|
unselectedFillColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
export interface EditorCanvasStyle {
|
||||||
|
/** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */
|
||||||
|
circleLevels?: CircleLevelStyle[];
|
||||||
|
/** 왜곡 영역 내부 채우기 색상 */
|
||||||
|
circleFillColor?: string;
|
||||||
|
/** 중심점 스타일 */
|
||||||
|
centerPoint?: CenterPointStyle;
|
||||||
|
/** 포인트 핸들 스타일 */
|
||||||
|
pointHandle?: PointHandleStyle;
|
||||||
|
/** 영역 외곽선 스타일 */
|
||||||
|
areaOutline?: AreaOutlineStyle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 에디터 Props
|
* 에디터 Props
|
||||||
*/
|
*/
|
||||||
@ -48,4 +132,6 @@ export interface DistortionEditorProps {
|
|||||||
width?: number;
|
width?: number;
|
||||||
/** 캔버스 높이 */
|
/** 캔버스 높이 */
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
canvasStyle?: EditorCanvasStyle;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user