|
import quantize from 'quantize'; |
|
import * as d3 from 'd3-color'; |
|
import type { Color } from 'd3-color'; |
|
import { dev } from '$app/environment'; |
|
|
|
export function randomSeed() { |
|
return BigInt(13248873089935215612 & (((1 << 63) - 1) * Math.random())); |
|
} |
|
|
|
function sortColors(colors: Color[]): Color[] { |
|
const reverse = true; |
|
return colors |
|
.map((color) => d3.hcl(color)) |
|
.sort((a, b) => { |
|
const aa = a.h; |
|
const bb = b.h; |
|
|
|
return !reverse ? aa - bb || isNaN(aa) - isNaN(bb) : bb - aa || isNaN(bb) - isNaN(aa); |
|
}); |
|
} |
|
|
|
function createPixelArray(imgData: Uint8ClampedArray, pixelCount: number, quality: number) { |
|
|
|
const pixels = imgData; |
|
const pixelArray = []; |
|
|
|
for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) { |
|
offset = i * 4; |
|
r = pixels[offset + 0]; |
|
g = pixels[offset + 1]; |
|
b = pixels[offset + 2]; |
|
a = pixels[offset + 3]; |
|
|
|
|
|
if (typeof a === 'undefined' || a >= 125) { |
|
if (!(r > 250 && g > 250 && b > 250)) { |
|
pixelArray.push([r, g, b]); |
|
} |
|
} |
|
} |
|
return pixelArray; |
|
} |
|
|
|
export function extractPalette( |
|
base64image: string, |
|
colorCount = 5, |
|
quality = 1 |
|
): Promise<{ colors: Color[]; imgBlob: Blob }> { |
|
return new Promise((resolve) => { |
|
const img = new Image(); |
|
img.onload = async () => { |
|
const w = img.width; |
|
const h = img.height; |
|
const canvas = document.createElement('canvas'); |
|
canvas.width = w; |
|
canvas.height = h; |
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; |
|
ctx.drawImage(img, 0, 0, w, h); |
|
const imageData = ctx.getImageData(0, 0, w, h); |
|
const pixelArray = createPixelArray(imageData.data, w * h, quality); |
|
const cmap = quantize(pixelArray, colorCount); |
|
const colors: number[][] = cmap.palette(); |
|
const tempCanvas = document.createElement('canvas'); |
|
tempCanvas.width = w / 5; |
|
tempCanvas.height = h / 5; |
|
const tempCtx = tempCanvas.getContext('2d') as CanvasRenderingContext2D; |
|
tempCtx.drawImage(img, 0, 0, w, h, 0, 0, w / 5, h / 5); |
|
|
|
const imgBlob: Blob = await new Promise((_resolve) => |
|
tempCanvas.toBlob(_resolve, 'image/jpeg', 0.8) |
|
); |
|
const colorsRGB = colors.map((color) => d3.rgb(...(color as [number, number, number]))); |
|
resolve({ |
|
colors: sortColors(colorsRGB), |
|
imgBlob |
|
}); |
|
}; |
|
img.src = base64image; |
|
}); |
|
} |
|
|
|
export async function uploadImage(imagBlob: Blob, prompt: string): string { |
|
|
|
const promptSlug = slugify(prompt); |
|
const UPLOAD_URL = dev ? 'moon/uploads' : 'https://huggingface.co/uploads'; |
|
|
|
const hash = crypto.randomUUID().split('-')[0]; |
|
const fileName = `color-palette-${hash}-${promptSlug}.jpeg`; |
|
|
|
const file = new File([imagBlob], fileName, { type: 'image/jpeg' }); |
|
|
|
console.log('uploading image', file); |
|
|
|
const response = await fetch(UPLOAD_URL, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': file.type, |
|
'X-Requested-With': 'XMLHttpRequest' |
|
}, |
|
body: file |
|
}); |
|
const url = await response.text(); |
|
|
|
console.log('uploaded images', url); |
|
return url; |
|
} |
|
|
|
function slugify(text: string) { |
|
if (!text) return ''; |
|
return text |
|
.toString() |
|
.toLowerCase() |
|
.replace(/\s+/g, '-') |
|
.replace(/[^\w\-]+/g, '') |
|
.replace(/\-\-+/g, '-') |
|
.replace(/^-+/, '') |
|
.replace(/-+$/, ''); |
|
} |
|
|