aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/generativeFill/generativeFillUtils
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/generativeFill/generativeFillUtils')
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts87
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts11
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts146
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts15
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts5
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts16
6 files changed, 280 insertions, 0 deletions
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
new file mode 100644
index 000000000..c2716e083
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -0,0 +1,87 @@
+import { GenerativeFillMathHelpers } from "./GenerativeFillMathHelpers";
+import { eraserColor } from "./generativeFillConstants";
+import { Point } from "./generativeFillInterfaces";
+
+export class BrushHandler {
+ static brushCircle = (
+ x: number,
+ y: number,
+ brushRadius: number,
+ ctx: CanvasRenderingContext2D
+ ) => {
+ ctx.globalCompositeOperation = "destination-out";
+ ctx.shadowColor = "#ffffffeb";
+ ctx.shadowBlur = 5;
+ ctx.beginPath();
+ ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.closePath();
+ };
+
+ static brushCircleOverlay = (
+ x: number,
+ y: number,
+ brushRadius: number,
+ ctx: CanvasRenderingContext2D,
+ fillColor: string,
+ erase: boolean
+ ) => {
+ ctx.globalCompositeOperation = "destination-out";
+ // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over";
+ ctx.fillStyle = fillColor;
+ ctx.shadowColor = eraserColor;
+ ctx.shadowBlur = 5;
+ ctx.beginPath();
+ ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.closePath();
+ };
+
+ static createBrushPath = (
+ startPoint: Point,
+ endPoint: Point,
+ brushRadius: number,
+ ctx: CanvasRenderingContext2D
+ ) => {
+ const dist = GenerativeFillMathHelpers.distanceBetween(
+ startPoint,
+ endPoint
+ );
+
+ for (let i = 0; i < dist; i += 5) {
+ const s = i / dist;
+ BrushHandler.brushCircle(
+ startPoint.x * (1 - s) + endPoint.x * s,
+ startPoint.y * (1 - s) + endPoint.y * s,
+ brushRadius,
+ ctx
+ );
+ }
+ };
+
+ static createBrushPathOverlay = (
+ startPoint: Point,
+ endPoint: Point,
+ brushRadius: number,
+ ctx: CanvasRenderingContext2D,
+ fillColor: string,
+ erase: boolean
+ ) => {
+ const dist = GenerativeFillMathHelpers.distanceBetween(
+ startPoint,
+ endPoint
+ );
+
+ for (let i = 0; i < dist; i += 5) {
+ const s = i / dist;
+ BrushHandler.brushCircleOverlay(
+ startPoint.x * (1 - s) + endPoint.x * s,
+ startPoint.y * (1 - s) + endPoint.y * s,
+ brushRadius,
+ ctx,
+ fillColor,
+ erase
+ );
+ }
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
new file mode 100644
index 000000000..027b99a52
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -0,0 +1,11 @@
+import { Point } from "./generativeFillInterfaces";
+
+export class GenerativeFillMathHelpers {
+ // math helpers
+ static distanceBetween = (p1: Point, p2: Point) => {
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ };
+ static angleBetween = (p1: Point, p2: Point) => {
+ return Math.atan2(p2.x - p1.x, p2.y - p1.y);
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
new file mode 100644
index 000000000..1c726afbb
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -0,0 +1,146 @@
+import { RefObject } from "react";
+import { OPENAI_KEY } from "../keys";
+import { canvasSize } from "./generativeFillConstants";
+
+export interface APISuccess {
+ status: "success";
+ urls: string[];
+}
+
+export interface APIError {
+ 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 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." };
+ }
+ };
+
+ 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 downloadCanvas = (canvas: HTMLCanvasElement) => {
+ const url = canvas.toDataURL();
+ const downloadLink = document.createElement("a");
+ downloadLink.href = url;
+ downloadLink.download = "canvas";
+
+ 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);
+
+ 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);
+ };
+ }
+ };
+
+ // 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;
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
new file mode 100644
index 000000000..9e620ad11
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -0,0 +1,15 @@
+import { Point } from "./generativeFillInterfaces";
+
+export class PointerHandler {
+ static getPointRelativeToElement = (
+ element: HTMLElement,
+ e: React.PointerEvent | PointerEvent,
+ scale: number
+ ): Point => {
+ const boundingBox = element.getBoundingClientRect();
+ return {
+ x: (e.clientX - boundingBox.x) / scale,
+ y: (e.clientY - boundingBox.y) / scale,
+ };
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
new file mode 100644
index 000000000..78903b9aa
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -0,0 +1,5 @@
+// constants
+export const canvasSize = 1024;
+
+export const activeColor = "#1976d2";
+export const eraserColor = "#e1e9ec";
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
new file mode 100644
index 000000000..9b9b9d3c2
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
@@ -0,0 +1,16 @@
+// interfaces
+export interface CursorData {
+ x: number;
+ y: number;
+ width: number;
+}
+
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export enum BrushMode {
+ ADD,
+ SUBTRACT,
+}