Files
losadev-tools/app/public/legacy/js/dds.js
T

183 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
// Texture decoding for the UI Image Set editor: DDS (DXT1/DXT3/DXT5, 32/24/16-bit RGB masks) and
// TGA (true-colour, RLE) → ImageData. BMP/PNG/JPG go through the browser (createImageBitmap).
const TexDecode = (() => {
const rgb565 = (c) => [((c >> 11) & 31) * 255 / 31, ((c >> 5) & 63) * 255 / 63, (c & 31) * 255 / 31];
/** One 4×4 DXT colour block → 16 RGB(A) values into out (at pixel x,y). */
function colorBlock(src, o, out, w, h, x, y, dxt1, alphaSrc) {
const c0 = src[o] | (src[o + 1] << 8);
const c1 = src[o + 2] | (src[o + 3] << 8);
const p0 = rgb565(c0);
const p1 = rgb565(c1);
const pal = [p0, p1];
if (!dxt1 || c0 > c1) {
pal.push([(2 * p0[0] + p1[0]) / 3, (2 * p0[1] + p1[1]) / 3, (2 * p0[2] + p1[2]) / 3]);
pal.push([(p0[0] + 2 * p1[0]) / 3, (p0[1] + 2 * p1[1]) / 3, (p0[2] + 2 * p1[2]) / 3]);
} else {
pal.push([(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2, (p0[2] + p1[2]) / 2]);
pal.push(null); // transparent black (DXT1 1-bit alpha)
}
const bits = src[o + 4] | (src[o + 5] << 8) | (src[o + 6] << 16) | (src[o + 7] << 24);
for (let i = 0; i < 16; i++) {
const px = x + (i & 3);
const py = y + (i >> 2);
if (px >= w || py >= h) continue;
const c = pal[(bits >>> (2 * i)) & 3];
const d = (py * w + px) * 4;
if (!c) {
out[d] = out[d + 1] = out[d + 2] = out[d + 3] = 0;
continue;
}
out[d] = c[0];
out[d + 1] = c[1];
out[d + 2] = c[2];
out[d + 3] = alphaSrc ? alphaSrc[i] : 255;
}
}
function dxt(src, offset, w, h, kind) {
const out = new Uint8ClampedArray(w * h * 4);
const blockBytes = kind === "DXT1" ? 8 : 16;
let o = offset;
const alpha = new Uint8Array(16);
for (let y = 0; y < h; y += 4) {
for (let x = 0; x < w; x += 4) {
let a = null;
if (kind === "DXT3") {
for (let i = 0; i < 8; i++) {
const b = src[o + i];
alpha[i * 2] = (b & 15) * 17;
alpha[i * 2 + 1] = (b >> 4) * 17;
}
a = alpha;
} else if (kind === "DXT5") {
const a0 = src[o];
const a1 = src[o + 1];
const pal = [a0, a1];
if (a0 > a1) for (let i = 1; i < 7; i++) pal.push(((7 - i) * a0 + i * a1) / 7);
else {
for (let i = 1; i < 5; i++) pal.push(((5 - i) * a0 + i * a1) / 5);
pal.push(0, 255);
}
// 48 bits of 3-bit indices
let lo = src[o + 2] | (src[o + 3] << 8) | (src[o + 4] << 16);
let hi = src[o + 5] | (src[o + 6] << 8) | (src[o + 7] << 16);
for (let i = 0; i < 8; i++) alpha[i] = pal[(lo >> (3 * i)) & 7];
for (let i = 0; i < 8; i++) alpha[8 + i] = pal[(hi >> (3 * i)) & 7];
a = alpha;
}
colorBlock(src, o + (kind === "DXT1" ? 0 : 8), out, w, h, x, y, kind === "DXT1", a);
o += blockBytes;
}
}
return new ImageData(out, w, h);
}
const shiftOf = (mask) => {
if (!mask) return [0, 0];
let s = 0;
while (!((mask >>> s) & 1)) s++;
let n = 0;
while ((mask >>> (s + n)) & 1) n++;
return [s, n];
};
function masked(src, offset, w, h, bpp, masks, hasAlpha) {
const out = new Uint8ClampedArray(w * h * 4);
const bytes = bpp / 8;
const pitch = w * bytes;
const chans = masks.map(shiftOf);
const dv = new DataView(src.buffer, src.byteOffset, src.byteLength);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const o = offset + y * pitch + x * bytes;
const v = bytes === 4 ? dv.getUint32(o, true) : bytes === 3 ? src[o] | (src[o + 1] << 8) | (src[o + 2] << 16) : dv.getUint16(o, true);
const d = (y * w + x) * 4;
for (let c = 0; c < 3; c++) {
const [s, n] = chans[c];
out[d + c] = n ? (((v >>> s) & ((1 << n) - 1)) * 255) / ((1 << n) - 1) : 0;
}
const [as, an] = chans[3];
out[d + 3] = hasAlpha && an ? (((v >>> as) & ((1 << an) - 1)) * 255) / ((1 << an) - 1) : 255;
}
}
return new ImageData(out, w, h);
}
function dds(bytes) {
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
if (dv.getUint32(0, true) !== 0x20534444) throw new Error("Not a DDS file");
const h = dv.getUint32(12, true);
const w = dv.getUint32(16, true);
const pf = dv.getUint32(80, true);
const fourCC = String.fromCharCode(bytes[84], bytes[85], bytes[86], bytes[87]);
const offset = 128;
if (pf & 4) {
if (fourCC === "DXT1" || fourCC === "DXT3" || fourCC === "DXT5") return dxt(bytes, offset, w, h, fourCC);
if (fourCC === "DXT2" || fourCC === "DXT4") return dxt(bytes, offset, w, h, fourCC === "DXT2" ? "DXT3" : "DXT5");
throw new Error(`DDS format ${fourCC} isn't supported`);
}
const bpp = dv.getUint32(88, true);
const masks = [dv.getUint32(92, true), dv.getUint32(96, true), dv.getUint32(100, true), dv.getUint32(104, true)];
return masked(bytes, offset, w, h, bpp, masks, !!(pf & 1));
}
function tga(bytes) {
const idLen = bytes[0];
const type = bytes[2];
const w = bytes[12] | (bytes[13] << 8);
const h = bytes[14] | (bytes[15] << 8);
const bpp = bytes[16];
const topDown = (bytes[17] & 0x20) !== 0;
if (![2, 10].includes(type) || ![24, 32].includes(bpp)) throw new Error("Only true-colour TGA (24/32-bit) is supported");
const px = bpp / 8;
const out = new Uint8ClampedArray(w * h * 4);
let o = 18 + idLen;
let i = 0;
const put = (b, g, r, a) => {
const x = i % w;
const y = Math.floor(i / w);
const d = ((topDown ? y : h - 1 - y) * w + x) * 4;
out[d] = r;
out[d + 1] = g;
out[d + 2] = b;
out[d + 3] = a;
i++;
};
while (i < w * h) {
if (type === 2) {
put(bytes[o], bytes[o + 1], bytes[o + 2], px === 4 ? bytes[o + 3] : 255);
o += px;
continue;
}
const hdr = bytes[o++];
const n = (hdr & 127) + 1;
if (hdr & 128) {
const c = [bytes[o], bytes[o + 1], bytes[o + 2], px === 4 ? bytes[o + 3] : 255];
o += px;
for (let k = 0; k < n; k++) put(...c);
} else {
for (let k = 0; k < n; k++) {
put(bytes[o], bytes[o + 1], bytes[o + 2], px === 4 ? bytes[o + 3] : 255);
o += px;
}
}
}
return new ImageData(out, w, h);
}
/** Any supported texture → ImageBitmap. */
async function decode(bytes, ext) {
const e = (ext || "").toLowerCase();
if (e === "dds") return createImageBitmap(dds(bytes));
if (e === "tga") return createImageBitmap(tga(bytes));
return createImageBitmap(new Blob([bytes]));
}
return { decode, dds, tga };
})();
window.TexDecode = TexDecode;