{"version":3,"file":"index.17c5f499.js","sources":["../../src/drawing/svelte-infinite-viewer/InfiniteViewer.svelte","../../vite/modulepreload-polyfill","../../src/drawing/svelte-infinite-viewer/index.js","../../src/utils.ts","../../src/components/Droppable.svelte","../../src/drawing/drawUtils.ts","../../src/drawing/DrawCanvas.svelte","../../src/utils/events.ts","../../src/types.ts","../../src/stores/storeUtils.ts","../../src/stores/ui.ts","../../src/constants.ts","../../src/assets/startImage1.jpeg","../../src/assets/startImage2.jpeg","../../src/assets/startImage3.jpeg","../../src/assets/startImage6.jpeg","../../src/assets/startImage4.jpeg","../../src/assets/startImage5.jpeg","../../src/stores/drawHistory.ts","../../src/stores/canvases.ts","../../src/imageContour.ts","../../src/maskDrawMethods.ts","../../src/components/Canvases.svelte","../../src/stores/viewer.ts","../../src/config.ts","../../src/lib/socket.ts","../../src/optimizeEvents.ts","../../src/components/Slider.svelte","../../src/components/Button.svelte","../../src/components/bottomControls/OptimizeOptions.svelte","../../src/components/bottomControls/BrushPreview.svelte","../../src/components/PillRadioButton.svelte","../../src/components/SearchInput.svelte","../../src/components/Toggle.svelte","../../src/components/bottomControls/MaskDrawControls.svelte","../../src/assets/x.svg","../../src/components/Modal.svelte","../../src/components/InfoModal.svelte","../../src/components/CreateModal.svelte","../../src/utils/network.ts","../../src/components/DownloadModal.svelte","../../src/assets/zoom-out.svg","../../src/assets/zoom-in.svg","../../src/assets/minimize.svg","../../src/assets/icon-undo.svg","../../src/assets/icon-redo.svg","../../src/components/ProseButton.svelte","../../src/assets/arrow-left.svg","../../src/assets/info.svg","../../src/assets/plus.svg","../../src/assets/download.svg","../../src/components/ControlBar.svelte","../../src/components/ControlBarMobile.svelte","../../src/components/MobileHeader.svelte","../../src/routes/Paint.svelte","../../src/components/gallery/Artwork.svelte","../../src/routes/Gallery.svelte","../../src/routes/Review.svelte","../../src/App.svelte","../../src/main.ts"],"sourcesContent":["\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n","const p = function polyfill() {\n const relList = document.createElement('link').relList;\n if (relList && relList.supports && relList.supports('modulepreload')) {\n return;\n }\n for (const link of document.querySelectorAll('link[rel=\"modulepreload\"]')) {\n processPreload(link);\n }\n new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') {\n continue;\n }\n for (const node of mutation.addedNodes) {\n if (node.tagName === 'LINK' && node.rel === 'modulepreload')\n processPreload(node);\n }\n }\n }).observe(document, { childList: true, subtree: true });\n function getFetchOpts(script) {\n const fetchOpts = {};\n if (script.integrity)\n fetchOpts.integrity = script.integrity;\n if (script.referrerpolicy)\n fetchOpts.referrerPolicy = script.referrerpolicy;\n if (script.crossorigin === 'use-credentials')\n fetchOpts.credentials = 'include';\n else if (script.crossorigin === 'anonymous')\n fetchOpts.credentials = 'omit';\n else\n fetchOpts.credentials = 'same-origin';\n return fetchOpts;\n }\n function processPreload(link) {\n if (link.ep)\n // ep marker = processed\n return;\n link.ep = true;\n // prepopulate the load record\n const fetchOpts = getFetchOpts(link);\n fetch(link.href, fetchOpts);\n }\n};__VITE_IS_MODERN__&&p();","import InfiniteViewer from \"./InfiniteViewer.svelte\";\nimport { METHODS } from \"infinite-viewer\";\n\nexport default /*#__PURE__*/ (() => {\n const prototype = InfiniteViewer.prototype;\n\n METHODS.forEach(name => {\n prototype[name] = function (...args) {\n const self = this.getInstance();\n const result = self[name](...args);\n\n if (result === self) {\n return this;\n } else {\n return result;\n }\n };\n });\n return InfiniteViewer;\n})();\n","export async function loadImage(src: string): Promise {\n return await new Promise((resolve, reject) => {\n const image = new Image()\n image.crossOrigin=\"anonymous\"\n image.src = src\n image.onload = () => resolve(image)\n image.onerror = () => reject(new Error('could not load image'))\n })\n}\n\nexport async function loadJson(path: string): Promise {\n return await fetch(path).then(async(response) => await response.json())\n}\n\nexport function imageToCanvas(img: HTMLImageElement): HTMLCanvasElement {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d') as CanvasRenderingContext2D\n canvas.height = img.naturalHeight\n canvas.width = img.naturalWidth\n ctx.drawImage(img, 0, 0)\n return canvas\n}\n\nexport function canvasToImage(canvas: HTMLCanvasElement): HTMLImageElement {\n const image = new Image()\n image.src = canvas.toDataURL()\n return image\n}\n\nexport function imgTob64(img: HTMLImageElement): string {\n const canvas = imageToCanvas(img)\n return canvas.toDataURL()\n}\n\nexport function downloadCanvas(\n canvas: HTMLCanvasElement,\n filename: string = 'download.png',\n format: string = 'image/png',\n quality:number = 1\n) {\n const url = canvas.toDataURL(format, quality)\n const aDownloadLink = document.createElement('a')\n aDownloadLink.download = filename\n aDownloadLink.href = url\n aDownloadLink.click()\n}\n\n\nexport function mergeCanvas(\n canvas1: HTMLCanvasElement,\n canvas2: HTMLCanvasElement\n): HTMLCanvasElement {\n const newCanvas = document.createElement('canvas')\n const newCtx = newCanvas.getContext('2d')\n newCanvas.width = canvas1.width\n newCanvas.height = canvas1.height\n newCtx.drawImage(canvas1, 0, 0)\n newCtx.drawImage(canvas2, 0, 0)\n return newCanvas\n}\n\nexport function thumbnailCanvas(\n canvas: HTMLCanvasElement,\n maxSize: number\n): HTMLCanvasElement {\n const scale = Math.min(1, maxSize / Math.max(canvas.height, canvas.width))\n const thumbnail = document.createElement('canvas')\n const thumbnailCtx = thumbnail.getContext('2d')\n thumbnail.width = Math.round(scale * canvas.width)\n thumbnail.height = Math.round(scale * canvas.height)\n thumbnailCtx.drawImage(\n canvas,\n 0, 0,\n canvas.width, canvas.height,\n 0, 0,\n thumbnail.width,\n thumbnail.height\n )\n return thumbnail\n}\n\nexport function clamp(a: number, b: number, c: number): number {\n return Math.max(b, Math.min(c, a))\n}\n\nexport function stopEvent(event) {\n event.stopPropagation()\n event.preventDefault()\n}\n\nexport function localStorageSpace(){\n let data = '';\n console.log('Current local storage: ');\n for(let key in window.localStorage){\n if(window.localStorage.hasOwnProperty(key)){\n data += window.localStorage[key];\n console.log( key + \" = \" + ((window.localStorage[key].length * 16)/(8 * 1024)).toFixed(2) + ' KB' );\n }\n }\n console.log(data ? '\\n' + 'Total space used: ' + ((data.length * 16)/(8 * 1024)).toFixed(2) + ' KB' : 'Empty (0 KB)');\n console.log(data ? 'Approx. space remaining: ' + (5120 - ((data.length * 16)/(8 * 1024)).toFixed(2)) + ' KB' : '5 MB');\n};\n\nexport function downloadJSON(exportObj:any, exportName:string) {\n if (!exportName.endsWith('.json')) {\n exportName += '.json'\n }\n const dataStr = \"data:text/json;charset=utf-8,\" + encodeURIComponent(JSON.stringify(exportObj))\n const downloadAnchorNode = document.createElement('a')\n downloadAnchorNode.setAttribute(\"href\", dataStr)\n downloadAnchorNode.setAttribute(\"download\", exportName)\n document.body.appendChild(downloadAnchorNode); // required for firefox\n downloadAnchorNode.click();\n downloadAnchorNode.remove();\n}\n","\n\n\n \n\n","\nexport function drawCircle(\n ctx,\n x,\n y,\n r,\n fill = true,\n stroke = false,\n clip = false\n) {\n ctx.beginPath();\n ctx.arc(x, y, r, 0, 2 * Math.PI, false);\n if (clip) {\n ctx.clip();\n }\n if (stroke) {\n ctx.stroke();\n }\n if (fill) {\n ctx.fill();\n }\n ctx.closePath();\n}\n\nexport function drawLine(\n ctx: CanvasRenderingContext2D,\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n radius: number\n) {\n ctx.lineWidth = radius\n ctx.beginPath();\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.stroke()\n}\n\nexport function drawLines(\n ctx: CanvasRenderingContext2D,\n points: number[][]\n) {\n if (points.length < 2) {\n return\n }\n ctx.beginPath()\n ctx.moveTo(points[0][0], points[0][1])\n for (let idx = 1; idx < points.length; idx++) {\n ctx.lineTo(points[idx][0], points[idx][1])\n }\n ctx.stroke()\n}\n\nexport function drawSmoothLines(\n ctx: CanvasRenderingContext2D,\n points: number[][]\n) {\n if (points.length < 3) {\n return\n }\n ctx.beginPath()\n ctx.moveTo(points[0][0], points[0][1])\n let i\n for (i = 1; i < points.length - 2; i++) {\n let xc = (points[i][0] + points[i + 1][0]) / 2;\n let yc = (points[i][1] + points[i + 1][1]) / 2;\n ctx.quadraticCurveTo(points[i][0], points[i][1], xc, yc);\n }\n // curve through the last two points\n ctx.quadraticCurveTo(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1]);\n ctx.stroke()\n}\n\n\nexport function createBrushImage(\n hardness: number,\n radius: number,\n opacity: number = 0.0,\n color1: string='rgba(0, 0, 0, 1.0)',\n color2: string='rgba(255, 255, 255, 1.0)',\n): HTMLCanvasElement {\n const size = (radius * 2 + 1)\n const canvas = document.createElement('canvas') as HTMLCanvasElement\n canvas.width = size\n canvas.height = size\n const context = canvas.getContext('2d')\n\n context.globalAlpha = 1 - opacity\n const gradient = context.createRadialGradient(\n radius+1,\n radius+1,\n Math.min(radius-1, Math.round(radius * hardness)),\n radius+1,\n radius+1,\n radius\n );\n gradient.addColorStop(0, color1)\n gradient.addColorStop(1, color2)\n context.fillStyle = gradient\n context.fillRect(0, 0, size, size)\n return canvas\n}\n\n\nexport function drawBrushLine(\n context:CanvasRenderingContext2D,\n brushImage: HTMLImageElement|HTMLCanvasElement,\n x1:number,\n y1: number,\n x2: number,\n y2: number\n) {\n const bw = brushImage.width\n const bh = brushImage.height\n const diffX = Math.abs(x2 - x1)\n const diffY = Math.abs(y2 - y1)\n const dist = Math.sqrt(diffX * diffX + diffY * diffY)\n // const step = .25 * bw / (dist ? (Math.min(24, dist)) : 1)\n const step = .05 * bw\n let i = 0\n let t = 0, b, x, y;\n // console.log(dist, step, dist / step);\n \n context.globalCompositeOperation = 'darken'\n while (i <= dist) {\n t = Math.max(0, Math.min(1, i / dist));\n x = x1 + (x2 - x1) * t;\n y = y1 + (y2 - y1) * t;\n b = (Math.random() * 3) | 0;\n context.drawImage(brushImage, x - bw * 0.5, y - bh * 0.5);\n i += step\n }\n context.globalCompositeOperation = 'source-over'\n}","\n\n\n\n strokeDone()}\n on:touchend={() => strokeDone()}\n\n on:mouseleave={() => strokeDone()}\n on:touchcancel={() => strokeDone()}\n\n on:mousemove={onMouseMove}\n on:touchmove={onMouseMove}\n \n>\n \n \n\n\n\n","export function mouseOrTouchOffsets(event): [number, number][] {\n if (event.touches?.length) {\n return Array.from(event.touches).map(({ clientX, clientY , target }) => { \n const r = target.getBoundingClientRect();\n const scale = target.clientWidth / r.width \n const offsetX = clientX - r.left\n const offsetY = clientY - r.top\n return [ offsetX * scale, offsetY * scale ]\n })\n } else {\n return [[ event.offsetX, event.offsetY ]]\n }\n}","\nexport enum Mode {\n MobileHomeScreen,\n DirectDraw,\n MaskDraw,\n Optimizing,\n PausedOptimizing,\n}\n\nexport interface OptimizationResults {\n // step: number,\n num_iterations: number, // The total possible.\n images: HTMLImageElement[]\n}\n\n// export interface GalleryExport {\n// creationDate: number,\n// artistName: string,\n// artworkName: string,\n// masks: Array<{ imageBase64: string, prompt: string }>\n// }\nexport interface ArtworkData {\n accepted: null | boolean\n artist_name: string\n created_at: string\n key: string\n}\nexport type Masks = Array<{ imageBase64: string, prompt: string }>\n\nexport type ColorPalette = \n 'blue-10' |\n 'blue-20' |\n 'blue-30' |\n 'blue-40' |\n 'blue-50' |\n 'cyan-10' |\n 'cyan-20' |\n 'cyan-30' |\n 'cyan-40' |\n 'cyan-50' |\n 'chartreuse-10' |\n 'chartreuse-20' |\n 'chartreuse-30' |\n 'chartreuse-40' |\n 'chartreuse-50' |\n 'green-10' |\n 'green-20' |\n 'green-30' |\n 'green-40' |\n 'green-50' |\n 'gray-0' |\n 'gray-10' |\n 'gray-20' |\n 'gray-30' |\n 'gray-40' |\n 'gray-50' |\n 'gray-60' |\n 'gray-70' |\n 'gray-80' |\n 'gray-90' |\n 'gray-100' |\n 'magenta-10' |\n 'magenta-20' |\n 'magenta-30' |\n 'magenta-40' |\n 'magenta-50' |\n 'orange-10' |\n 'orange-20' |\n 'orange-30' |\n 'orange-40' |\n 'orange-50' |\n 'purple-10' |\n 'purple-20' |\n 'purple-30' |\n 'purple-40' |\n 'purple-50' |\n 'red-10' |\n 'red-20' |\n 'red-30' |\n 'red-40' |\n 'red-50' |\n 'yellow-10' |\n 'yellow-20' |\n 'yellow-30' |\n 'yellow-40' |\n 'yellow-50'\n","import type { Writable } from 'svelte/store'\nimport { writable } from 'svelte/store'\nimport { clamp } from '@/utils'\n\nexport function localStorageWritable(name:string, defaultValue:T):Writable{\n // Svelte store that persists to localStorage.\n // Data must be JSON-able.\n let value = null\n try {\n value = JSON.parse(localStorage.getItem(name))\n } catch (error) {}\n const store = writable(value ?? defaultValue)\n store.subscribe($value => {\n localStorage.setItem(name, JSON.stringify($value))\n })\n return store\n}\n\nexport function clampedNumberStore(value:number, min:number, max:number) {\n const store = writable(value)\n return {\n subscribe: store.subscribe,\n set: (v:number) => store.set(clamp(v, min, max))\n }\n}","\nimport { writable, derived, get } from 'svelte/store'\nimport type { OptimizationResults } from '@/types'\nimport { Mode } from '@/types'\nimport { localStorageWritable, clampedNumberStore } from '@/stores/storeUtils'\nimport { onMount } from 'svelte'\n\n\nexport const prompt = localStorageWritable('prompt', 'radiant fireworks of diamonds')\nexport const stylePrompt = localStorageWritable('stylePrompt', '')\nexport const learningRate = localStorageWritable('learningRate', 250)\nexport const numRecSteps = localStorageWritable('numRecSteps', 0)\nexport const zoom = clampedNumberStore(1, .25, 5.0)\nexport const innerWidth = writable(window.innerWidth)\nexport const innerHeight = writable(window.innerHeight)\nexport const isMobile = derived(innerWidth, $w => $w <= 900)\nexport const mode = writable(\n get(isMobile) ? Mode.MobileHomeScreen : Mode.MaskDraw\n)\n\n// UserPool\nexport const userQueuePosition = writable(-1)\nexport const optimizationResults = writable(null as null | OptimizationResults)\nexport const selectedOptIdx = writable(0)\nexport const selectedOptImage = derived([selectedOptIdx, optimizationResults], ([$idx, $results]) => {\n return $results == null ? null : $results.images[$idx]\n})\n\n","export const MAX_IMAGE_SIZE = 1024\n\n// import image0Url from './assets/startImage0.jpeg'\nimport image1Url from './assets/startImage1.jpeg'\nimport image2Url from './assets/startImage2.jpeg'\nimport image3Url from './assets/startImage3.jpeg'\nimport image4Url from './assets/startImage4.jpeg'\nimport image5Url from './assets/startImage5.jpeg'\nimport image6Url from './assets/startImage6.jpeg'\n\nexport const DEFAULT_IMAGES = [\n image1Url,\n image2Url,\n image3Url,\n image6Url,\n image4Url,\n image5Url\n]\n\nexport const DEFAULT_IMAGE = DEFAULT_IMAGES[1]","export default \"__VITE_ASSET__badf2d78__\"","export default \"__VITE_ASSET__aeeffbb2__\"","export default \"__VITE_ASSET__31a691d9__\"","export default \"__VITE_ASSET__09905a97__\"","export default \"__VITE_ASSET__444abc04__\"","export default \"__VITE_ASSET__df20ae51__\"","// A file to store the history of the edits to a canvas.\nimport { get } from 'svelte/store'\nimport { DEFAULT_IMAGE } from '../constants'\nimport { canvasToImage, loadImage, thumbnailCanvas } from '@/utils'\nimport { mainCanvas, maskCanvas } from '@/stores/canvases'\nimport { prompt } from '@/stores/ui'\nimport { localStorageWritable } from '@/stores/storeUtils'\n\ninterface DrawHistory {\n startImage: string\n masks: Array<{ imageBase64: string, prompt: string }>\n}\n\nexport const drawHistoryStore = localStorageWritable('drawHistory', {\n startImage: '',\n masks: []\n})\n\nexport function recordOptimizationHistory() {\n console.log('Accepted.')\n const smallCanvas = thumbnailCanvas(get(maskCanvas).getCanvas(), 256)\n const imageBase64 = smallCanvas.toDataURL('image/webp');\n drawHistoryStore.update(($drawHistoryStore) => {\n $drawHistoryStore.masks = $drawHistoryStore.masks || []\n $drawHistoryStore.masks.push({ imageBase64, prompt: get(prompt) })\n // Limit the total, taking the previous 12.\n $drawHistoryStore.masks = $drawHistoryStore.masks.slice(-12)\n console.log('Draw history length:', $drawHistoryStore.masks.length);\n return $drawHistoryStore\n })\n}\n\n// export function setHasOptimized() {\n// console.log('Accepted.')\n// drawHistoryStore.update((_drawHistoryStore) => ({\n// ..._drawHistoryStore,\n// hasOptimized: true,\n// }))\n// }\n\nexport function reset() {\n // Called when draw canvas is set to a new image.\n console.log('draw history reset')\n drawHistoryStore.update((_drawHistoryStore) => ({\n startImage: get(mainCanvas).getCanvas().toDataURL('image/jpeg', 90),\n masks: []\n }))\n}\n\nexport async function exportBeforeAfter(): Promise {\n const finalCanvas = get(mainCanvas).getCanvas()\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n canvas.width = (finalCanvas.width * 2)\n canvas.height = finalCanvas.height\n // The start image will be an empty string on the first load.\n const startImageUrl = get(drawHistoryStore).startImage || DEFAULT_IMAGE\n const startImage = await loadImage(startImageUrl)\n ctx.drawImage(startImage, 0, 0)\n ctx.drawImage(finalCanvas, finalCanvas.width, 0)\n return canvas\n}","import { writable, get, derived } from 'svelte/store'\nimport { tick } from \"svelte\";\nimport { localStorageWritable } from '@/stores/storeUtils'\nimport { mode } from '@/stores/ui'\nimport type DrawCanvas from \"@/drawing/DrawCanvas.svelte\";\nimport * as drawHistory from '@/stores/drawHistory'\nimport { loadImage, imageToCanvas, thumbnailCanvas } from \"@/utils\";\nimport { MAX_IMAGE_SIZE } from \"@/constants\";\nimport { Mode } from '@/types';\n\nexport const canvasSize = localStorageWritable('canvasSize', [512, 512])\nexport const mainCanvas = writable(null as null | DrawCanvas)\nexport const maskCanvas = writable(null as null | DrawCanvas)\nexport const canvasEvents: EventTarget = new EventTarget()\n\nexport const activeCanvas = derived(\n [mode, maskCanvas, mainCanvas],\n ([$mode, $maskCanvas, $mainCanvas]) => {\n if (!$mainCanvas || !$maskCanvas) {\n return null\n }\n return ($mode == Mode.DirectDraw) ? $mainCanvas : $maskCanvas\n})\n\nexport async function setEmpty(width, height) {\n console.log('set empty', width, height);\n \n canvasSize.set([width, height]);\n await tick(); // DrawCanvases get recreated.\n const whiteCanvas = document.createElement('canvas')\n whiteCanvas.width = width\n whiteCanvas.height = height\n const ctx = whiteCanvas.getContext('2d')\n ctx.fillStyle = 'white'\n ctx.fillRect(0, 0, width, height)\n\n get(mainCanvas).setImage(whiteCanvas)\n drawHistory.reset()\n canvasEvents.dispatchEvent(new CustomEvent('setImage', {}))\n}\n\nexport async function setImageByURL(imageUrl: string) {\n try {\n const image = await loadImage(imageUrl);\n const canvas = thumbnailCanvas(imageToCanvas(image), MAX_IMAGE_SIZE);\n canvasSize.set([canvas.width, canvas.height]);\n await tick(); // DrawCanvases get recreated.\n \n get(mainCanvas).setImage(canvas);\n drawHistory.reset()\n canvasEvents.dispatchEvent(new CustomEvent('setImage', {}))\n } catch (error) {\n console.log(error);\n return\n }\n}\n\nexport async function setImageByFile(file: File) {\n const url = URL.createObjectURL(file)\n await setImageByURL(url)\n}","import { contours } from 'd3-contour'\n\n\nfunction _imageContour(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D\n): number[][][] {\n const width = canvas.width\n const height = canvas.height\n const data4 = ctx.getImageData(0, 0, width, height).data\n // Check if the image uses transparency by the alpha of top-left pixel.\n if (data4[3] === 255) {\n // If no transparency return the square.\n return [[\n [0, 0],\n [width, 0],\n [width, height],\n [0, height],\n ]]\n } else {\n // Collect the alpha pixels to use for marching squares.\n const alpha: number[] = []\n for (let i = 3; i < data4.length; i += 4) {\n alpha.push(data4[i])\n }\n // console.time('contours')\n return contours()\n .size([width, height])\n .thresholds([5])(alpha)[0]\n .coordinates.flat()\n }\n}\n\nconst contourCanvas = document.createElement('canvas')\ncontourCanvas.width = 300\ncontourCanvas.height = 300\nconst contourCtx = contourCanvas.getContext('2d')\n\nexport function imageContour(maskCanvas: HTMLCanvasElement) {\n // A wrapper for the contours that places it in a smaller canvas first for optimization.\n contourCtx.clearRect(0, 0, contourCanvas.width, contourCanvas.height)\n contourCtx.drawImage(\n maskCanvas,\n 0, 0,\n maskCanvas.width,\n maskCanvas.height,\n 0, 0,\n contourCanvas.width,\n contourCanvas.height,\n )\n const scaleX = maskCanvas.width / contourCanvas.width\n const scaleY = maskCanvas.height / contourCanvas.height\n return _imageContour(contourCanvas, contourCtx).map(poly => (\n poly.map(p => [p[0] * scaleX, p[1] * scaleY])\n ))\n}","import { drawLines } from \"./drawing/drawUtils\";\nimport { imageContour } from \"./imageContour\";\n\nexport function drawGrid(\n canvasSize: number[],\n gridSize: number,\n colorA: string = 'blue',\n colorB: string = 'yellow'\n // colorA: string = 'white',\n // colorB: string = 'black'\n): [HTMLCanvasElement, CanvasRenderingContext2D] {\n const canvas: HTMLCanvasElement = document.createElement('canvas')\n canvas.width = canvasSize[0]\n canvas.height = canvasSize[1]\n const ctx = canvas.getContext('2d')\n const numCols = Math.round(canvasSize[0] / gridSize)\n const numRows = Math.round(canvasSize[1] / gridSize)\n for (let row = 0; row < numRows; row += 1) {\n for (let col = 0; col < numCols; col += 1) {\n ctx.beginPath()\n ctx.fillStyle = row % 2 == col % 2 ? colorA : colorB\n const x = col * gridSize\n const y = row * gridSize\n ctx.rect(x, y, gridSize, gridSize)\n ctx.fill()\n }\n }\n return [canvas, ctx]\n}\n\nfunction drawContours(\n maskCanvas: HTMLCanvasElement,\n dstCtx: CanvasRenderingContext2D,\n lineWidth: number,\n // colorA: string = 'white',\n // colorB: string = 'black',\n colorA: string = 'blue',\n colorB: string = 'yellow',\n dash:number = 3\n) {\n // console.time('contours')\n const contours = imageContour(maskCanvas)\n // console.timeEnd('contours')\n // dstCtx.globalAlpha = .5\n dstCtx.lineWidth = lineWidth;\n dstCtx.lineDashOffset = 0;\n dstCtx.strokeStyle = colorA\n dstCtx.setLineDash([dash, dash]);\n for (const poly of contours) {\n drawLines(dstCtx, poly);\n }\n dstCtx.lineDashOffset = dash;\n dstCtx.strokeStyle = colorB\n dstCtx.setLineDash([dash, dash]);\n for (const poly of contours) {\n drawLines(dstCtx, poly);\n }\n dstCtx.globalAlpha = 0\n}\n\nexport function drawMaskGridAlpha(\n dstCtx: CanvasRenderingContext2D,\n canvasSize: number[],\n maskCanvas: HTMLCanvasElement,\n gridCanvas: HTMLCanvasElement,\n lineWidth:number=2\n) {\n dstCtx.clearRect(0, 0, canvasSize[0], canvasSize[1]);\n dstCtx.globalAlpha = 0.6\n dstCtx.drawImage(gridCanvas, 0, 0)\n dstCtx.globalAlpha = 1.0\n dstCtx.globalCompositeOperation = \"destination-in\";\n dstCtx.drawImage(maskCanvas, 0, 0)\n dstCtx.globalCompositeOperation = \"source-over\"; // reset to default.\n // Draw contours\n drawContours(maskCanvas, dstCtx, lineWidth)\n}\n\n\n// export function drawGridColor(\n// canvas:HTMLCanvasElement,\n// ctx: CanvasRenderingContext2D,\n// gridSize: number,\n// color:string,\n// offset: boolean\n// ): [HTMLCanvasElement, CanvasRenderingContext2D] {\n// const numCols = Math.round(canvas.width / gridSize)\n// const numRows = Math.round(canvas.height / gridSize)\n// ctx.beginPath()\n// ctx.fillStyle = color\n// for (let row = 0; row < numRows; row += 1) {\n// for (let col = 0; col < numCols; col += 1) {\n// if ((row % 2 == col % 2) == offset) {\n// const x = col * gridSize\n// const y = row * gridSize\n// ctx.rect(x, y, gridSize, gridSize)\n// }\n// }\n// }\n// ctx.fill()\n// return [canvas, ctx]\n// }\n\n\n// function drawGrid(gridSize:number): [HTMLCanvasElement, CanvasRenderingContext2D] {\n// const canvas:HTMLCanvasElement = document.createElement('canvas')\n// canvas.width = $canvasSize[0]\n// canvas.height = $canvasSize[1]\n// const ctx = canvas.getContext('2d')\n// ctx.fillStyle = \"white\";\n// ctx.beginPath();\n// for (let x = 0; x < $canvasSize[0]; x += 2 * gridSize) {\n// for (let row = 0; row < $canvasSize[0] / gridSize; row += 1) {\n// const y = row * gridSize;\n// const xOffset = row % 2 == 0 ? 0 : gridSize;\n// ctx.rect(x + xOffset, y, gridSize, gridSize);\n// }\n// }\n// ctx.fill();\n// return [canvas, ctx]\n// }\n\n\n// function drawMaskGridInverted(maskCanvas: HTMLCanvasElement, gridSize: number =3) {\n// outlineCtx.clearRect(0, 0, $canvasSize[0], $canvasSize[1]);\n// // First union the grid and mask\n// if ($mainCanvas) {\n// var imageData = $mainCanvas.getContext().getImageData(0, 0, $canvasSize[0], $canvasSize[1]);\n// var data = imageData.data;\n// for (let i = 0; i < data.length; i+=4) {\n// data[i] = Math.min(255, 255 - data[i] + 40)\n// data[i+1] = 255 - data[i+1]\n// data[i+2] = 255 - data[i+2]\n// } \n// outlineCtx.putImageData(imageData, 0, 0)\n// }\n// outlineCtx.globalCompositeOperation = \"destination-in\";\n// outlineCtx.drawImage(maskCanvas, 0, 0); \n// const gridCanvas = drawGrid(gridSize)[0]\n// outlineCtx.drawImage(gridCanvas, 0, 0);\n// }\n\n// function drawMaskGridBinary(maskCanvas: HTMLCanvasElement, gridSize: number =3) {\n// outlineCtx.clearRect(0, 0, $canvasSize[0], $canvasSize[1]);\n// // First union the grid and mask\n// if ($mainCanvas) {\n// const smallCanvas:HTMLCanvasElement = document.createElement('canvas')\n// const smallCtx = smallCanvas.getContext('2d')\n// smallCanvas.width = Math.round($canvasSize[0] / gridSize)\n// smallCanvas.height = Math.round($canvasSize[1] / gridSize)\n// smallCtx.drawImage(\n// $mainCanvas.getCanvas(),\n// 0, 0,\n// $canvasSize[0],\n// $canvasSize[1],\n// 0, 0,\n// smallCanvas.width, \n// smallCanvas.height, \n// )\n// const imageData = smallCtx.getImageData(0, 0, smallCanvas.width, smallCanvas.height)\n// const data = imageData.data\n// console.log(data.length);\n\n// // ctx.fillStyle = \"white\";\n// const numCols:number = Math.round($canvasSize[0] / gridSize)\n// const numRows:number = Math.round($canvasSize[1] / gridSize)\n\n// // for (let x = 0; x < $canvasSize[0]; x += 2 * gridSize) {\n// console.time('draw grid')\n// for (let row = 0; row < numRows; row += 1) {\n// for (let col = 0; col < numCols; col += 1) {\n// if (row %2 != col%2) {\n// continue\n// }\n// outlineCtx.beginPath()\n// const x:number = col * gridSize\n// const y:number = row * gridSize\n// const i = 4 * ((row*numRows)+col)\n// const [r, g, b] = [data[i], data[i+1], data[i+2]] \n// const lumin = r*0.299 + g*0.587 + b*0.114\n// // outlineCtx.fillStyle = `rgb(${r}, ${g}, ${b})`\n// // outlineCtx.fillStyle = `rgb(${r}, ${g}, ${b})`\n// // outlineCtx.fillStyle = lumin > 186 ? 'black' : 'white'\n// // outlineCtx.fillStyle = lumin > 210 ? 'black' : 'white'\n// // outlineCtx.fillStyle = lumin > 210 ? 'lightgray' : 'darkgray'\n// outlineCtx.rect(x, y, gridSize, gridSize);\n// outlineCtx.fill();\n// }\n// }\n// console.timeEnd('draw grid')\n// }\n// outlineCtx.globalCompositeOperation = \"destination-in\";\n// outlineCtx.drawImage(maskCanvas, 0, 0); \n// // const gridCanvas = drawGrid(gridSize)[0]\n// // outlineCtx.drawImage(gridCanvas, 0, 0);\n// }","\n (mouseover = true)}\n on:mouseout={() => (mouseover = false)}\n on:focus={() => (mouseover = true)}\n on:blur={() => (mouseover = false)}\n>\n
\n 3 ? 'pixelated' : ''};\"\n id=\"mainCanvas\"\n defaultImageUrl={DEFAULT_IMAGE}\n showCursor={false}\n bind:this={$mainCanvas}\n />\n
\n
\n
\n \n
\n
\n
\n \n
\n {#if $selectedOptImage}\n \n {/if}\n \n\n\n\n","import { writable, derived, get } from 'svelte/store'\nimport { canvasSize } from '@/stores/canvases'\nimport { innerWidth, innerHeight, zoom } from '@/stores/ui'\nexport const viewer = writable(null as null | any)\n\nexport function fitImage() {\n const $zoom = Math.min(1, Math.min(\n get(innerWidth) / get(canvasSize)[0],\n get(innerHeight) / get(canvasSize)[1]\n ) * .85) \n zoom.set($zoom)\n get(viewer).scrollCenter()\n}","// Live environments serve the client and server on different domains, so we need\n// a mapping.\nconst serverHostnameMappings = {\n 'prosepainter-client.test.morphogen.io': 'prosepainter-server.test.morphogen.io',\n 'prosepainter-client.staging.morphogen.io': 'prosepainter-server.staging.morphogen.io',\n 'prosepainter-client.grid.morphogen.io': 'prosepainter-server.grid.morphogen.io',\n 'prosepainter-client.grid-b.morphogen.io': 'prosepainter-server.grid-b.morphogen.io',\n 'www.prosepainter.com': 'prosepainter-server.grid.morphogen.io'\n}\n\nconst serverHostname = serverHostnameMappings[window.location.host] || window.location.host.replace(':8003', ':8004');\nconst proto = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\nconst apiURL = window.location.host.startsWith('localhost') ? 'http://localhost:8888' : 'https://www.artbreeder.com' \n\nexport default {\n serverUrl: `${proto}//${serverHostname}/ws`,\n apiURL: `${apiURL}/prosepainter`,\n assetsUrl: 'https://prosepainter-public.b-cdn.net'\n};\n","import { writable, readable, get } from 'svelte/store'\nimport config from '../config'\n\nlet socket\nconst events = []\nexport const socketOpen = writable(false)\n\nlet userID = localStorage.getItem('userId')\nif (userID == null) {\n userID = (Math.random() + 1).toString(36).slice(2)\n localStorage.setItem('userId', userID)\n}\nconsole.log({ userID });\n\nfunction attemptConnect() {\n if (get(socketOpen)) {\n return\n }\n console.log('Attempting socket connection...'); \n socket = new WebSocket(config.serverUrl)\n socket.onerror = function(error) {\n console.log('Socket error:', error)\n };\n socket.onopen = () => { \n console.log('Websocket connected!');\n socketOpen.set(true)\n messageServer('set-id', userID)\n for (const [name, callback] of events) {\n console.log('registering event', name);\n socket.addEventListener(name, callback) \n }\n }\n socket.onclose = (closeEvent) => {\n const reason = closeEvent.reason\n console.log('Socket closed:', reason)\n socketOpen.set(false)\n setTimeout(attemptConnect, 3000)\n }\n} \n\n\nexport function messageServer(topic:string, data:any) {\n if (get(socketOpen)) {\n socket.send(JSON.stringify({ topic, data }))\n }\n}\nexport function addEventListener(name, callback) {\n if (get(socketOpen)) {\n socket.addEventListener(name, callback) \n }\n events.push([name, callback])\n}\n\nattemptConnect()\n\n\n // // preserve the socket across HMR updates\n// if (import.meta.hot) {\n// if (import.meta.hot.data.stores) {\n// socket = import.meta.hot.data.socket\n// }\n// import.meta.hot.accept()\n// import.meta.hot.dispose(() => {\n// import.meta.hot.data.socket = socket\n// })\n// }\n","import { Mode } from './types';\nimport { writable, get } from 'svelte/store'\nimport { addEventListener, messageServer } from \"@/lib/socket\";\nimport { prompt, mode, learningRate, optimizationResults, stylePrompt, numRecSteps, selectedOptIdx, selectedOptImage, userQueuePosition } from '@/stores/ui'\nimport { mainCanvas, maskCanvas } from '@/stores/canvases'\nimport { imgTob64 } from './utils';\nimport { recordOptimizationHistory } from '@/stores/drawHistory'\ninterface StartGenerationData {\n prompt: string\n stylePrompt: string,\n imageBase64: string,\n learningRate: number,\n backgroundImg: string,\n numRecSteps: number | null,\n num_iterations?: number\n}\n\nfunction getGenerationData(): StartGenerationData {\n return {\n num_iterations: 30,\n prompt: get(prompt),\n stylePrompt: get(stylePrompt) ?? '',\n learningRate: get(learningRate) / 1000,\n imageBase64: get(maskCanvas).canvasBase64,\n backgroundImg: get(mainCanvas).canvasBase64,\n numRecSteps: get(numRecSteps),\n }\n}\n\nfunction validateGenerationData(data: StartGenerationData) {\n for (const [key, value] of Object.entries(data)) {\n if (key == 'stylePrompt') {\n continue\n }\n if (key == 'numRecSteps') {\n continue\n }\n if (!value || value.length == 0) {\n throw new Error(`Empty value for: ${key}.`)\n }\n }\n}\n\nfunction lastOptimizationResult(): HTMLImageElement {\n const images = get(optimizationResults).images\n return images[images.length]\n}\n\nexport const events: EventTarget = new EventTarget()\n\nexport function start() {\n if (!get(maskCanvas).hasAnyVisiblePixels()) {\n return alert('You must apply the prose-paint to the painting first!')\n } \n const data = getGenerationData()\n if (data.prompt.length == 0) {\n return alert('You must enter a description!')\n }\n validateGenerationData(data)\n messageServer('start-generation', data)\n mode.set(Mode.Optimizing)\n}\n\nexport function discard() {\n messageServer(\"stop-generation\", {})\n optimizationResults.set(null)\n mode.set(Mode.MaskDraw)\n}\n\nexport function accept() {\n messageServer(\"stop-generation\", {})\n get(mainCanvas).setImage(get(selectedOptImage))\n optimizationResults.set(null)\n mode.set(Mode.MaskDraw)\n // setHasOptimized()\n recordOptimizationHistory()\n events.dispatchEvent(new CustomEvent('accepted', {}))\n}\n\n// export function upscale() {\n// const data = getGenerationData()\n// data.backgroundImg = imgTob64(get(lastOptimizationResult).image)\n// validateGenerationData(data)\n// messageServer('upscale-generation', data)\n// mode.set(Mode.Optimizing)\n// }\n\nexport function pause() {\n // TODO\n // messageServer(\"pause-generation\", {})\n messageServer(\"stop-generation\", {})\n mode.set(Mode.PausedOptimizing)\n}\n\nexport function resume() {\n // TODO\n // messageServer(\"resume-generation\", {})\n // like start() but use lastOptimizationResult instead of main canvas\n const data = getGenerationData()\n data.backgroundImg = imgTob64(lastOptimizationResult())\n validateGenerationData(data)\n messageServer('resume-generation', data)\n mode.set(Mode.Optimizing)\n}\n\naddEventListener(\"message\", (e) => {\n const message = JSON.parse(e.data) as { topic: string, data: any }\n console.log(\"MESSAGE RECEIVED!\", message)\n if (message.topic == \"queuePosition\") {\n userQueuePosition.set(message.data.queuePosition as number)\n } else if (message.topic == \"error\") {\n alert(message.data)\n mode.set(Mode.PausedOptimizing)\n } else if (message.topic == 'jobProgress') {\n if (get(mode) !== Mode.Optimizing) {\n // If we get a message from the server after the user paused.\n return\n }\n const { image, step, num_iterations } = message.data\n if (image) {\n console.log(\"IMAGE RECEIVED!\")\n const newImage = new Image()\n newImage.src = \"data:text/plain;base64,\" + image\n optimizationResults.update(_optResults => ({\n images: (_optResults ? _optResults.images : []).concat([newImage]),\n num_iterations: num_iterations as number,\n }))\n if (step == num_iterations - 1) {\n mode.set(Mode.PausedOptimizing)\n }\n selectedOptIdx.set(step)\n } else {\n console.log(\"NO IMAGE RECEIVED!\")\n }\n }\n});\n","\n\n
\n \n
\n

{name}:

\n

{val}

\n
\n
\n \n
\n\n\n","\n\n\n \n\n\n\n","\n\n{#if $mode == Mode.Optimizing}\n
\n
\n \n
\n
\n {#if results}\n

{results.images.length} / {results.num_iterations}

\n {:else}\n

Waiting to start...

\n {/if}\n
\n
\n{:else if $mode == Mode.PausedOptimizing}\n
\n
\n optEvents.discard()}\n >\n

Discard

\n \n optEvents.accept()}\n >\n

Accept

\n \n
\n
\n {#if results}\n \n {/if}\n
\n
\n{/if}\n\n\n","\n\n
\n {#if showBrushControls}\n
\n \n \n \n \n
\n {/if}\n \n
\n\n\n","\n\n\n\n","\n\n\n