From 3b17868560090756caf8b9b0f043ea163f2320e8 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 20 Oct 2024 12:44:15 -0400 Subject: changes --- .../views/nodes/imageEditor/ImageEditor.scss | 134 +++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.scss (limited to 'src/client/views/nodes/imageEditor/ImageEditor.scss') diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss new file mode 100644 index 000000000..21c28f6da --- /dev/null +++ b/src/client/views/nodes/imageEditor/ImageEditor.scss @@ -0,0 +1,134 @@ +$navHeight: 5rem; +$canvasSize: 1024px; +$scale: 0.5; + +.imageEditorContainer { + position: absolute; + top: 0; + left: 0; + z-index: 9999; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + overflow: hidden; + + .imageEditorTopBar { + flex-shrink: 0; + height: $navHeight; + color: #000000; + background-color: #ffffff; + z-index: 999; + width: 100%; + display: flex; + gap: 3rem; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #c7cdd0; + padding: 0 2rem; + + .imageEditorControls { + display: flex; + align-items: center; + gap: 1.5rem; + } + + h1 { + font-size: 1.5rem; + } + } + + .drawingArea { + cursor: none; + touch-action: none; + position: relative; + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + background-color: #f0f4f6; + + canvas { + display: block; + position: absolute; + transform-origin: 50% 50%; + } + + .pointer { + pointer-events: none; + position: absolute; + border-radius: 50%; + width: 50px; + height: 50px; + border: 1px solid #ffffff; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + + .innerPointer { + width: 100%; + height: 100%; + border: 1px solid #000000; + border-radius: 50%; + } + } + + .iconContainer { + position: absolute; + top: 3rem; + left: 2rem; + display: flex; + flex-direction: column; + gap: 4rem; + + .imageToolsContainer { + display: flex; + flex-direction: column; + gap: 10px; + } + + .undoRedoContainer { + justify-content: center; + display: flex; + flex-direction: row; + } + + .sliderContainer { + height: 225px; + width: 100%; + display: flex; + justify-content: center; + cursor: pointer; + } + } + + .editsBox { + position: absolute; + top: 2rem; + right: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + + .originalImageLabel { + position: absolute; + bottom: 10; + left: 10; + color: #ffffff; + font-size: 0.8rem; + letter-spacing: 1px; + text-transform: uppercase; + } + + img { + cursor: pointer; + transition: all 0.2s ease-in-out; + &:hover { + opacity: 0.8; + } + } + } + } +} -- cgit v1.2.3-70-g09d2 From d0569fcb79389b3c4f3d60e2a84f6f9220ca9cf3 Mon Sep 17 00:00:00 2001 From: eleanor-park <113556828+eleanor-park@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:52:20 -0400 Subject: added different cut types, haven't been able to run & test --- .../views/nodes/imageEditor/ImageEditor.scss | 6 +++ src/client/views/nodes/imageEditor/ImageEditor.tsx | 56 ++++++++++++++++++++-- .../imageEditorUtils/imageEditorInterfaces.ts | 6 ++- 3 files changed, 61 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/imageEditor/ImageEditor.scss') diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss index 21c28f6da..49146aa23 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.scss +++ b/src/client/views/nodes/imageEditor/ImageEditor.scss @@ -89,6 +89,12 @@ $scale: 0.5; gap: 10px; } + .cutToolsContainer { + display: grid; + gap: 5px; + grid-template-columns: 1fr 1fr; + } + .undoRedoContainer { justify-content: center; display: flex; diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 86f7d8d29..d9f46876e 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -25,13 +25,14 @@ import { ApplyFuncButtons, ImageToolButton } from './ImageEditorButtons'; import { BrushHandler, BrushType } from './imageEditorUtils/BrushHandler'; import { APISuccess, ImageUtility } from './imageEditorUtils/ImageHandler'; import { PointerHandler } from './imageEditorUtils/PointerHandler'; -import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants'; -import { CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces'; +import { activeColor, bgColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants'; +import { BrushMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces'; import { DocumentView } from '../DocumentView'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ImageField } from '../../../../fields/URLField'; import { resolve } from 'url'; import { DocData } from '../../../../fields/DocSymbols'; +import { SettingsManager } from '../../../util/SettingsManager'; interface GenerativeFillProps { imageEditorOpen: boolean; @@ -57,13 +58,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc const [edits, setEdits] = useState<{ url: string; saveRes: Doc | undefined }[]>([]); const [edited, setEdited] = useState(false); const isFirstDoc = useRef(true); - // const [brushStyle] = useState(BrushStyle.ADD); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [canvasDims, setCanvasDims] = useState({ width: canvasSize, height: canvasSize, }); + const [cutType, setCutType] = useState(BrushMode.IN); // whether to create a new collection or not const [isNewCollection, setIsNewCollection] = useState(true); // the current image in the main canvas @@ -84,6 +85,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // constants for image cutting const cutPts = useRef([]); + /** * * @param type The new tool type we are changing to @@ -380,8 +382,21 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc maxY = Math.max(cutPts.current[i].y, maxY); } ctx.closePath(); - ctx.globalCompositeOperation = 'destination-in'; - ctx.fill(); + switch (cutType) { // may need to move this before the drawing at all for the line cases + case BrushMode.IN: + ctx.globalCompositeOperation = 'destination-in'; + ctx.fill(); + break; + case BrushMode.OUT: + ctx.globalCompositeOperation = 'destination-out'; + ctx.fill(); + break; + case BrushMode.LINE_OUT: + ctx.globalCompositeOperation = 'destination-out'; + break; + case BrushMode.LINE_IN: + ctx.globalCompositeOperation = 'destination-in'; + } } const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl @@ -578,6 +593,37 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc return ImageToolButton(tool, tool.type === currTool.type, changeTool); })} + {currTool.type == ImageToolType.Cut && +
+
}
e.stopPropagation()}> {currTool.type === ImageToolType.GenerativeFill && ( Date: Mon, 28 Oct 2024 23:10:24 -0400 Subject: basic structure of 4 cut modes implemented, many bugs to fix --- .../views/nodes/imageEditor/ImageEditor.scss | 59 +++-- src/client/views/nodes/imageEditor/ImageEditor.tsx | 265 +++++++++++---------- .../views/nodes/imageEditor/ImageEditorButtons.tsx | 2 +- .../imageEditorUtils/imageEditorInterfaces.ts | 3 +- 4 files changed, 180 insertions(+), 149 deletions(-) (limited to 'src/client/views/nodes/imageEditor/ImageEditor.scss') diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss index 49146aa23..c691e6a18 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.scss +++ b/src/client/views/nodes/imageEditor/ImageEditor.scss @@ -75,38 +75,47 @@ $scale: 0.5; } } - .iconContainer { + .sideControlsContainer { + width: 160px; position: absolute; - top: 3rem; - left: 2rem; - display: flex; - flex-direction: column; - gap: 4rem; + left: 0; + height: 100%; - .imageToolsContainer { + .sideControls { + position: absolute; + width: 120px; + top: 3rem; + left: 2rem; display: flex; flex-direction: column; - gap: 10px; - } + gap: 1rem; - .cutToolsContainer { - display: grid; - gap: 5px; - grid-template-columns: 1fr 1fr; - } + .imageToolsContainer { + display: flex; + flex-direction: column; + gap: 10px; + } - .undoRedoContainer { - justify-content: center; - display: flex; - flex-direction: row; - } + .cutToolsContainer { + display: grid; + gap: 5px; + grid-template-columns: 1fr 1fr; + } - .sliderContainer { - height: 225px; - width: 100%; - display: flex; - justify-content: center; - cursor: pointer; + .undoRedoContainer { + justify-content: center; + display: flex; + flex-direction: row; + } + + .sliderContainer { + margin: 3rem 0; + height: 225px; + width: 100%; + display: flex; + justify-content: center; + cursor: pointer; + } } } diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index d9f46876e..980f3e566 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -4,7 +4,7 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable react/function-component-definition */ import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material'; -import { IconButton } from 'browndash-components'; +import { Button, IconButton, Type } from 'browndash-components'; import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import { CgClose } from 'react-icons/cg'; @@ -85,7 +85,6 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // constants for image cutting const cutPts = useRef([]); - /** * * @param type The new tool type we are changing to @@ -356,7 +355,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc setLoading(false); }; - const cutImage = async () => { + const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => { const img = currImg.current; const canvas = canvasRef.current; if (!canvas || !img) return; @@ -381,21 +380,22 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc maxX = Math.max(cutPts.current[i].x, maxX); maxY = Math.max(cutPts.current[i].y, maxY); } - ctx.closePath(); - switch (cutType) { // may need to move this before the drawing at all for the line cases + switch (currCutType) { case BrushMode.IN: + ctx.closePath(); ctx.globalCompositeOperation = 'destination-in'; ctx.fill(); break; case BrushMode.OUT: + ctx.closePath(); ctx.globalCompositeOperation = 'destination-out'; ctx.fill(); break; - case BrushMode.LINE_OUT: - ctx.globalCompositeOperation = 'destination-out'; - break; case BrushMode.LINE_IN: ctx.globalCompositeOperation = 'destination-in'; + ctx.lineWidth = brushWidth + 20; + ctx.stroke(); + break; } } @@ -423,31 +423,41 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc const image = new Image(); image.src = url; image.onload = async () => { - const croppedCanvas = document.createElement('canvas'); - const croppedCtx = croppedCanvas.getContext('2d'); - if (!croppedCtx) return; - croppedCanvas.width = maxX - minX; - croppedCanvas.height = maxY - minY; - croppedCtx.globalCompositeOperation = 'source-over'; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(image, -minX, -minY); - const croppedURL = croppedCanvas.toDataURL(); - const croppedImage = new Image(); - croppedImage.src = croppedURL; - - currImg.current = croppedImage; - const newImgDoc = await createNewImgDoc(croppedImage, isFirstDoc.current); - if (isFirstDoc.current) isFirstDoc.current = false; + let finalImg: HTMLImageElement | undefined = image; + let finalImgURL: string = url; + if (currCutType == BrushMode.IN) { + const croppedData = cropImage(image, minX, maxY, minY, maxY); + finalImg = croppedData; + finalImgURL = croppedData.src; + } + currImg.current = finalImg; + const newImgDoc = await createNewImgDoc(finalImg, isFirstDoc.current); if (newImgDoc) { const docData = newImgDoc[DocData]; docData.backgroundColor = 'transparent'; + if (isFirstDoc.current) isFirstDoc.current = false; + setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]); } - setEdits(prevEdits => [...prevEdits, { url: croppedURL, saveRes: undefined }]); setLoading(false); cutPts.current.length = 0; }; }; + const cropImage = (image: HTMLImageElement, minX: number, maxX: number, minY: number, maxY: number) => { + const croppedCanvas = document.createElement('canvas'); + const croppedCtx = croppedCanvas.getContext('2d'); + if (!croppedCtx) return image; + croppedCanvas.width = maxX - minX; + croppedCanvas.height = maxY - minY; + croppedCtx.globalCompositeOperation = 'source-over'; + croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); + croppedCtx.drawImage(image, -minX, -minY); + const croppedURL = croppedCanvas.toDataURL(); + const croppedImage = new Image(); + croppedImage.src = croppedURL; + return croppedImage; + }; + // adjusts all the img positions to be aligned const adjustImgPositions = () => { if (!parentDoc.current) return; @@ -577,7 +587,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc labelPlacement="end" sx={{ whiteSpace: 'nowrap' }} /> - + currTool.applyFunc(cutType, cursorData.width, edits)} loading={loading} onReset={handleReset} btnText={currTool.btnText} /> } onClick={handleViewClose} />
@@ -587,109 +597,120 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // the side icons including tool type, the slider, and undo/redo function renderSideIcons() { return ( -
-
- {imageEditTools.map(tool => { - return ImageToolButton(tool, tool.type === currTool.type, changeTool); - })} -
- {currTool.type == ImageToolType.Cut && -
-
} -
e.stopPropagation()}> - {currTool.type === ImageToolType.GenerativeFill && ( - +
+
+ {imageEditTools.map(tool => { + return ImageToolButton(tool, tool.type === currTool.type, changeTool); + })} +
+ {currTool.type == ImageToolType.Cut && ( +
+
+ )} +
e.stopPropagation()}> + {currTool.type === ImageToolType.GenerativeFill && ( + { + setCursorData(prev => ({ ...prev, width: val as number })); + }} + /> + )} + {currTool.type === ImageToolType.Cut && ( + { + setCursorData(prev => ({ ...prev, width: val as number })); + }} + /> + )} +
+ {/* Undo and Redo */} +
+ { + e.stopPropagation(); + handleUndo(); }} - orientation="vertical" - min={genFillTool.sliderMin} - max={genFillTool.sliderMax} - defaultValue={genFillTool.sliderDefault} - size="small" - valueLabelDisplay="auto" - onChange={(e, val) => { - setCursorData(prev => ({ ...prev, width: val as number })); + onPointerUp={e => { + e.stopPropagation(); }} + color={activeColor} + tooltip="Undo" + icon={} /> - )} - {currTool.type === ImageToolType.Cut && ( - { + e.stopPropagation(); + handleRedo(); }} - orientation="vertical" - min={cutTool.sliderMin} - max={cutTool.sliderMax} - defaultValue={cutTool.sliderDefault} - size="small" - valueLabelDisplay="auto" - onChange={(e, val) => { - setCursorData(prev => ({ ...prev, width: val as number })); + onPointerUp={e => { + e.stopPropagation(); }} + color={activeColor} + tooltip="Redo" + icon={} /> - )} -
- {/* Undo and Redo */} -
- { - e.stopPropagation(); - handleUndo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Undo" - icon={} - /> - { - e.stopPropagation(); - handleRedo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Redo" - icon={} - /> +
); diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx index e90babf9b..cb963616b 100644 --- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx @@ -55,7 +55,7 @@ export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTo return (