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) { // from https://github.com/lokesh/color-thief 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 pixel is mostly opaque and not white 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 { // simple regex slugify string for file name 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 /// <- File inherits from Blob }); 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(/-+$/, ''); }