vtf-js
Version:
A javascript IO library for the Valve Texture Format.
123 lines (122 loc) • 6.36 kB
JavaScript
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);
}
}