fix(refboard): revert image size cap, keep Assets.unload() cleanup

Remove server-side Sharp resize and texture dimension cap — PixiJS
handles large textures natively. Keep the proper Assets.unload() fix
to prevent "TextureSource destroyed instead of unloaded" warnings.
This commit is contained in:
Hiren Kangad
2026-03-10 10:36:07 +05:30
parent 03727cc014
commit 2b708f50c6
2 changed files with 7 additions and 34 deletions
+4 -20
View File
@@ -18,35 +18,19 @@ app.get('/health', (_req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() }); res.json({ status: 'ok', timestamp: new Date().toISOString() });
}); });
// ---- Image proxy (MinIO → browser, optional resize via ?w=) ---- // ---- Image proxy (MinIO → browser) ----
app.get('/api/images/*', async (req, res) => { app.get('/api/images/*', async (req, res) => {
try { try {
const objectPath = req.params[0]; // everything after /api/images/ const objectPath = req.params[0]; // everything after /api/images/
if (!objectPath) return res.status(400).json({ error: 'Missing path' }); if (!objectPath) return res.status(400).json({ error: 'Missing path' });
const { minioClient, MINIO_BUCKET } = require('./minio'); const { minioClient, MINIO_BUCKET } = require('./minio');
const stat = await minioClient.statObject(MINIO_BUCKET, objectPath); const stat = await minioClient.statObject(MINIO_BUCKET, objectPath);
const contentType = stat.metaData?.['content-type'] || ''; if (stat.metaData?.['content-type']) {
res.setHeader('Content-Type', stat.metaData['content-type']);
}
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
const stream = await minioClient.getObject(MINIO_BUCKET, objectPath); const stream = await minioClient.getObject(MINIO_BUCKET, objectPath);
const maxW = parseInt(req.query.w, 10);
const isImage = contentType.startsWith('image/') && !contentType.includes('svg');
if (maxW > 0 && isImage) {
// Resize on the fly with Sharp — cap width, preserve aspect ratio
const sharp = require('sharp');
const transform = sharp()
.resize({ width: maxW, withoutEnlargement: true })
.on('error', () => {
// If sharp fails (e.g. unsupported format), just pipe original
res.setHeader('Content-Type', contentType);
});
// Sharp outputs same format by default
if (contentType) res.setHeader('Content-Type', contentType);
stream.pipe(transform).pipe(res);
} else {
if (contentType) res.setHeader('Content-Type', contentType);
stream.pipe(res); stream.pipe(res);
}
} catch (err) { } catch (err) {
if (err.code === 'NoSuchKey' || err.code === 'NotFound') { if (err.code === 'NoSuchKey' || err.code === 'NotFound') {
return res.status(404).json({ error: 'Image not found' }); return res.status(404).json({ error: 'Image not found' });
+2 -13
View File
@@ -1,10 +1,5 @@
import { Texture, Assets } from "pixi.js"; import { Texture, Assets } from "pixi.js";
// Max texture dimension — avoids WebGL INVALID_VALUE on large images.
// Most GPUs support 4096 or 8192; we cap at 2048 for memory efficiency
// since display sizes are typically 300-600px anyway.
const MAX_TEXTURE_DIM = 2048;
interface TextureEntry { interface TextureEntry {
texture: Texture; texture: Texture;
url: string; // needed for Assets.unload() url: string; // needed for Assets.unload()
@@ -14,12 +9,11 @@ interface TextureEntry {
/** /**
* TextureManager — GPU texture cache with LRU eviction and memory budget. * TextureManager — GPU texture cache with LRU eviction and memory budget.
* Caps texture dimensions at MAX_TEXTURE_DIM to prevent WebGL OOM.
* Uses Assets.unload() instead of texture.destroy() for proper cleanup. * Uses Assets.unload() instead of texture.destroy() for proper cleanup.
*/ */
export class TextureManager { export class TextureManager {
private cache = new Map<string, TextureEntry>(); private cache = new Map<string, TextureEntry>();
private budget = 256 * 1024 * 1024; // 256 MB (conservative for 90+ items) private budget = 512 * 1024 * 1024; // 512 MB
private currentUsage = 0; private currentUsage = 0;
/** Build the URL for a given asset. */ /** Build the URL for a given asset. */
@@ -33,7 +27,6 @@ export class TextureManager {
/** /**
* Load a texture for the given asset. * Load a texture for the given asset.
* Returns cached texture if available, otherwise fetches and caches. * Returns cached texture if available, otherwise fetches and caches.
* Large images are downscaled server-side via width param to stay within GPU limits.
*/ */
async load(assetKey: string): Promise<Texture> { async load(assetKey: string): Promise<Texture> {
const existing = this.cache.get(assetKey); const existing = this.cache.get(assetKey);
@@ -42,11 +35,7 @@ export class TextureManager {
return existing.texture; return existing.texture;
} }
// Request capped size from server to avoid loading huge textures into GPU const url = this.urlForAsset(assetKey);
let url = this.urlForAsset(assetKey);
if (!url.includes('?')) {
url += `?w=${MAX_TEXTURE_DIM}`;
}
let texture: Texture; let texture: Texture;
try { try {