(function (global) { 'use strict'; const root = global.PixelIslandModules ||= {}; const MAX_DIMENSION = 64; function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function clampInt(value, min, max, fallback = min) { const n = Math.round(Number(value)); return Number.isFinite(n) ? clamp(n, min, max) : fallback; } function clampDimension(value, fallback = 8) { return clampInt(value, 1, MAX_DIMENSION, fallback); } function fnv1a(value) { let hash = 0x811c9dc5; const text = String(value); for (let i = 0; i < text.length; i++) { hash ^= text.charCodeAt(i); hash = Math.imul(hash, 0x01000193) >>> 0; } return hash.toString(16).padStart(8, '0'); } function assetWidth(asset) { return clampInt(asset?.width ?? asset?.w ?? asset?.size, 1, MAX_DIMENSION, 16); } function assetHeight(asset) { return clampInt(asset?.height ?? asset?.ht ?? asset?.size, 1, MAX_DIMENSION, assetWidth(asset)); } function assetMaxSize(asset) { return Math.max(assetWidth(asset), assetHeight(asset)); } function blankPixels(width, height = width) { return Array(Math.max(1, width) * Math.max(1, height)).fill(null); } function parseHex(hex) { if (typeof hex !== 'string' || !hex.startsWith('#')) return null; const clean = hex.replace('#', ''); const full = clean.length === 3 ? clean.split('').map((c) => c + c).join('') : clean; const value = parseInt(full, 16); if (!Number.isFinite(value)) return null; return { r: (value >> 16) & 255, g: (value >> 8) & 255, b: value & 255 }; } function nearestPaletteCodeFrom(entries, color, fallback) { if (!Array.isArray(entries) || !entries.length) return fallback || '0'; if (!color || typeof color !== 'string') return fallback || entries[0]?.code || '0'; if (entries.some((entry) => entry.code === color)) return color; const rgb = parseHex(color); if (!rgb) return fallback || entries[0]?.code || '0'; let best = entries[0]?.code || '0'; let bestDist = Infinity; for (const entry of entries) { const entryRgb = parseHex(entry.color); if (!entryRgb) continue; const dist = (rgb.r - entryRgb.r) ** 2 + (rgb.g - entryRgb.g) ** 2 + (rgb.b - entryRgb.b) ** 2; if (dist < bestDist) { bestDist = dist; best = entry.code; } } return best; } function normalizePixels(pixels, width, height = width, palette = root.Palette?.PALETTE || [], defaultColorCode = root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a') { const out = blankPixels(width, height); const paletteByCode = new Map((palette || []).map((entry) => [entry.code, entry.color])); if (typeof pixels === 'string') { for (let i = 0; i < Math.min(out.length, pixels.length); i++) { const ch = pixels[i]; out[i] = ch === '.' ? null : (paletteByCode.has(ch) ? ch : nearestPaletteCodeFrom(palette, ch, defaultColorCode)); } return out; } if (!Array.isArray(pixels)) return out; for (let i = 0; i < Math.min(out.length, pixels.length); i++) { const value = pixels[i]; if (!value) out[i] = null; else if (paletteByCode.has(value)) out[i] = value; else out[i] = nearestPaletteCodeFrom(palette, value, defaultColorCode); } return out; } function encodePixels(pixels, width = null, height = null, palette = root.Palette?.PALETTE || [], defaultColorCode = root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a') { const w = width || Math.sqrt(pixels?.length || 0) || 8; const h = height || w; return normalizePixels(pixels, w, h, palette, defaultColorCode).map((value) => value || '.').join(''); } function buildPixelBlob(encodedPixels, width, height = width, palette = root.Palette?.PALETTE || [], defaultColorCode = root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a') { const w = clampDimension(width, 16); const h = clampDimension(height, w); const payload = encodePixels(encodedPixels, w, h, palette, defaultColorCode); const codec = 'palette-index-v1'; return { id: computePixelBlobId(codec, w, h, payload, palette, defaultColorCode), codec, width: w, height: h, payload }; } function computePixelBlobId(codec, width, height, payload, palette = root.Palette?.PALETTE || [], defaultColorCode = root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a') { const normalizedCodec = codec || 'palette-index-v1'; const w = clampDimension(width, 16); const h = clampDimension(height, w); const encoded = encodePixels(payload, w, h, palette, defaultColorCode); return `blob:${fnv1a(['pixel-blob-v1', normalizedCodec, w, h, encoded].join('|'))}`; } function ensureAssetBlobId(asset, palette = root.Palette?.PALETTE || [], defaultColorCode = root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a') { if (!asset) return asset; const w = assetWidth(asset); const h = assetHeight(asset); const right = normalizePixels(asset.faces?.right || asset.pixels || blankPixels(w, h), w, h, palette, defaultColorCode); const blob = buildPixelBlob(right, w, h, palette, defaultColorCode); return { ...asset, blobId: asset.blobId || asset.bi || blob.id }; } function colorToHex(value) { if (!value) return 'rgba(0,0,0,0)'; return root.Palette?.PALETTE_BY_CODE?.[value] || value; } function nearestPaletteCode(color) { return nearestPaletteCodeFrom(root.Palette?.PALETTE || [], color, root.Palette?.DEFAULT_SELECTED_COLOR_CODE || 'a'); } function nearestBasicPaletteCode(color) { const palette = root.Palette?.PALETTE || []; const count = root.Palette?.BASIC_PALETTE_COUNT || palette.length; return nearestPaletteCodeFrom(palette.slice(0, count), root.Palette?.PALETTE_BY_CODE?.[color] || color, palette[0]?.code || '0'); } function readableTextColor(hex) { const rgb = parseHex(hex); if (!rgb) return '#243044'; const yiq = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; return yiq > 140 ? '#243044' : '#fffdf5'; } root.PixelCodec = { MAX_DIMENSION, assetHeight, assetMaxSize, assetWidth, blankPixels, buildPixelBlob, clampDimension, clampInt, colorToHex, computePixelBlobId, ensureAssetBlobId, encodePixels, fnv1a, nearestBasicPaletteCode, nearestPaletteCode, nearestPaletteCodeFrom, normalizePixels, parseHex, readableTextColor }; })(typeof self !== 'undefined' ? self : window);