|
<script lang="ts"> |
|
import { onMount, tick } from 'svelte'; |
|
|
|
let txt = ''; |
|
let isLoading = false; |
|
let isOutputControlAdded = false; |
|
let drawingBoard: any; |
|
let canvas: HTMLCanvasElement; |
|
let ctx: CanvasRenderingContext2D | null; |
|
let noiseTs: DOMHighResTimeStamp; |
|
let imageTs: DOMHighResTimeStamp; |
|
let drawNextImage: () => void; |
|
let interval: ReturnType<typeof setInterval>; |
|
let canvasSize = 400; |
|
let canvasContainerEl: HTMLDivElement; |
|
let fileInput: HTMLInputElement; |
|
let sketchEl: HTMLCanvasElement; |
|
let isShowSketch = false; |
|
let outputImgs: CanvasImageSource[] = []; |
|
|
|
const animImageDuration = 500 as const; |
|
const animNoiseDuration = 3000 as const; |
|
|
|
async function drawNoise() { |
|
if (!ctx) { |
|
return; |
|
} |
|
|
|
const imageData = ctx.createImageData(canvas.width, canvas.height); |
|
const pix = imageData.data; |
|
|
|
for (let i = 0, n = pix.length; i < n; i += 4) { |
|
const c = 7; |
|
pix[i] = 40 * Math.random() * c; |
|
pix[i + 1] = 40 * Math.random() * c; |
|
pix[i + 2] = 40 * Math.random() * c; |
|
pix[i + 3] = 255; |
|
} |
|
|
|
const bitmap = await createImageBitmap(imageData); |
|
|
|
const duration = performance.now() - noiseTs; |
|
ctx.globalAlpha = Math.min(duration, animNoiseDuration) / animNoiseDuration; |
|
ctx.drawImage(bitmap, 0, 0, canvasSize, canvasSize); |
|
|
|
if (isLoading) { |
|
window.requestAnimationFrame(drawNoise); |
|
} |
|
} |
|
|
|
function drawImage(image: CanvasImageSource) { |
|
if (!ctx) { |
|
return; |
|
} |
|
|
|
const duration = performance.now() - imageTs; |
|
ctx.globalAlpha = Math.min(duration, animImageDuration) / animImageDuration; |
|
ctx.drawImage(image, 0, 0, canvasSize, canvasSize); |
|
|
|
if (duration < animImageDuration) { |
|
window.requestAnimationFrame(() => drawImage(image)); |
|
} |
|
} |
|
|
|
async function getCanvasSnapshot( |
|
canvas: HTMLCanvasElement |
|
): Promise<{ imgFile: File; imgBitmap: ImageBitmap }> { |
|
const canvasDataUrl = canvas.toDataURL('png'); |
|
const res = await fetch(canvasDataUrl); |
|
const blob = await res.blob(); |
|
const imgFile = new File([blob], 'canvas shot.png', { type: 'image/png' }); |
|
const imgData = canvas.getContext('2d')!.getImageData(0, 0, canvasSize, canvasSize); |
|
const imgBitmap = await createImageBitmap(imgData); |
|
return { imgFile, imgBitmap }; |
|
} |
|
|
|
async function submitRequest() { |
|
if (!txt) { |
|
return alert('Please add prompt'); |
|
} |
|
|
|
if (!canvas || !ctx) { |
|
return; |
|
} |
|
|
|
if (interval) { |
|
clearInterval(interval); |
|
} |
|
isLoading = true; |
|
isShowSketch = false; |
|
copySketch(); |
|
|
|
|
|
noiseTs = performance.now(); |
|
drawNoise(); |
|
|
|
const { imgFile, imgBitmap: initialSketchBitmap } = await getCanvasSnapshot(canvas); |
|
const form = new FormData(); |
|
form.append('prompt', txt); |
|
form.append('strength', '0.1'); |
|
form.append('image', imgFile); |
|
|
|
try { |
|
const response = await fetch('https://sdb.pcuenca.net/i2i', { |
|
method: 'POST', |
|
body: form |
|
}); |
|
|
|
const json = JSON.parse(await response.text()); |
|
|
|
const { images: imagesBase64Strs }: { images: string[] } = json; |
|
|
|
if (!imagesBase64Strs.length) { |
|
return alert( |
|
'All the results were flagged. Please try again with diffeerent sketch + prompt' |
|
); |
|
} |
|
|
|
outputImgs = (await Promise.all( |
|
imagesBase64Strs.map(async (imgBase64Str) => { |
|
const imgEl = new Image(); |
|
imgEl.src = `data:image/png;base64, ${imgBase64Str}`; |
|
|
|
await new Promise((resolve, _) => { |
|
imgEl.onload = () => resolve(imgEl); |
|
}); |
|
return imgEl; |
|
}) |
|
)) as CanvasImageSource[]; |
|
|
|
isShowSketch = true; |
|
let i = 0; |
|
imageTs = performance.now(); |
|
drawImage(outputImgs[i % outputImgs.length]); |
|
drawNextImage = () => { |
|
if (interval) { |
|
clearInterval(interval); |
|
} |
|
imageTs = performance.now(); |
|
i = i + 1; |
|
drawImage(outputImgs[i % outputImgs.length]); |
|
}; |
|
interval = setInterval(() => { |
|
i = i + 1; |
|
imageTs = performance.now(); |
|
drawImage(outputImgs[i % outputImgs.length]); |
|
}, 2500); |
|
|
|
if (!isOutputControlAdded) { |
|
addOutputControl(); |
|
} |
|
} catch (err) { |
|
console.error(err); |
|
alert('Error happened, queue might be full. Please try again in a bit :)'); |
|
} finally { |
|
isLoading = false; |
|
} |
|
} |
|
|
|
function addOutputControl() { |
|
const div = document.createElement('div'); |
|
div.className = 'drawing-board-control'; |
|
|
|
const btn = document.createElement('button'); |
|
btn.innerHTML = '⏯'; |
|
btn.onclick = drawNextImage; |
|
div.append(btn); |
|
|
|
const controlsEl = document.querySelector('.drawing-board-controls'); |
|
if (controlsEl && outputImgs.length > 1) { |
|
controlsEl.appendChild(div); |
|
isOutputControlAdded = true; |
|
canvasContainerEl.onclick = () => { |
|
if (interval) { |
|
clearInterval(interval); |
|
} |
|
}; |
|
} |
|
} |
|
|
|
function addClearCanvasControl() { |
|
const div = document.createElement('div'); |
|
div.className = 'drawing-board-control'; |
|
|
|
const btn = document.createElement('button'); |
|
btn.innerHTML = '🧹'; |
|
btn.onclick = () => { |
|
ctx?.clearRect(0, 0, canvasSize, canvasSize); |
|
outputImgs = []; |
|
isShowSketch = false; |
|
}; |
|
div.append(btn); |
|
|
|
const controlsEl = document.querySelector('.drawing-board-controls'); |
|
if (controlsEl) { |
|
controlsEl.appendChild(div); |
|
} |
|
} |
|
|
|
function copySketch() { |
|
const context = sketchEl.getContext('2d'); |
|
|
|
|
|
sketchEl.width = canvas.width; |
|
sketchEl.height = canvas.height; |
|
|
|
|
|
context!.drawImage(canvas, 0, 0); |
|
} |
|
|
|
async function drawUploadedImg(file: File) { |
|
if (interval) { |
|
clearInterval(interval); |
|
} |
|
const imgEl = new Image(); |
|
imgEl.src = URL.createObjectURL(file); |
|
|
|
await new Promise((resolve, _) => { |
|
imgEl.onload = () => resolve(imgEl); |
|
}); |
|
const { width, height } = imgEl; |
|
ctx?.drawImage(imgEl, 0, 0, width, height, 0, 0, canvasSize, canvasSize); |
|
} |
|
|
|
function onfImgUpload() { |
|
const file = fileInput.files?.[0]; |
|
if (file) { |
|
drawUploadedImg(file); |
|
} |
|
} |
|
|
|
function handleDrop(e: DragEvent) { |
|
if (!e.dataTransfer?.files) { |
|
return; |
|
} |
|
e.preventDefault(); |
|
const files = Array.from(e.dataTransfer.files); |
|
const file = files[0]; |
|
drawUploadedImg(file); |
|
} |
|
|
|
function handlePaste(e: ClipboardEvent) { |
|
if (!e.clipboardData) { |
|
return; |
|
} |
|
const files = Array.from(e.clipboardData.files); |
|
if (files.length === 0) { |
|
return; |
|
} |
|
e.preventDefault(); |
|
const file = files[0]; |
|
drawUploadedImg(file); |
|
} |
|
|
|
function onKeyDown(e: KeyboardEvent) { |
|
if (e.code === 'Enter') { |
|
e.preventDefault(); |
|
submitRequest(); |
|
} |
|
} |
|
|
|
|
|
function polyfillCreateImageBitmap() { |
|
window.createImageBitmap = async function (data: ImageData): Promise<ImageBitmap> { |
|
return new Promise((resolve, _) => { |
|
const canvas = document.createElement('canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
canvas.width = data.width; |
|
canvas.height = data.height; |
|
ctx!.putImageData(data, 0, 0); |
|
const dataURL = canvas.toDataURL(); |
|
const img = document.createElement('img'); |
|
img.addEventListener('load', () => { |
|
resolve(img as any as ImageBitmap); |
|
}); |
|
img.src = dataURL; |
|
}); |
|
}; |
|
} |
|
|
|
function makeLinksTargetBlank() { |
|
const linkEls = document.querySelectorAll('a'); |
|
for (const linkEl of linkEls) { |
|
linkEl.target = '_blank'; |
|
} |
|
} |
|
|
|
onMount(async () => { |
|
if (typeof createImageBitmap === 'undefined') { |
|
polyfillCreateImageBitmap(); |
|
} |
|
const { innerWidth: windowWidth } = window; |
|
canvasSize = Math.min(canvasSize, Math.floor(windowWidth * 0.75)); |
|
canvasContainerEl.style.width = `${canvasSize}px`; |
|
canvasContainerEl.style.height = `${canvasSize}px`; |
|
sketchEl.style.width = `${canvasSize}px`; |
|
sketchEl.style.height = `${canvasSize}px`; |
|
await tick(); |
|
drawingBoard = new window.DrawingBoard.Board('board-container', { |
|
size: 10, |
|
controls: ['Color', { Size: { type: 'dropdown' } }, { DrawingMode: { filler: false } }], |
|
webStorage: false, |
|
enlargeYourContainer: true |
|
}); |
|
canvas = drawingBoard.canvas; |
|
ctx = canvas.getContext('2d'); |
|
canvas.ondragover = function (e) { |
|
e.preventDefault(); |
|
return false; |
|
}; |
|
addClearCanvasControl(); |
|
makeLinksTargetBlank(); |
|
}); |
|
</script> |
|
|
|
<svelte:head> |
|
<link |
|
href="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.css" |
|
rel="stylesheet" |
|
/> |
|
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script> |
|
<script |
|
src="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.min.js"></script> |
|
<script |
|
src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script> |
|
</svelte:head> |
|
|
|
<svelte:window on:drop|preventDefault|stopPropagation={handleDrop} on:paste={handlePaste} /> |
|
|
|
<div class="flex flex-wrap gap-x-4 gap-y-2 justify-center my-8"> |
|
<canvas |
|
class="border-[1.2px] desktop:mt-[34px] {!isShowSketch ? 'hidden' : ''}" |
|
bind:this={sketchEl} |
|
/> |
|
<div class="flex flex-col items-center {isLoading ? 'pointer-events-none' : ''}"> |
|
<div id="board-container" bind:this={canvasContainerEl} /> |
|
<div> |
|
<div class="flex gap-x-2 mt-3 items-center justify-center {isLoading ? 'animate-pulse' : ''}"> |
|
<input |
|
type="text" |
|
class="border-2 py-1" |
|
placeholder="Add prompt" |
|
maxlength="200" |
|
on:keydown={onKeyDown} |
|
bind:value={txt} |
|
/> |
|
<button |
|
on:click={submitRequest} |
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1.5 px-4" |
|
> |
|
diffuse the f rest |
|
</button> |
|
</div> |
|
<p class="hidden desktop:inline mt-2 opacity-50"> |
|
pro tip: upload img by dropping on the canvas |
|
</p> |
|
<div class="mt-2"> |
|
<label class="inline desktop:hidden border py-1 px-1.5 bg-slate-200 cursor-pointer"> |
|
<input |
|
accept="image/*" |
|
bind:this={fileInput} |
|
on:change={onfImgUpload} |
|
style="display: none;" |
|
type="file" |
|
/> |
|
upload img |
|
</label> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<article class="prose-sm px-4 md:px-8 lg:px-24 mb-8"> |
|
<div class="text-center"> |
|
Stable Diffusion model by [CompVis](https://huggingface.co/CompVis) and [Stability |
|
AI](https://huggingface.co/stabilityai) - Demo by 🤗 Hugging Face Powered by [🤗 Diffusers: |
|
State-of-the-art diffusion models for image and audio generation in |
|
PyTorch](https://github.com/huggingface/diffusers). Based on [notebook by |
|
@psuraj28](https://twitter.com/psuraj28/status/1562039265126670339) Check out [Stable Diffusion |
|
Gradio demo](https://huggingface.co/spaces/stabilityai/stable-diffusion) |
|
</div> |
|
|
|
### LICENSE The model is licensed with a [CreativeML Open |
|
RAIL-M](https://huggingface.co/spaces/CompVis/stable-diffusion-license) license. The authors claim |
|
no rights on the outputs you generate, you are free to use them and are accountable for their use |
|
which must not go against the provisions set in this license. The license forbids you from sharing |
|
any content that violates any laws, produce any harm to a person, disseminate any personal |
|
information that would be meant for harm, spread misinformation and target vulnerable groups. For |
|
the full list of restrictions please [read the |
|
license](https://huggingface.co/spaces/CompVis/stable-diffusion-license) ### Biases and content |
|
acknowledgment Despite how impressive being able to turn text into image is, beware to the fact |
|
that this model may output content that reinforces or exacerbates societal biases, as well as |
|
realistic faces, pornography and violence. The model was trained on the [LAION-5B |
|
dataset](https://laion.ai/blog/laion-5b/), which scraped non-curated image-text-pairs from the |
|
internet (the exception being the removal of illegal content) and is meant for research purposes. |
|
You can read more in the [model card](https://huggingface.co/CompVis/stable-diffusion-v1-4) |
|
</article> |
|
|