From 3b17868560090756caf8b9b0f043ea163f2320e8 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 20 Oct 2024 12:44:15 -0400 Subject: changes --- .../imageEditorUtils/imageEditorInterfaces.ts | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts (limited to 'src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts') diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts new file mode 100644 index 000000000..f4ae7d9c4 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts @@ -0,0 +1,38 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; + +export interface CursorData { + x: number; + y: number; + width: number; +} + +export interface Point { + x: number; + y: number; +} + +export enum ImageToolType { + GenerativeFill = 'genFill', + Cut = 'cut', +} + +export interface ImageEditTool { + type: ImageToolType; + name: string; + btnText: string; + icon: IconProp; + applyFunc: () => Promise; + sliderMin?: number; + sliderMax?: number; + sliderDefault?: number; +} + +export enum BrushMode { + ADD, + SUBTRACT, +} + +export interface ImageDimensions { + width: number; + height: number; +} -- 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/imageEditorUtils/imageEditorInterfaces.ts') 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/imageEditorUtils/imageEditorInterfaces.ts') 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 (
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts index 660af7c42..75659fc53 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts @@ -22,7 +22,7 @@ export interface ImageEditTool { name: string; btnText: string; icon: IconProp; - applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => Promise; + applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise; sliderMin?: number; sliderMax?: number; sliderDefault?: number; -- cgit v1.2.3-70-g09d2 From e6a4b7c05899ee6d6a821666b72023856081d5ca Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Fri, 13 Dec 2024 17:45:34 -0500 Subject: added comments, refactored duplicate code, and resolved lint errors --- src/client/views/nodes/imageEditor/ImageEditor.tsx | 137 +++++++++++---------- .../imageEditorUtils/imageEditorInterfaces.ts | 18 +-- 2 files changed, 82 insertions(+), 73 deletions(-) (limited to 'src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts') diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 5c4c83eef..a742673e0 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -22,11 +22,11 @@ import { ImageEditorData } from '../ImageBox'; import { OpenWhereMod } from '../OpenWhere'; import './ImageEditor.scss'; import { ApplyFuncButtons, ImageToolButton } from './ImageEditorButtons'; -import { BrushHandler, BrushType } from './imageEditorUtils/BrushHandler'; +import { BrushHandler } from './imageEditorUtils/BrushHandler'; import { APISuccess, ImageUtility } from './imageEditorUtils/ImageHandler'; import { PointerHandler } from './imageEditorUtils/PointerHandler'; -import { activeColor, bgColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants'; -import { BrushMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces'; +import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants'; +import { CutMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces'; import { DocumentView } from '../DocumentView'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ImageField } from '../../../../fields/URLField'; @@ -64,7 +64,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc width: canvasSize, height: canvasSize, }); - const [cutType, setCutType] = useState(BrushMode.IN); + const [cutType, setCutType] = useState(CutMode.IN); // whether to create a new collection or not const [isNewCollection, setIsNewCollection] = useState(true); // the current image in the main canvas @@ -183,7 +183,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc x: currPoint.x - e.movementX / canvasScale, y: currPoint.y - e.movementY / canvasScale, }; - const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, BrushType.CUT); + const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor); cutPts.current.push(...pts); }; @@ -283,7 +283,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc })); }; - // Get AI Edit + // Get AI Edit for Generative Fill const getEdit = async () => { const img = currImg.current; if (!img) return; @@ -304,32 +304,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // create first image if (!newCollectionRef.current) { - if (!isNewCollection && imageRootDoc) { - // if the parent hasn't been set yet - if (!parentDoc.current) parentDoc.current = imageRootDoc; - } else { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); - - // opening new tab - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - - // add the doc to the main freeform - // eslint-disable-next-line no-use-before-define - await createNewImgDoc(originalImg.current, true); - } + createNewCollection(); } else { childrenDocs.current = []; } - + if (!(originalImg.current && imageRootDoc)) return; + // add the doc to the main freeform + // eslint-disable-next-line no-use-before-define + await createNewImgDoc(originalImg.current, true); originalImg.current = currImg.current; originalDoc.current = parentDoc.current; const { urls } = res as APISuccess; @@ -356,23 +338,32 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc setLoading(false); }; - const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], firstDoc: boolean) => { + /** + * This function performs image cutting based on the inputted BrushMode. There are currently four ways to cut images: + * 1. By outlining the area that should be kept (BrushMode.IN) + * 2. By outlining the area that should be removed (BrushMode.OUT) + * 3. By drawing in the area that should be kept (where the image is brushed, the image will remain and everything else will be removed) (BrushMode.DRAW_IN) + * 4. By drawing the area that she be removed, so this operates as an eraser (BrushMode.ERASE) + * @param currCutType BrushMode enum that determines what kind of cutting operation to perform + * @param firstDoc boolean for whether it's the first edited image. This is for positioning of the edited images when they render on the canvas. + */ + const cutImage = async (currCutType: CutMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], firstDoc: boolean) => { const img = currImg.current; const canvas = canvasRef.current; if (!canvas || !img) return; const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return; - setLoading(true); // get the original image const canvasOriginalImg = ImageUtility.getCanvasImg(img); if (!canvasOriginalImg) return; - // NOTE: cutting two diff shapes can be made possible by having the user press a button to set a new shape! + setLoading(true); const currPts = [...cutPts.current]; - if (currCutType !== BrushMode.LINE_OUT) handleReset(); // gets rid of the visible brush strokes (mostly needed for line_in) unless it's erasing (which depends on the brush strokes) + if (currCutType !== CutMode.ERASE) handleReset(); // gets rid of the visible brush strokes (mostly needed for line_in) unless it's erasing (which depends on the brush strokes) let minX = img.width; let maxX = 0; let minY = img.height; let maxY = 0; + // currPts is populated by the brush strokes' points, so this code is drawing a path along the points if (currPts.length) { ctx.beginPath(); ctx.moveTo(currPts[0].x, currPts[0].y); @@ -383,44 +374,30 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc maxX = Math.max(currPts[i].x, maxX); maxY = Math.max(currPts[i].y, maxY); } - switch (currCutType) { - case BrushMode.IN: + switch ( + currCutType // use different canvas operations depending on the type of cutting we're applying + ) { + case CutMode.IN: ctx.closePath(); ctx.globalCompositeOperation = 'destination-in'; ctx.fill(); break; - case BrushMode.OUT: + case CutMode.OUT: ctx.closePath(); ctx.globalCompositeOperation = 'destination-out'; ctx.fill(); break; - case BrushMode.LINE_IN: + case CutMode.DRAW_IN: ctx.globalCompositeOperation = 'destination-in'; - ctx.lineWidth = brushWidth + 10; // added offset because width gets cut off a little bit + ctx.lineWidth = brushWidth + brushWidthOffset; // added offset because width gets cut off a little bit ctx.stroke(); break; } } - const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl + const url = canvas.toDataURL(); if (!newCollectionRef.current) { - if (!isNewCollection && imageRootDoc) { - // if the parent hasn't been set yet - if (!parentDoc.current) parentDoc.current = imageRootDoc; - } else { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); - // opening new tab - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - } + createNewCollection(); } const image = new Image(); @@ -428,7 +405,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc image.onload = async () => { let finalImg: HTMLImageElement | undefined = image; let finalImgURL: string = url; - if (currCutType == BrushMode.IN || currCutType == BrushMode.LINE_IN) { + // crop the image for these brush modes to remove excess blank space around the image contents + if (currCutType == CutMode.IN || currCutType == CutMode.DRAW_IN) { const croppedData = cropImage(image, minX, maxX, minY, maxY); finalImg = croppedData; finalImgURL = croppedData.src; @@ -436,6 +414,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc currImg.current = finalImg; const newImgDoc = await createNewImgDoc(finalImg, firstDoc); if (newImgDoc) { + // set the image to transparent to remove the background / brushstrokes const docData = newImgDoc[DocData]; docData.backgroundColor = 'transparent'; docData.disableMixBlend = true; @@ -447,6 +426,34 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }; }; + /** + * Creates a new collection to put the image edits on. Adds to a new tab on the right if "Create New Collection" is checked. + * @returns + */ + const createNewCollection = () => { + if (!isNewCollection && imageRootDoc) { + // if the parent hasn't been set yet + if (!parentDoc.current) parentDoc.current = imageRootDoc; + } else { + if (!(originalImg.current && imageRootDoc)) return; + // create new collection and add it to the view + newCollectionRef.current = Docs.Create.FreeformDocument([], { + x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, + y: NumCast(imageRootDoc.y), + _width: newCollectionSize, + _height: newCollectionSize, + title: 'Image edit collection', + }); + DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); + // opening new tab + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + } + }; + + /** + * This function crops an image based on the inputted dimensions. This is used to automatically adjust the images that are + * edited to be smaller than the original (i.e. for cutting into a small part of the image) + */ const cropImage = (image: HTMLImageElement, minX: number, maxX: number, minY: number, maxY: number) => { const croppedCanvas = document.createElement('canvas'); const croppedCtx = croppedCanvas.getContext('2d'); @@ -617,36 +624,36 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc style={{ width: '100%' }} text="Keep in" type={Type.TERT} - color={cutType == BrushMode.IN ? SettingsManager.userColor : bgColor} + color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor} onClick={() => { - setCutType(BrushMode.IN); + setCutType(CutMode.IN); }} />