UNPKG

vtf-js

Version:

A javascript IO library for the Valve Texture Format.

123 lines (122 loc) 6.36 kB
import { VEncodedImageData, VImageData } from './image.js'; import { VFilters, VImageScaler } from './resize.js'; import { getMipSize, getThumbMip } from './utils.js'; /** A class for storing collections of mipmaps, frames, faces, and slices. */ export class VDataCollection { __mipmaps; constructor(mipmaps) { this.__mipmaps = mipmaps; } getImage(mip, frame, face, slice, allowEncoded = false) { if (mip >= this.__mipmaps.length) throw Error(`Mipmap ${mip} does not exist in VDataCollection!`); if (frame >= this.__mipmaps[mip].length) throw Error(`Frame ${frame} does not exist in VDataCollection!`); if (face >= this.__mipmaps[mip][frame].length) throw Error(`Face ${face} does not exist in VDataCollection!`); if (slice >= this.__mipmaps[mip][frame][face].length) throw Error(`Slice ${slice} does not exist in VDataCollection!`); let image = this.__mipmaps[mip][frame][face][slice]; const is_encoded = image instanceof VEncodedImageData; const is_decoded = image instanceof VImageData; if (is_encoded && !allowEncoded) image = this.__mipmaps[mip][frame][face][slice] = image.decode(); if (!is_encoded && !is_decoded) throw TypeError(`Expected VImageData or VEncodedImageData in VDataProvider, but found ${image.constructor.name} instead!`); return image; } getSize(mip = 0, frame = 0, face = 0, slice = 0) { if (mip >= this.__mipmaps.length) throw Error(`Mipmap ${mip} does not exist in VDataCollection!`); if (frame >= this.__mipmaps[mip].length) throw Error(`Frame ${frame} does not exist in VDataCollection!`); if (face >= this.__mipmaps[mip][frame].length) throw Error(`Face ${face} does not exist in VDataCollection!`); if (slice >= this.__mipmaps[mip][frame][face].length) throw Error(`Slice ${slice} does not exist in VDataCollection!`); const img = this.__mipmaps[mip][frame][face][slice]; return [img.width, img.height]; } mipmapCount() { return this.__mipmaps.length; } frameCount() { return this.__mipmaps[0]?.length ?? 0; } faceCount() { return this.__mipmaps[0]?.[0]?.length ?? 0; } sliceCount() { return this.__mipmaps[0]?.[0]?.[0]?.length ?? 0; } } /** A class that extends the base provider interface, but automatically generates mipmaps. */ export class VMipmapProvider { __frames; __mipmap_count; __filter; __scalers; constructor(frames, options) { this.__frames = frames; const [width, height] = this.getSize(0, 0, 0, 0); this.__mipmap_count = options?.mipmaps ?? getThumbMip(width, height, 1) + 1; this.__filter = options?.filter ?? VFilters.Triangle; this.__scalers = new Array(this.__mipmap_count); } getImage(mip, frame, face, slice, allowEncoded = false) { if (mip >= this.__mipmap_count) throw new Error(`Mipmap ${mip} does not exist in VMipmapProvider!`); if (frame >= this.__frames.length) throw new Error(`Frame ${frame} does not exist in VMipmapProvider!`); if (face >= this.__frames[frame].length) throw new Error(`Face ${face} does not exist in VMipmapProvider!`); if (slice >= this.__frames[frame][face].length) throw new Error(`Slice ${slice} does not exist in VMipmapProvider!`); // Get image from storage and return if allowed let original = this.__frames[frame][face][slice]; const [width, height] = this.getSize(mip, frame, face, slice); const size_matches = width === original.width && height === original.height; if (size_matches && allowEncoded) return original; // Decode if necessary if (original instanceof VEncodedImageData) original = this.__frames[frame][face][slice] = original.decode(); else if (!(original instanceof VImageData)) throw TypeError(`Expected VImageData or VEncodedImageData in VDataProvider, but found ${original.constructor.name} instead!`); // Resize if necessary if (size_matches) return original; const scaler = (this.__scalers[mip] ??= new VImageScaler(original.width, original.height, width, height, this.__filter)); const out_data = new (original.getDataConstructor())(width * height * 4); const out_image = new VImageData(out_data, width, height); return scaler.resize(original, out_image); } getSize(mip = 0, frame = 0, face = 0, slice = 0) { if (mip >= this.__mipmap_count) throw new Error(`Mipmap ${mip} does not exist in VMipmapProvider!`); if (frame >= this.__frames.length) throw new Error(`Frame ${frame} does not exist in VMipmapProvider!`); if (face >= this.__frames[frame].length) throw new Error(`Face ${face} does not exist in VMipmapProvider!`); if (slice >= this.__frames[frame][face].length) throw new Error(`Slice ${slice} does not exist in VMipmapProvider!`); const img = this.__frames[frame][face][slice]; return getMipSize(mip, img.width, img.height); } mipmapCount() { return this.__mipmap_count; } frameCount() { return this.__mipmap_count ? this.__frames.length ?? 0 : 0; } faceCount() { return this.__mipmap_count ? this.__frames[0]?.length ?? 0 : 0; } sliceCount() { return this.__mipmap_count ? this.__frames[0]?.[0]?.length ?? 0 : 0; } } /** A class that extends VMipmapProvider but takes an array of frames in the constructor. */ export class VFrameCollection extends VMipmapProvider { constructor(frames, options) { const inFrames = frames.map(frame => [[frame]]); super(inFrames, options); } } /** A class that extends VMipmapProvider but takes an array of faces in the constructor. */ export class VFaceCollection extends VMipmapProvider { constructor(faces, options) { const inFrames = faces.map(frame => [frame]); super([inFrames], options); } } /** A class that extends VMipmapProvider but takes an array of slices in the constructor. */ export class VSliceCollection extends VMipmapProvider { constructor(slices, options) { super([[slices]], options); } }