vtf-js
Version:
A javascript IO library for the Valve Texture Format.
145 lines (144 loc) • 5.13 kB
JavaScript
import { VImageData } from '../core/image.js';
// let p_totalTime = 0.0;
// let p_colorTime = 0.0;
// let p_alphaTime = 0.0;
// let p_runCount = 0;
function decode565(x1, x2, out, o) {
const v = x1 | (x2 << 8);
const r = (v >> 11) & 0x1f;
const g = (v >> 5) & 0x3f;
const b = v & 0x1f;
out[o] = (r << 3) | (r >> 2);
out[o + 1] = (g << 2) | (g >> 4);
out[o + 2] = (b << 3) | (b >> 2);
out[o + 3] = 255;
return v;
}
// Reuse memory across calls and pray we don't multithread
const codes = new Uint8Array(16);
function decompressColor(block, flags, out_rgba) {
// const codes = new Uint8Array(16);
const a = decode565(block[0], block[1], codes, 0);
const b = decode565(block[2], block[3], codes, 4);
const isDxt1 = (flags & 1 /* DxtFlags.DXT1 */) !== 0;
const oneBitAlpha = (flags & 256 /* DxtFlags.OneBitAlpha */) !== 0;
// Mode A
if (isDxt1 && a <= b) {
codes[8] = (codes[0] + codes[4]) / 2;
codes[9] = (codes[1] + codes[5]) / 2;
codes[10] = (codes[2] + codes[6]) / 2;
codes[11] = 255;
codes[15] = oneBitAlpha ? 0 : 255;
}
// Mode B
else {
codes[8] = (codes[0] * 2 + codes[4]) / 3;
codes[9] = (codes[1] * 2 + codes[5]) / 3;
codes[10] = (codes[2] * 2 + codes[6]) / 3;
codes[12] = (codes[0] + codes[4] * 2) / 3;
codes[13] = (codes[1] + codes[5] * 2) / 3;
codes[14] = (codes[2] + codes[6] * 2) / 3;
codes[11] = codes[15] = 255;
}
// Unpack indices and choose from palette
let p = 0;
for (let byte = 4; byte < 8; byte++) {
const bits = block[byte];
for (let i = 0; i < 4; i++, p += 4) {
const idx = ((bits >> (i * 2)) & 3) * 4;
out_rgba[p] = codes[idx];
out_rgba[p + 1] = codes[idx + 1];
out_rgba[p + 2] = codes[idx + 2];
out_rgba[p + 3] = codes[idx + 3];
}
}
}
function decompressDxt3Alpha(block, out_rgba) {
for (let i = 0, c = 0; i < 8; i++, c += 8) {
const b = block[i];
const a0 = b & 0x0f;
const a1 = b & 0xf0;
out_rgba[c + 3] = a0 | a0 << 4;
out_rgba[c + 7] = a1 | a1 >> 4;
}
}
function decompressDxt5Alpha(block, out_rgba) {
// const codes = new Uint8Array(8);
const a = codes[0] = block[0];
const b = codes[1] = block[1];
// 5-blend mode
if (a <= b) {
codes[2] = (a * 4 + b) / 5;
codes[3] = (a * 3 + b * 2) / 5;
codes[4] = (a * 2 + b * 3) / 5;
codes[5] = (a + b * 4) / 5;
codes[6] = 0;
codes[7] = 255;
}
else {
codes[2] = (a * 6 + b) / 7;
codes[3] = (a * 5 + b * 2) / 7;
codes[4] = (a * 4 + b * 3) / 7;
codes[5] = (a * 3 + b * 4) / 7;
codes[6] = (a * 2 + b * 5) / 7;
codes[7] = (a + b * 6) / 7;
}
// Unpack indices and choose from palette
let p = 0;
for (let byte = 2; byte < 8; byte += 3) {
const bits = block[byte] | (block[byte + 1] << 8) | (block[byte + 2] << 16);
for (let i = 0; i < 24; i += 3, p += 4) {
const idx = (bits >> i) & 7;
out_rgba[p + 3] = codes[idx];
}
}
}
const HAS_ALPHA_BLOCK = 2 /* DxtFlags.DXT3 */ | 4 /* DxtFlags.DXT5 */;
export function decompressBlock(block, flags, out_rgba) {
const colorData = (flags & HAS_ALPHA_BLOCK) ? block.subarray(8) : block;
decompressColor(colorData, flags, out_rgba);
if (flags & 4 /* DxtFlags.DXT5 */)
decompressDxt5Alpha(block, out_rgba);
else if (flags & 2 /* DxtFlags.DXT3 */)
decompressDxt3Alpha(block, out_rgba);
return;
}
export function decompressImage(image, flags) {
const data = image.data;
const out = new Uint8Array(image.width * image.height * 4);
const blockSize = (flags & HAS_ALPHA_BLOCK) ? 16 : 8;
const blockDest = new Uint8Array(64);
let blockIdx = 0;
for (let y = 0; y < image.height; y += 4) {
for (let x = 0; x < image.width; x += 4) {
// Decompress block
const blockSrc = data.subarray(blockIdx, blockIdx + blockSize);
decompressBlock(blockSrc, flags, blockDest);
// Copy decompressed block to image
for (let by = 0; by < 4; by++) {
if (y + by >= image.height)
break;
for (let bx = 0; bx < 4; bx++) {
if (x + bx >= image.width)
break;
const i_dst = ((y + by) * image.width + x + bx) * 4;
const i_src = (by * 4 + bx) * 4;
out[i_dst] = blockDest[i_src];
out[i_dst + 1] = blockDest[i_src + 1];
out[i_dst + 2] = blockDest[i_src + 2];
out[i_dst + 3] = blockDest[i_src + 3];
}
}
blockIdx += blockSize;
} // x in image.width
} // y in image.height
return new VImageData(out, image.width, image.height);
}
// export function dumpPerfStats() {
// return {
// p_totalTime,
// p_colorTime,
// p_alphaTime,
// p_runCount
// };
// }