diff options
Diffstat (limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')
-rw-r--r-- | src/client/views/nodes/imageEditor/ImageEditor.tsx | 265 |
1 files changed, 143 insertions, 122 deletions
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<Point[]>([]); - /** * * @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' }} /> - <ApplyFuncButtons onClick={currTool.applyFunc} loading={loading} onReset={handleReset} btnText={currTool.btnText} /> + <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits)} loading={loading} onReset={handleReset} btnText={currTool.btnText} /> <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} /> </div> </div> @@ -587,109 +597,120 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // the side icons including tool type, the slider, and undo/redo function renderSideIcons() { return ( - <div className="iconContainer"> - <div className="imageToolsContainer"> - {imageEditTools.map(tool => { - return ImageToolButton(tool, tool.type === currTool.type, changeTool); - })} - </div> - {currTool.type == ImageToolType.Cut && - <div className="cutToolContainer"> - <Button - text="Keep in" - type={Type.TERT} - color={cutType == BrushMode.IN ? SettingsManager.userVariantColor : bgColor} - onClick={() => { - setCutType(BrushMode.IN); - }}/> - <Button - text="Keep out" - type={Type.TERT} - color={cutType == BrushMode.OUT ? SettingsManager.userVariantColor : bgColor} - onClick={() => { - setCutType(BrushMode.OUT); - }}/> - <Button - text="Draw in" - type={Type.TERT} - color={cutType == BrushMode.LINE_IN ? SettingsManager.userVariantColor : bgColor} - onClick={() => { - setCutType(BrushMode.LINE_IN); - }}/> - <Button - text="Erase" - type={Type.TERT} - color={cutType == BrushMode.LINE_OUT ? SettingsManager.userVariantColor : bgColor} - onClick={() => { - setCutType(BrushMode.LINE_OUT); - }}/> - </div>} - <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}> - {currTool.type === ImageToolType.GenerativeFill && ( - <Slider - sx={{ - '& input[type="range"]': { - WebkitAppearance: 'slider-vertical', - }, + <div className="sideControlsContainer" style={{ backgroundColor: bgColor }}> + <div className="sideControls"> + <div className="imageToolsContainer"> + {imageEditTools.map(tool => { + return ImageToolButton(tool, tool.type === currTool.type, changeTool); + })} + </div> + {currTool.type == ImageToolType.Cut && ( + <div className="cutToolsContainer"> + <Button + style={{ width: '100%' }} + text="Keep in" + type={Type.TERT} + color={cutType == BrushMode.IN ? SettingsManager.userColor : bgColor} + onClick={() => { + setCutType(BrushMode.IN); + }} + /> + <Button + style={{ width: '100%' }} + text="Keep out" + type={Type.TERT} + color={cutType == BrushMode.OUT ? SettingsManager.userColor : bgColor} + onClick={() => { + setCutType(BrushMode.OUT); + }} + /> + <Button + style={{ width: '100%' }} + text="Draw in" + type={Type.TERT} + color={cutType == BrushMode.LINE_IN ? SettingsManager.userColor : bgColor} + onClick={() => { + setCutType(BrushMode.LINE_IN); + }} + /> + <Button + style={{ width: '100%' }} + text="Erase" + type={Type.TERT} + color={cutType == BrushMode.LINE_OUT ? SettingsManager.userColor : bgColor} + onClick={() => { + setCutType(BrushMode.LINE_OUT); + }} + /> + </div> + )} + <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}> + {currTool.type === ImageToolType.GenerativeFill && ( + <Slider + sx={{ + '& input[type="range"]': { + WebkitAppearance: 'slider-vertical', + }, + }} + 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 })); + }} + /> + )} + {currTool.type === ImageToolType.Cut && ( + <Slider + sx={{ + '& input[type="range"]': { + WebkitAppearance: 'slider-vertical', + }, + }} + 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 })); + }} + /> + )} + </div> + {/* Undo and Redo */} + <div className="undoRedoContainer"> + <IconButton + style={{ cursor: 'pointer' }} + onPointerDown={e => { + 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={<IoMdUndo />} /> - )} - {currTool.type === ImageToolType.Cut && ( - <Slider - sx={{ - '& input[type="range"]': { - WebkitAppearance: 'slider-vertical', - }, + <IconButton + style={{ cursor: 'pointer' }} + onPointerDown={e => { + 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={<IoMdRedo />} /> - )} - </div> - {/* Undo and Redo */} - <div className="undoRedoContainer"> - <IconButton - style={{ cursor: 'pointer' }} - onPointerDown={e => { - e.stopPropagation(); - handleUndo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Undo" - icon={<IoMdUndo />} - /> - <IconButton - style={{ cursor: 'pointer' }} - onPointerDown={e => { - e.stopPropagation(); - handleRedo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Redo" - icon={<IoMdRedo />} - /> + </div> </div> </div> ); |