aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts2
-rw-r--r--src/client/views/MainView.tsx11
-rw-r--r--src/client/views/nodes/ImageBox.tsx11
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.scss4
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx157
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx36
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts230
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts5
9 files changed, 273 insertions, 184 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 4b3960902..18222b32a 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -39,7 +39,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
temperature: opts.temp,
prompt: `${opts.prompt}${inputText}`,
});
- console.log(response.data.choices[0]);
+ // console.log(response.data.choices[0]);
return response.data.choices[0].text;
} catch (err) {
console.log(err);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c0c473cfc..1fa1eff84 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -65,6 +65,7 @@ import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -72,6 +73,15 @@ export class MainView extends React.Component {
public static Instance: MainView;
public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
+ // for ai image editor
+ @observable public imageEditorOpen: boolean = false;
+ @action public setImageEditorOpen = (open: boolean) => (this.imageEditorOpen = open);
+ @observable public imageEditorSource: string = '';
+ @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source);
+ @observable public imageRootDoc: Doc | undefined;
+ @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc);
+ @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+
@observable public LastButton: Opt<Doc>;
@observable private _windowWidth: number = 0;
@observable private _windowHeight: number = 0;
@@ -1014,6 +1024,7 @@ export class MainView extends React.Component {
<InkTranscription />
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ <GenerativeFill imageEditorOpen={this.imageEditorOpen} imageEditorSource={this.imageEditorSource} imageRootDoc={this.imageRootDoc} addDoc={this.addDoc} />
</div>
);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b302e7ce..2c6f92681 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -38,6 +38,7 @@ import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
import React = require('react');
import Color = require('color');
+import { MainView } from '../MainView';
export const pageSchema = createSchema({
googlePhotosUrl: 'string',
@@ -248,6 +249,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+ funcs.push({
+ description: 'Open Image Editor',
+ event: action(() => {
+ MainView.Instance.setImageEditorOpen(true);
+ MainView.Instance.setImageEditorSource(this.choosePath(field.url));
+ MainView.Instance.addDoc = this.props.addDocument;
+ MainView.Instance.imageRootDoc = this.rootDoc;
+ }),
+ icon: 'pencil-alt',
+ });
if (!Doc.noviceMode) {
funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0dc186eaf..3fa5c099b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -882,6 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
animateRes = (resIndex: number) => {
+ console.log(this.gptRes);
if (resIndex < this.gptRes.length) {
this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
setTimeout(() => {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
index 92406ba9d..b1e570cf1 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.scss
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss
@@ -6,14 +6,14 @@ $scale: 0.5;
position: absolute;
top: 0;
left: 0;
- z-index: 999;
+ z-index: 9999;
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
overflow: hidden;
- .controls {
+ .generativeFillControls {
flex-shrink: 0;
height: $navHeight;
background-color: #ffffff;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 6dd80a5d1..0df05f4d7 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,14 +3,21 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
import { BrushHandler } from './generativeFillUtils/BrushHandler';
import { IconButton, TextField } from '@mui/material';
import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
-import { activeColor, canvasSize, eraserColor } from './generativeFillUtils/generativeFillConstants';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize } from './generativeFillUtils/generativeFillConstants';
import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { BsBrush, BsEraser } from 'react-icons/bs';
+import { BsBrush, BsEraser, BsX } from 'react-icons/bs';
import { AiOutlineUpload } from 'react-icons/ai';
import { CiUndo, CiRedo } from 'react-icons/ci';
import Buttons from './GenerativeFillButtons';
-import React from 'react';
+import React = require('react');
import './GenerativeFill.scss';
+import { EditableText } from 'browndash-components';
+import { MainView } from '../../MainView';
+import { Doc } from '../../../../fields/Doc';
+import { Networking } from '../../../Network';
+import { Utils } from '../../../../Utils';
+import { DocUtils, Docs } from '../../../documents/Documents';
+import { NumCast } from '../../../../fields/Types';
/**
* For images not 1024x1024 fill in the rest in solid black, or a
@@ -33,7 +40,14 @@ interface ImageEdit {
children: ImageEdit[];
}
-const GenerativeFill = () => {
+interface GenerativeFillProps {
+ imageEditorOpen: boolean;
+ imageEditorSource: string;
+ imageRootDoc: Doc | undefined;
+ addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+}
+
+const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
const drawingAreaRef = useRef<HTMLDivElement>(null);
@@ -51,8 +65,49 @@ const GenerativeFill = () => {
const [loading, setLoading] = useState(false);
// used to store the current image loaded to the main canvas
const currImg = useRef<HTMLImageElement | null>(null);
- // ref to store history
- const undoStack = useRef<ImageData[]>([]);
+ const originalImg = useRef<HTMLImageElement | null>(null);
+ // stores history of data urls
+ const undoStack = useRef<string[]>([]);
+ // stores redo stack
+ const redoStack = useRef<string[]>([]);
+
+ // Undo and Redo
+ const handleUndo = () => {
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx || !currImg.current || !canvasRef.current) return;
+
+ const target = undoStack.current[undoStack.current.length - 1];
+ if (!target) {
+ ImageUtility.drawImgToCanvas(currImg.current, canvasRef);
+ } else {
+ redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
+ const img = new Image();
+ img.src = target;
+ ImageUtility.drawImgToCanvas(img, canvasRef);
+ undoStack.current = undoStack.current.slice(0, -1);
+ }
+ };
+
+ const handleRedo = () => {
+ // TODO: handle undo as well
+ const target = redoStack.current[redoStack.current.length - 1];
+ if (!target) {
+ } else {
+ const img = new Image();
+ img.src = target;
+ ImageUtility.drawImgToCanvas(img, canvasRef);
+ redoStack.current = redoStack.current.slice(0, -1);
+ }
+ };
+
+ const handleReset = () => {
+ if (!canvasRef.current || !currImg.current) return;
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ ctx.clearRect(0, 0, canvasSize, canvasSize);
+ undoStack.current = [];
+ ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
+ };
// initiate brushing
const handlePointerDown = (e: React.PointerEvent) => {
@@ -61,6 +116,9 @@ const GenerativeFill = () => {
const ctx = ImageUtility.getCanvasContext(canvasRef);
if (!ctx) return;
+ undoStack.current = [...undoStack.current, canvasRef.current.toDataURL()];
+ redoStack.current = [];
+
setIsBrushing(true);
const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
@@ -98,17 +156,13 @@ const GenerativeFill = () => {
// first load
useEffect(() => {
+ if (!imageEditorSource || imageEditorSource === '') return;
const img = new Image();
- img.src = '/assets/art.jpeg';
+ img.src = imageEditorSource;
ImageUtility.drawImgToCanvas(img, canvasRef);
currImg.current = img;
- }, [canvasRef]);
-
- useEffect(() => {
- if (!canvasBackgroundRef.current) return;
- const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
- if (!ctx) return;
- }, [canvasBackgroundRef]);
+ originalImg.current = img;
+ }, [canvasRef, imageEditorSource]);
// handles brush sizing
useEffect(() => {
@@ -180,16 +234,9 @@ const GenerativeFill = () => {
const maskBlob = await ImageUtility.canvasToBlob(canvas);
const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
- // const res = await ImageUtility.getEdit(
- // imgBlob,
- // maskBlob,
- // input !== ""
- // ? input + " in the same style"
- // : "Fill in the image in the same style",
- // 1
- // );
+ const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
- const res = await ImageUtility.mockGetEdit();
+ // const res = await ImageUtility.mockGetEdit();
const { urls } = res as APISuccess;
const image = new Image();
image.src = urls[0];
@@ -202,11 +249,47 @@ const GenerativeFill = () => {
}
};
+ const onSave = async () => {
+ if (!currImg.current || !imageRootDoc) return;
+ try {
+ const src = currImg.current.src;
+ console.log(src);
+ const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+ const source = Utils.prepend(result.accessPaths.agnostic.client);
+ console.log(source);
+ const newImg = Docs.Create.ImageDocument(source, {
+ x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20,
+ y: NumCast(imageRootDoc.y),
+ _height: freeformRenderSize,
+ _width: freeformRenderSize,
+ data_nativeWidth: result.nativeWidth,
+ data_nativeHeight: result.nativeHeight,
+ });
+
+ addDoc?.(newImg);
+ // Create link between prompt and image
+ DocUtils.MakeLink(imageRootDoc, newImg, { link_relationship: 'Image Edit' });
+ console.log('done');
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
return (
- <div className="generativeFillContainer">
- <div className="controls">
+ <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
+ <div className="generativeFillControls">
<h1>Generative Fill</h1>
- <Buttons canvasRef={canvasRef} backgroundref={canvasBackgroundRef} currImg={currImg} getEdit={getEdit} undoStack={undoStack} loading={loading} />
+ <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
+ <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
+ <IconButton
+ onClick={() => {
+ MainView.Instance.setImageEditorOpen(false);
+ MainView.Instance.setImageEditorSource('');
+ setEdits([]);
+ }}>
+ <BsX color={activeColor} />
+ </IconButton>
+ </div>
</div>
{/* Main canvas for editing */}
<div
@@ -245,26 +328,33 @@ const GenerativeFill = () => {
}}>
<BsBrush color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
</IconButton>
- <IconButton
+ {/* <IconButton
onClick={() => {
setBrushStyle(BrushStyle.SUBTRACT);
}}>
<BsEraser color={brushStyle === BrushStyle.SUBTRACT ? activeColor : 'inherit'} />
- </IconButton>
+ </IconButton> */}
{/* Undo and Redo */}
- {/* <IconButton
+ <IconButton
onPointerDown={e => {
e.stopPropagation();
- console.log(undoStack.current);
+ handleUndo();
}}
onPointerUp={e => {
e.stopPropagation();
}}>
<CiUndo />
</IconButton>
- <IconButton onClick={() => {}}>
+ <IconButton
+ onPointerDown={e => {
+ e.stopPropagation();
+ handleRedo();
+ }}
+ onPointerUp={e => {
+ e.stopPropagation();
+ }}>
<CiRedo />
- </IconButton> */}
+ </IconButton>
</div>
{/* Edits box */}
<div className="editsBox">
@@ -292,12 +382,15 @@ const GenerativeFill = () => {
type="text"
label="Prompt"
placeholder="Prompt..."
+ InputLabelProps={{ style: { fontSize: '1.5rem' } }}
+ inputProps={{ style: { fontSize: '1.5rem' } }}
sx={{
backgroundColor: '#ffffff',
position: 'absolute',
bottom: '1rem',
transform: 'translateX(calc(50vw - 50%))',
width: 'calc(100vw - 4rem)',
+ scale: 1.2,
}}
/>
</div>
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 348e27a16..e8cb61ab5 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -3,44 +3,34 @@ import { ImageUtility } from './generativeFillUtils/ImageHandler';
import { canvasSize } from './generativeFillUtils/generativeFillConstants';
import { Oval } from 'react-loader-spinner';
import './GenerativeFillButtons.scss';
-import React from 'react';
+import React = require('react');
+import { Doc } from '../../../../fields/Doc';
interface ButtonContainerProps {
canvasRef: React.RefObject<HTMLCanvasElement>;
- backgroundref: React.RefObject<HTMLCanvasElement>;
currImg: React.MutableRefObject<HTMLImageElement | null>;
- undoStack: React.MutableRefObject<ImageData[]>;
getEdit: () => Promise<void>;
loading: boolean;
+ onSave: () => Promise<void>;
+ onReset: () => void;
}
-const Buttons = ({ canvasRef, backgroundref, currImg, undoStack, loading, getEdit }: ButtonContainerProps) => {
- const handleReset = () => {
- if (!canvasRef.current || !currImg.current) return;
- const ctx = ImageUtility.getCanvasContext(canvasRef);
- if (!ctx) return;
- ctx.clearRect(0, 0, canvasSize, canvasSize);
- ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
+const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: ButtonContainerProps) => {
+ const handleSave = () => {
+ onSave();
};
return (
<div className="generativeFillBtnContainer">
- <Button onClick={handleReset}>Reset</Button>
- <Button
+ <Button onClick={onReset}>Reset</Button>
+ <Button onClick={handleSave}>Save</Button>
+ {/* <Button
onClick={() => {
if (!canvasRef.current) return;
- ImageUtility.downloadCanvas(canvasRef.current);
+ ImageUtility.downloadImageCanvas('/assets/firefly.png');
}}>
- Download
- </Button>
- {/* <Button
- onClick={() => {
- if (!canvasRef.current) return;
- ImageUtility.downloadImageCanvas("/assets/firefly.png");
- }}
- >
- Download Original
- </Button> */}
+ Download Original
+ </Button> */}
<Button
variant="contained"
onClick={() => {
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 1c726afbb..91f12f866 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -1,146 +1,128 @@
-import { RefObject } from "react";
-import { OPENAI_KEY } from "../keys";
-import { canvasSize } from "./generativeFillConstants";
+import { RefObject } from 'react';
+import { canvasSize } from './generativeFillConstants';
export interface APISuccess {
- status: "success";
- urls: string[];
+ status: 'success';
+ urls: string[];
}
export interface APIError {
- status: "error";
- message: string;
+ status: 'error';
+ message: string;
}
export class ImageUtility {
- static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
- return new Promise((resolve) => {
- canvas.toBlob((blob) => {
- if (blob) {
- resolve(blob);
- }
- }, "image/png");
- });
- };
+ static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
+ return new Promise(resolve => {
+ canvas.toBlob(blob => {
+ if (blob) {
+ resolve(blob);
+ }
+ }, 'image/png');
+ });
+ };
- static getEdit = async (
- imgBlob: Blob,
- maskBlob: Blob,
- prompt: string,
- n?: number
- ): Promise<APISuccess | APIError> => {
- const apiUrl = "https://api.openai.com/v1/images/edits";
- const fd = new FormData();
- fd.append("image", imgBlob, "image.png");
- fd.append("mask", maskBlob, "mask.png");
- fd.append("prompt", prompt);
- fd.append("size", "1024x1024");
- fd.append("n", n ? JSON.stringify(n) : "1");
- fd.append("response_format", "b64_json");
+ static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+ const apiUrl = 'https://api.openai.com/v1/images/edits';
+ const fd = new FormData();
+ fd.append('image', imgBlob, 'image.png');
+ fd.append('mask', maskBlob, 'mask.png');
+ fd.append('prompt', prompt);
+ fd.append('size', '1024x1024');
+ fd.append('n', n ? JSON.stringify(n) : '1');
+ fd.append('response_format', 'b64_json');
- try {
- const res = await fetch(apiUrl, {
- method: "POST",
- headers: {
- Authorization: `Bearer ${OPENAI_KEY}`,
- },
- body: fd,
- });
- const data = await res.json();
- console.log(data.data);
- return {
- status: "success",
- urls: (data.data as { b64_json: string }[]).map(
- (data) => `data:image/png;base64,${data.b64_json}`
- ),
- };
- } catch (err) {
- console.log(err);
- return { status: "error", message: "API error." };
- }
- };
+ try {
+ const res = await fetch(apiUrl, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${process.env.OPENAI_KEY}`,
+ },
+ body: fd,
+ });
+ const data = await res.json();
+ console.log(data.data);
+ return {
+ status: 'success',
+ urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+ };
+ } catch (err) {
+ console.log(err);
+ return { status: 'error', message: 'API error.' };
+ }
+ };
- static mockGetEdit = async (): Promise<APISuccess | APIError> => {
- return {
- status: "success",
- urls: [
- "/assets/shiba.png",
- "/assets/souffle-dalle.png",
- "/assets/firefly.png",
- ],
+ static mockGetEdit = async (): Promise<APISuccess | APIError> => {
+ return {
+ status: 'success',
+ urls: ['/assets/shiba.png', '/assets/souffle-dalle.png', '/assets/firefly.png'],
+ };
};
- };
- static getCanvasContext = (
- canvasRef: RefObject<HTMLCanvasElement>
- ): CanvasRenderingContext2D | null => {
- if (!canvasRef.current) return null;
- const ctx = canvasRef.current.getContext("2d");
- if (!ctx) return null;
- return ctx;
- };
+ static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+ if (!canvasRef.current) return null;
+ const ctx = canvasRef.current.getContext('2d');
+ if (!ctx) return null;
+ return ctx;
+ };
- static downloadCanvas = (canvas: HTMLCanvasElement) => {
- const url = canvas.toDataURL();
- const downloadLink = document.createElement("a");
- downloadLink.href = url;
- downloadLink.download = "canvas";
+ static downloadCanvas = (canvas: HTMLCanvasElement) => {
+ const url = canvas.toDataURL();
+ const downloadLink = document.createElement('a');
+ downloadLink.href = url;
+ downloadLink.download = 'canvas';
- downloadLink.click();
- downloadLink.remove();
- };
+ downloadLink.click();
+ downloadLink.remove();
+ };
- static downloadImageCanvas = (imgUrl: string) => {
- const img = new Image();
- img.src = imgUrl;
- img.onload = () => {
- const canvas = document.createElement("canvas");
- canvas.width = canvasSize;
- canvas.height = canvasSize;
- const ctx = canvas.getContext("2d");
- ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+ static downloadImageCanvas = (imgUrl: string) => {
+ const img = new Image();
+ img.src = imgUrl;
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = canvasSize;
+ canvas.height = canvasSize;
+ const ctx = canvas.getContext('2d');
+ ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
- this.downloadCanvas(canvas);
+ this.downloadCanvas(canvas);
+ };
};
- };
- static drawImgToCanvas = (
- img: HTMLImageElement,
- canvasRef: React.RefObject<HTMLCanvasElement>,
- loaded?: boolean
- ) => {
- if (loaded) {
- const ctx = this.getCanvasContext(canvasRef);
- if (!ctx) return;
- ctx.globalCompositeOperation = "source-over";
- const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
- const width = img.width * scale;
- const height = img.height * scale;
- ctx.clearRect(0, 0, canvasSize, canvasSize);
- ctx.drawImage(img, 0, 0, width, height);
- } else {
- img.onload = () => {
- const ctx = this.getCanvasContext(canvasRef);
- if (!ctx) return;
- ctx.globalCompositeOperation = "source-over";
- const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
- const width = img.width * scale;
- const height = img.height * scale;
- ctx.clearRect(0, 0, canvasSize, canvasSize);
- ctx.drawImage(img, 0, 0, width, height);
- };
- }
- };
+ static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, loaded?: boolean) => {
+ if (loaded) {
+ const ctx = this.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ ctx.globalCompositeOperation = 'source-over';
+ const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
+ const width = img.width * scale;
+ const height = img.height * scale;
+ ctx.clearRect(0, 0, canvasSize, canvasSize);
+ ctx.drawImage(img, 0, 0, width, height);
+ } else {
+ img.onload = () => {
+ const ctx = this.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ ctx.globalCompositeOperation = 'source-over';
+ const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
+ const width = img.width * scale;
+ const height = img.height * scale;
+ ctx.clearRect(0, 0, canvasSize, canvasSize);
+ ctx.drawImage(img, 0, 0, width, height);
+ };
+ }
+ };
- // The image must be loaded!
- static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => {
- const canvas = document.createElement("canvas");
- canvas.width = canvasSize;
- canvas.height = canvasSize;
- const ctx = canvas.getContext("2d");
- ctx?.clearRect(0, 0, canvasSize, canvasSize);
- ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+ // The image must be loaded!
+ static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => {
+ const canvas = document.createElement('canvas');
+ canvas.width = canvasSize;
+ canvas.height = canvasSize;
+ const ctx = canvas.getContext('2d');
+ ctx?.clearRect(0, 0, canvasSize, canvasSize);
+ ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
- return canvas;
- };
+ return canvas;
+ };
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 78903b9aa..fe668ef6f 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,5 +1,6 @@
// constants
export const canvasSize = 1024;
+export const freeformRenderSize = 200;
-export const activeColor = "#1976d2";
-export const eraserColor = "#e1e9ec";
+export const activeColor = '#1976d2';
+export const eraserColor = '#e1e9ec';