// --------------------------------------------------------------------------- // Atlas texture helpers: decode the baked sheet into a Pixi texture and slice // per-entry sub-textures from it on demand (cached). // --------------------------------------------------------------------------- import { Rectangle, Texture } from 'pixi.js' import type { AtlasEntry } from './contract' /** Decode a data URL (or any image URL) into a nearest-neighbour Texture, so the * baked pixel art stays crisp under the renderer's uniform zoom. */ export async function loadTexture(url: string): Promise { const img = new Image() // Request CORS so a remote atlas (e.g. an atproto PDS blob) can be uploaded to // WebGL without tainting the canvas. Harmless for same-origin `data:`/`blob:` // URLs (the editor's case), required for cross-origin http(s) URLs. img.crossOrigin = 'anonymous' img.src = url await img.decode() const tex = Texture.from(img) tex.source.scaleMode = 'nearest' return tex } /** A lazily-built, cached lookup from atlas entry id → its sub-texture of the * baked sheet. Returns null when there is no atlas (colour-only / failed bake) * so callers can skip art that isn't available. */ export interface AtlasSlicer { texFor: (id: number) => Texture | null /** Destroy every interned sub-texture (NOT the underlying atlas source). */ destroy: () => void } export function makeAtlasSlicer(atlas: Texture | null, entries: AtlasEntry[]): AtlasSlicer { const cache = new Map() return { texFor(id) { if (!atlas) return null let t = cache.get(id) if (!t) { const e = entries[id] if (!e) return null t = new Texture({ source: atlas.source, frame: new Rectangle(e.x, e.y, e.w, e.h) }) cache.set(id, t) } return t }, destroy() { for (const t of cache.values()) t.destroy(false) cache.clear() }, } }