tarinai/js/assets.js

133 lines
4.5 KiB
JavaScript
Raw Normal View History

2026-06-18 18:59:56 +09:00
"use strict";
const images = new Map();
const imageBoundsCache = new WeakMap();
const imageMetrics = new Map();
2026-06-19 02:27:19 +09:00
const scaledSpriteCache = new Map();
const stainOverlayCache = new Map();
function trimCanvasCache(cache, limit = 420) {
while (cache.size > limit) {
const first = cache.keys().next().value;
if (first === undefined) break;
cache.delete(first);
}
}
function quantizedCanvasSize(v, step = 16) {
return Math.max(1, Math.round(v / step) * step);
}
const SPRITE_CACHE_SCALE = 2.0;
function getCachedSpriteCanvas(id, img, w, h) {
if (!img || !w || !h) return img;
const qw = quantizedCanvasSize(w, 16);
const qh = quantizedCanvasSize(h, 16);
const key = `${id}:${qw}x${qh}`;
let canvas = scaledSpriteCache.get(key);
if (canvas) return canvas;
canvas = document.createElement("canvas");
canvas.width = qw;
canvas.height = qh;
const c = canvas.getContext("2d");
c.imageSmoothingEnabled = true;
if ("imageSmoothingQuality" in c) c.imageSmoothingQuality = "high";
c.clearRect(0, 0, qw, qh);
c.drawImage(img, 0, 0, qw, qh);
scaledSpriteCache.set(key, canvas);
trimCanvasCache(scaledSpriteCache, 520);
return canvas;
}
function getCachedStainOverlayCanvas(id, img, w, h, stain = 0, seed = 0.5) {
if (!img || stain <= 0.01 || !w || !h) return null;
const qw = quantizedCanvasSize(w, 16);
const qh = quantizedCanvasSize(h, 16);
const bucket = Math.max(1, Math.min(8, Math.ceil(stain * 8)));
const seedBucket = Math.round((seed || 0) * 16);
const key = `${id}:${qw}x${qh}:s${bucket}:r${seedBucket}`;
let canvas = stainOverlayCache.get(key);
if (canvas) return canvas;
canvas = document.createElement("canvas");
canvas.width = qw;
canvas.height = qh;
const c = canvas.getContext("2d");
c.imageSmoothingEnabled = true;
if ("imageSmoothingQuality" in c) c.imageSmoothingQuality = "high";
c.clearRect(0, 0, qw, qh);
c.drawImage(img, 0, 0, qw, qh);
c.globalCompositeOperation = "source-in";
c.globalAlpha = 0.34 + bucket * 0.075;
c.fillStyle = "rgba(28, 92, 22, 0.96)";
const s = (seedBucket % 16) / 16;
const spots = [
[0.40 + s * 0.035, 0.82, 0.14, 0.040, -0.10],
[0.59 - s * 0.030, 0.87, 0.15, 0.044, 0.12],
[0.50, 0.91, 0.20, 0.036, 0.02],
[0.48 + s * 0.020, 0.78, 0.10, 0.030, -0.18],
];
for (const [sx, sy, rw, rh, rot] of spots) {
c.beginPath();
c.ellipse(sx * qw, sy * qh, rw * qw, rh * qh, rot, 0, Math.PI * 2);
c.fill();
}
c.globalAlpha = 1;
c.globalCompositeOperation = "source-over";
stainOverlayCache.set(key, canvas);
trimCanvasCache(stainOverlayCache, 360);
return canvas;
}
2026-06-18 18:59:56 +09:00
function loadImages() {
const assets = [...SPRITES, ...DECOR_ASSETS];
let loaded = 0;
return new Promise((resolve) => {
for (const s of assets) {
const img = new Image();
img.onload = img.onerror = () => {
const w = img.naturalWidth || img.width || ALPHA_BOUNDS[s.id]?.imageW || 512;
const h = img.naturalHeight || img.height || ALPHA_BOUNDS[s.id]?.imageH || 512;
const bounds = ALPHA_BOUNDS[s.id] || { x: 0, y: 0, w, h, imageW: w, imageH: h };
imageMetrics.set(s.id, {
w,
h,
ratio: h / Math.max(w, 1),
visibleBottomRatio: (bounds.y + bounds.h) / Math.max(bounds.imageH, 1),
});
loaded += 1;
if (loaded === assets.length) resolve();
};
img.alphaBounds = ALPHA_BOUNDS[s.id] || null;
img.src = s.path;
images.set(s.id, img);
}
});
}
function imageAlphaBounds(img) {
if (!img) return null;
if (img.alphaBounds) return img.alphaBounds;
if (imageBoundsCache.has(img)) return imageBoundsCache.get(img);
const w = img.naturalWidth || img.width || 0;
const h = img.naturalHeight || img.height || 0;
if (!w || !h) return null;
const bounds = { x: 0, y: 0, w, h, imageW: w, imageH: h };
imageBoundsCache.set(img, bounds);
return bounds;
}
function getImageMetrics(idOrImg) {
if (typeof idOrImg === "string") return imageMetrics.get(idOrImg) || null;
if (!idOrImg) return null;
for (const [id, img] of images) {
if (img === idOrImg) return imageMetrics.get(id) || null;
}
const w = idOrImg.naturalWidth || idOrImg.width || 0;
const h = idOrImg.naturalHeight || idOrImg.height || 0;
return w && h ? { w, h, ratio: h / w, visibleBottomRatio: 1 } : null;
}
2026-06-19 02:27:19 +09:00
window.TarinaiAssets = { images, imageBoundsCache, imageMetrics, scaledSpriteCache, stainOverlayCache, loadImages, imageAlphaBounds, getImageMetrics, getCachedSpriteCanvas, getCachedStainOverlayCanvas };