aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditor.scss59
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditor.tsx265
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditorButtons.tsx2
-rw-r--r--src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts3
4 files changed, 180 insertions, 149 deletions
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<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>
);
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 (
<div className="imageEditorButtonContainer">
<Button
- style={{ width: '100%', border: '1px' }}
+ style={{ width: '100%' }}
text={tool.name}
type={Type.TERT}
color={isActive ? SettingsManager.userVariantColor : bgColor}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index bd2fac775..660af7c42 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -1,4 +1,5 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../../fields/Doc';
export interface CursorData {
x: number;
@@ -21,7 +22,7 @@ export interface ImageEditTool {
name: string;
btnText: string;
icon: IconProp;
- applyFunc: () => Promise<void>;
+ applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => Promise<void>;
sliderMin?: number;
sliderMax?: number;
sliderDefault?: number;