From 10c9ef174be25a821900185e762ab1cc3e48df18 Mon Sep 17 00:00:00 2001 From: Noma Date: Fri, 21 Mar 2025 15:21:32 -0400 Subject: feat: mesh transform for images --- .../imageEditor/imageMeshTool/ImageMeshTool.ts | 0 .../nodes/imageEditor/imageMeshTool/imageMesh.scss | 24 +++++ .../nodes/imageEditor/imageMeshTool/imageMesh.tsx | 109 +++++++++++++++++++++ .../imageMeshTool/imageMeshToolButton.scss | 21 ++++ .../imageMeshTool/imageMeshToolButton.tsx | 90 +++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx (limited to 'src') diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts b/src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss new file mode 100644 index 000000000..253f48f77 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss @@ -0,0 +1,24 @@ +/* MeshTransformGrid.scss */ +.meshTransformGrid { + position: relative; + width: 100%; + height: 100%; + pointer-events: none; /* Prevents interaction with the grid itself */ + opacity: 5%; +} + +.grid-line { + position: absolute; + background-color: rgba(255, 255, 255, 0.6); /* Light grid lines */ +} + +.control-point { + position: absolute; + width: 12px; + height: 12px; + background-color: rgba(255, 255, 255, 1); /* White control points */ + border-radius: 50%; + cursor: pointer; + z-index: 10; + pointer-events: auto; /* Allows dragging of control points */ +} diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx new file mode 100644 index 000000000..ee5c597e9 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from 'react'; +import './MeshTransformGrid.scss'; + +interface MeshTransformGridProps { + imageRef: React.RefObject; // Reference to the image element + gridXSize: number; // Number of X subdivisions + gridYSize: number; // Number of Y subdivisions + isInteractive: boolean; // Whether control points are interactive (can be dragged) +} + +const MeshTransformGrid: React.FC = ({ imageRef, gridXSize, gridYSize, isInteractive }) => { + const [controlPoints, setControlPoints] = useState([]); + + // Set up control points based on image size and grid sizes + useEffect(() => { + if (imageRef.current) { + const { width, height, left, top } = imageRef.current.getBoundingClientRect(); + const newControlPoints = []; + + for (let i = 0; i <= gridYSize; i++) { + for (let j = 0; j <= gridXSize; j++) { + newControlPoints.push({ + id: `${i}-${j}`, + x: (j * width) / gridXSize + left, + y: (i * height) / gridYSize + top, + }); + } + } + + setControlPoints(newControlPoints); + } + }, [imageRef, gridXSize, gridYSize]); + + // Handle dragging of control points + const handleDrag = (e: React.MouseEvent, pointId: string) => { + if (!isInteractive) return; // Prevent dragging if grid is not interactive + + const { clientX, clientY } = e; + const updatedPoints = controlPoints.map((point) => { + if (point.id === pointId) { + return { ...point, x: clientX, y: clientY }; + } + return point; + }); + setControlPoints(updatedPoints); + }; + + // Render grid lines between control points + const renderGridLines = () => { + const lines = []; + for (let i = 0; i < controlPoints.length; i++) { + const point = controlPoints[i]; + const nextPoint = controlPoints[i + 1]; + + // Horizontal lines + if (nextPoint && i % (gridXSize + 1) !== gridXSize) { + lines.push({ + start: { x: point.x, y: point.y }, + end: { x: nextPoint.x, y: nextPoint.y }, + }); + } + + // Vertical lines + if (i + gridXSize + 1 < controlPoints.length) { + const downPoint = controlPoints[i + gridXSize + 1]; + lines.push({ + start: { x: point.x, y: point.y }, + end: { x: downPoint.x, y: downPoint.y }, + }); + } + } + return lines.map((line, index) => ( +
+ )); + }; + + return ( +
+ {renderGridLines()} + + {controlPoints.map((point) => ( +
handleDrag(e, point.id)} + /> + ))} +
+ ); +}; + +export default MeshTransformGrid; diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss new file mode 100644 index 000000000..a72b2de6a --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss @@ -0,0 +1,21 @@ +/* MeshTransformButton.scss */ +.meshTransformBtnContainer { + position: relative; + width: 100%; + height: 100%; +} + +.grid-line { + position: absolute; + background-color: rgba(255, 255, 255, 0.6); +} + +.control-point { + position: absolute; + width: 12px; + height: 12px; + background-color: rgba(255, 255, 255, 1); + border-radius: 50%; + cursor: pointer; + z-index: 10; +} diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx new file mode 100644 index 000000000..eb68410b0 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx @@ -0,0 +1,90 @@ +import './MeshTransformButton.scss'; +import * as React from 'react'; +import ReactLoading from 'react-loading'; +import { Button, IconButton, Type } from '@dash/components'; +import { AiOutlineInfo } from 'react-icons/ai'; +import { SettingsManager } from '../../../../util/SettingsManager'; +import MeshTransformGrid from './imageMesh'; + +interface ButtonContainerProps { + onClick: () => Promise; + loading: boolean; + onReset: () => void; + btnText: string; + imageWidth: number; + imageHeight: number; + gridXSize: number; // X subdivisions + gridYSize: number; // Y subdivisions +} + +export function MeshTransformButton({ + loading, + onClick: startMeshTransform, + onReset, + btnText, + imageWidth, + imageHeight, + gridXSize, + gridYSize +}: ButtonContainerProps) { + const [showGrid, setShowGrid] = React.useState(false); + const [isGridInteractive, setIsGridInteractive] = React.useState(false); // Controls the dragging of control points + const imageRef = React.useRef(null); // Reference to the image element + + const handleGridToggle = () => { + if (showGrid) { + setShowGrid(false); // Hide the grid + setIsGridInteractive(false); // Disable control points manipulation + } else { + setShowGrid(true); // Show the grid + setIsGridInteractive(true); // Enable control points manipulation + } + }; + + return ( +
+
+ ); +} -- cgit v1.2.3-70-g09d2