import type { PropertyBag } from '../model/Properties'; import type { Project } from '../model/Project'; import { resolveTexture, type TextureRef } from '../model/texture'; export type SizeAxis = 'width' | 'height'; export interface PixelSize { w: number; h: number; } /** * Pixel size of the picture a sprite wears. Null while that * is not known: an upload still running has no measured size yet, and a ratio taken from 0 × 0 * would be invented. */ export function texturePixelSize(project: Project, props: PropertyBag): PixelSize | null { const ref = resolveTexture(project, props); return ref ? pictureSize(ref) : null; } /** A frame's rectangle, else the whole image. */ export function pictureSize(ref: TextureRef): PixelSize | null { const w = ref.rect ? ref.rect.w : ref.sourceWidth; const h = ref.rect ? ref.rect.h : ref.sourceHeight; return w > 0 && h > 0 ? { w, h } : null; } /** A sprite wearing the texture at its own pixel size – or at the default size while that is unknown. */ export function spriteFromTexture(project: Project, textureId: string): PropertyBag { const props: PropertyBag = { texture: textureId }; const size = texturePixelSize(project, props); return size ? { ...props, width: size.w, height: size.h } : props; } /** * What `axis` has to become for the sprite to carry the picture's proportions, leaving the * other side where it is. Null when the other side is not a usable number. */ export function fitAxisToAspect(size: PixelSize, props: PropertyBag, axis: SizeAxis): number | null { const other = Number(props[axis === 'width' ? 'height' : 'width']); if (!Number.isFinite(other) || other <= 0) return null; const fitted = axis === 'width' ? (other * size.w) / size.h : (other * size.h) / size.w; return Number.isFinite(fitted) && fitted > 0 ? fitted : null; } /** * Whether the sprite is drawn at the picture's own pixel size – one image pixel per world unit. * Exact, unlike `matchesAspect`: these two numbers are written as they are, with no division in * between that could cost a digit. */ export function matchesPixelSize(size: PixelSize, props: PropertyBag): boolean { return Number(props.width) === size.w && Number(props.height) === size.h; } /** * Cross product with a relative tolerance, because 3135 of the 40 000 sizes up to 200 × 200 fail an * exact test right after their own fit (rounding). */ export function matchesAspect(size: PixelSize, props: PropertyBag): boolean { const w = Number(props.width); const h = Number(props.height); if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return false; return Math.abs(w * size.h - h * size.w) <= 1e-9 * w * size.h; }