Skip to content

Commit

Permalink
Working state
Browse files Browse the repository at this point in the history
  • Loading branch information
Ubuntu committed Dec 12, 2023
1 parent 960cc02 commit 0be7e33
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Redo,
ArrowDownToLine,
ArrowLeftSquare,
MoveUpRight,
} from "lucide-svelte";
import { onMount } from "svelte";
import paper from "paper";
Expand All @@ -24,10 +25,14 @@
let isImageUploaded = false;
let firstImageGenerated = false;
let isDrawing = false;
// we track lastUpdatedAt so that expired requests don't overwrite the latest
let lastUpdatedAt = 0;
// used for undo/redo functionality
let outputImageHistory: string[] = [];
$: currentOutputImageIndex = -1;
$: isLoading = false;
$: brushSize = "sm";
Expand All @@ -46,6 +51,10 @@
};
onMount(() => {
/*
Setup paper.js for canvas which is a layer above our input image.
Paper is used for drawing/paint functionality.
*/
paper.setup(canvasDrawLayer);
const tool = new paper.Tool();
Expand All @@ -57,22 +66,23 @@
path.strokeWidth = radiusByBrushSize[brushSize] * 4;
path.add(event.point);
isDrawing = true;
throttledGetNextFrameLoop();
throttledGenerateOutputImage();
};
tool.onMouseDrag = (event: paper.ToolEvent) => {
path.add(event.point);
isDrawing = true;
throttledGetNextFrameLoop();
throttledGenerateOutputImage();
};
imgInput.onload = () => {
resizeImage(imgInput);
isImageUploaded = true;
// kick off an inference on first image load so output image is populated as well
// otherwise it will be empty
if (!firstImageGenerated) {
getNextFrameLoop();
GenerateOutputImage();
firstImageGenerated = true;
}
};
Expand All @@ -83,6 +93,9 @@
imgInput.src = defaultInputImage;
});
// Our images need to be sized 320x320 for both input and output
// This is important because we combine the canvas layer with the image layer
// so the pixels need to matchup.
const resizeImage = (img: HTMLImageElement) => {
let newWidth;
let newHeight;
Expand Down Expand Up @@ -117,10 +130,12 @@
reader.readAsDataURL(file);
getNextFrameLoop();
GenerateOutputImage();
}
}
// combines the canvas with the input image so that the
// generated image contains edits made by paint brush
function getCombinedImageData() {
const tempCanvas = document.createElement("canvas");
tempCanvas.width = 320;
Expand All @@ -133,18 +148,18 @@
return tempCanvas.toDataURL("image/jpeg");
}
const throttledGetNextFrameLoop = throttle(
const throttledGenerateOutputImage = throttle(
250,
() => {
getNextFrameLoop();
GenerateOutputImage();
},
{ noLoading: false, noTrailing: false },
);
const debouncedGetNextFrameLoop = debounce(
const debouncedGenerateOutputImage = debounce(
100,
() => {
getNextFrameLoop();
GenerateOutputImage();
},
{ atBegin: false },
);
Expand Down Expand Up @@ -174,7 +189,7 @@
}
};
const getNextFrameLoop = () => {
const GenerateOutputImage = () => {
isLoading = true;
const data = getCombinedImageData();
const sentAt = new Date().getTime();
Expand Down Expand Up @@ -211,11 +226,13 @@
<h2 class="text-2xl font-medium">SDXL Turbo Image2Image</h2>
<div class="font-sm">example description</div>
</div>
<div class="button py-2 px-5"><Github size={16} />View Code</div>
<div class="button py-2 px-5 font-medium">
<Github size={16} />View Code
</div>
</div>

<div class="mt-6">
<div class="mb-6 font-medium">Input</div>
<div class="mb-6 font-semibold">Input</div>
<input
type="file"
accept="image/*"
Expand All @@ -233,20 +250,20 @@
</div>

<div class="mt-6">
<h3 class="mb-6">Prompt</h3>
<h3 class="mb-6 font-semibold">Prompt</h3>
<input
class="rounded-lg border border-white/20 bg-white/10 py-4 px-6 outline-none w-full"
bind:value
on:input={debouncedGetNextFrameLoop}
on:input={debouncedGenerateOutputImage}
/>
</div>

<div class="mt-6 flex items-center">
<div class="pr-[72px] border-r border-white/10 flex items-center">
<div class="pr-7 border-r border-white/10 flex items-center">
<div>
<div>
<div class="mb-2">Canvas</div>
<div>Draw on the image and generate a new one</div>
<div class="pb-6">
<div class="mb-2 font-medium">Canvas</div>
<div>Draw on the image to generate a new one</div>
</div>

<img
Expand All @@ -263,53 +280,60 @@
/>
</div>

<div class="ml-6">
<div class="ml-6 mt-4">
<Paint
{paint}
{brushSize}
on:clearCanvas={() => {
paper.project.activeLayer.removeChildren();
paper.view.update();
getNextFrameLoop();
GenerateOutputImage();
}}
on:setPaint={setPaint}
on:setBrushSize={setBrushSize}
/>
</div>
</div>

<div class="pl-[72px]">
<div class="pl-7 flex">
<div>
<div class="mb-2 flex items-center gap-1">
Output
{#if isLoading}
<Loader size={14} class="animate-spin" />
{/if}
</div>
<div class="flex justify-between">
<div class="pb-6">
<div class="mb-2 flex items-center gap-1 font-medium">
Output
{#if isLoading}
<Loader size={14} class="animate-spin" />
{/if}
</div>
<div>Generated Image</div>
<div class="flex">
<button on:click={movetoCanvas}
><ArrowLeftSquare size={16} />Move to Canvas</button
</div>

<img
alt="output"
bind:this={imgOutput}
class="w-[320px] h-[320px] bg-[#D9D9D9]"
class:hidden={!firstImageGenerated}
/>
</div>
<div class="flex justify-between ml-6 items-center">
<div class="flex flex-col gap-4 mb-[108px]">
<div class="btns-container justify-space-between">
<button class="text-xs flex gap-1.5" on:click={undoOutputImage}
><Undo size={16} />Back</button
>
<button on:click={undoOutputImage}><Undo size={16} /></button>
<button on:click={redoOutputImage}><Redo size={16} /></button>
<button on:click={downloadImage}
><ArrowDownToLine size={16} /></button
<div class="w-[1px] h-4 bg-white/10" />
<button class="text-xs flex gap-1.5" on:click={redoOutputImage}
><Redo size={16} />Next</button
>
</div>
<button class="text-xs btns-container" on:click={movetoCanvas}>
<ArrowLeftSquare size={16} />Move to Canvas
</button>

<button class="text-xs btns-container" on:click={downloadImage}>
<ArrowDownToLine size={16} /> Download
</button>
</div>
</div>

<img
alt="output"
bind:this={imgOutput}
class="w-[320px] h-[320px] bg-[#D9D9D9]"
class:hidden={!firstImageGenerated}
/>
{#if !firstImageGenerated}
<div class="w-[320px] h-[320px] bg-[#D9D9D9]" />
{/if}
</div>
</div>
</div>
Expand All @@ -322,9 +346,12 @@
src={modalLogoWithText}
/>
</div>
<a href="https://modal.com/docs/guide" class="button px-5 py-[6px]"
>Get Started</a
<a
href="https://modal.com/docs/guide"
class="button px-5 py-[6px] font-medium"
>
Get Started <MoveUpRight size={16} />
</a>
</div>
</main>

Expand All @@ -333,6 +360,11 @@
@apply bg-white/10 border border-white/20 rounded-lg p-6 max-w-screen-lg;
}
.btns-container {
@apply flex items-center gap-2.5 py-2 px-3 border rounded-[10px] border-white/5 bg-white/10;
width: 144px;
}
.button {
@apply border border-primary bg-primary/20 rounded-lg justify-center items-center flex gap-2 cursor-pointer;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import { Eraser } from "lucide-svelte";
import smallPaintIcon from "$lib/assets/sm-paint-icon.svg";
import extraSmallPaintIcon from "$lib/assets/xs-paint-icon.svg";
import mediumPaintIcon from "$lib/assets/md-paint-icon.svg";
Expand Down Expand Up @@ -26,8 +28,11 @@
<div class="flex flex-col">
<div class="container">
<div class="flex justify-between w-full">
<button style="font-size:10px" on:click={() => handleClearCanvas()}>
Clear paint
<button
class="flex items-center gap-2.5 w-full text-sm"
on:click={() => handleClearCanvas()}
>
<Eraser size={12} /> Clear
</button>
</div>
</div>
Expand Down Expand Up @@ -86,26 +91,46 @@
class="flex border-white/10 border-t pt-2.5 justify-between w-full"
>
<button on:click={() => handleSetBrushSize("xs")}>
<img class="brush" class:brush-active={brushSize === "xs"} src={extraSmallPaintIcon} alt="extrasmall paint icon" />
<img
class="brush"
class:brush-active={brushSize === "xs"}
src={extraSmallPaintIcon}
alt="extrasmall paint icon"
/>
</button>
<button on:click={() => handleSetBrushSize("sm")}>
<img class="brush" class:brush-active={brushSize === "sm"} src={smallPaintIcon} alt="small paint icon" />
<img
class="brush"
class:brush-active={brushSize === "sm"}
src={smallPaintIcon}
alt="small paint icon"
/>
</button>
</div>
<div class="flex justify-between w-full">
<button on:click={() => handleSetBrushSize("md")}>
<img class="brush" class:brush-active={brushSize === "md"} src={mediumPaintIcon} alt="medium paint icon" />
<img
class="brush"
class:brush-active={brushSize === "md"}
src={mediumPaintIcon}
alt="medium paint icon"
/>
</button>
<button on:click={() => handleSetBrushSize("lg")}>
<img class="brush" class:brush-active={brushSize === "lg"} src={largePaintIcon} alt="large paint icon" />
<img
class="brush"
class:brush-active={brushSize === "lg"}
src={largePaintIcon}
alt="large paint icon"
/>
</button>
</div>
</div>
</div>

<style lang="postcss">
.container {
@apply flex flex-col items-center gap-2.5 py-3 px-4 border rounded-[10px] border-white/5 bg-white/10;
@apply flex flex-col items-center gap-2.5 py-2 px-3 border rounded-[10px] border-white/5 bg-white/10;
width: 88px;
}
Expand All @@ -118,10 +143,12 @@
}
.brush {
filter: invert(100%) sepia(6%) saturate(7487%) hue-rotate(293deg) brightness(103%) contrast(118%);
filter: invert(100%) sepia(6%) saturate(7487%) hue-rotate(293deg)
brightness(103%) contrast(118%);
}
.brush-active {
filter: invert(84%) sepia(34%) saturate(768%) hue-rotate(51deg) brightness(97%) contrast(92%);
filter: invert(84%) sepia(34%) saturate(768%) hue-rotate(51deg)
brightness(97%) contrast(92%);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ export default {

// Global font modifications
fontSize: {
xs: [
"0.8125rem",
{
lineHeight: "1rem",
},
],
sm: [
"0.875rem",
{
Expand Down

0 comments on commit 0be7e33

Please sign in to comment.