diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/imageEditor/ImageEditor.tsx | 137 | ||||
-rw-r--r-- | src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts | 18 |
2 files changed, 82 insertions, 73 deletions
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>(BrushMode.IN); + const [cutType, setCutType] = useState<CutMode>(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); }} /> <Button style={{ width: '100%' }} text="Keep out" type={Type.TERT} - color={cutType == BrushMode.OUT ? SettingsManager.userColor : bgColor} + color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor} onClick={() => { - setCutType(BrushMode.OUT); + setCutType(CutMode.OUT); }} /> <Button style={{ width: '100%' }} text="Draw in" type={Type.TERT} - color={cutType == BrushMode.LINE_IN ? SettingsManager.userColor : bgColor} + color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor} onClick={() => { - setCutType(BrushMode.LINE_IN); + setCutType(CutMode.DRAW_IN); }} /> <Button style={{ width: '100%' }} text="Erase" type={Type.TERT} - color={cutType == BrushMode.LINE_OUT ? SettingsManager.userColor : bgColor} + color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor} onClick={() => { - setCutType(BrushMode.LINE_OUT); + setCutType(CutMode.ERASE); }} /> </div> diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts index 75659fc53..a14b55439 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts @@ -17,24 +17,26 @@ export enum ImageToolType { Cut = 'cut', } +export enum CutMode { + IN, + OUT, + DRAW_IN, + ERASE, +} + export interface ImageEditTool { type: ImageToolType; name: string; btnText: string; icon: IconProp; - applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise<void>; + // this is the function that the image tool applies, so it can be defined depending on the tool + applyFunc: (currCutType: CutMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise<void>; + // these optional parameters are here because different tools require different brush sizes and defaults sliderMin?: number; sliderMax?: number; sliderDefault?: number; } -export enum BrushMode { - IN, - OUT, - LINE_IN, - LINE_OUT, -} - export interface ImageDimensions { width: number; height: number; |