diff options
Diffstat (limited to 'src/client/views/nodes/generativeFill/generativeFillUtils')
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, +} |