97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
// Minimal DXT1/3/5 decoder. Everything is decoded to RGBA8 so the viewer has a
|
|
// single upload path and doesn't depend on the S3TC extension being present.
|
|
|
|
function color565(c, out, i) {
|
|
out[i] = ((c >> 11) & 0x1f) * 255 / 31;
|
|
out[i + 1] = ((c >> 5) & 0x3f) * 255 / 63;
|
|
out[i + 2] = (c & 0x1f) * 255 / 31;
|
|
}
|
|
|
|
function buildPalette(data, offset, dxt1) {
|
|
const c0 = data[offset] | (data[offset + 1] << 8);
|
|
const c1 = data[offset + 2] | (data[offset + 3] << 8);
|
|
const p = new Uint8Array(16);
|
|
color565(c0, p, 0);
|
|
color565(c1, p, 4);
|
|
p[3] = p[7] = 255;
|
|
if (c0 > c1 || !dxt1) {
|
|
for (let i = 0; i < 3; i++) {
|
|
p[8 + i] = (2 * p[i] + p[4 + i]) / 3;
|
|
p[12 + i] = (p[i] + 2 * p[4 + i]) / 3;
|
|
}
|
|
p[11] = p[15] = 255;
|
|
} else {
|
|
for (let i = 0; i < 3; i++) {
|
|
p[8 + i] = (p[i] + p[4 + i]) / 2;
|
|
p[12 + i] = 0;
|
|
}
|
|
p[11] = 255;
|
|
p[15] = 0; // 1 bit punch-through alpha
|
|
}
|
|
return p;
|
|
}
|
|
|
|
export function decodeDXT(data, width, height, format) {
|
|
const out = new Uint8Array(width * height * 4);
|
|
const blocksX = Math.max(1, (width + 3) >> 2);
|
|
const blocksY = Math.max(1, (height + 3) >> 2);
|
|
const blockSize = format === 1 ? 8 : 16;
|
|
let offset = 0;
|
|
|
|
for (let by = 0; by < blocksY; by++) {
|
|
for (let bx = 0; bx < blocksX; bx++, offset += blockSize) {
|
|
if (offset + blockSize > data.length) return out;
|
|
const colorOffset = format === 1 ? offset : offset + 8;
|
|
const palette = buildPalette(data, colorOffset, format === 1);
|
|
const bits =
|
|
data[colorOffset + 4] |
|
|
(data[colorOffset + 5] << 8) |
|
|
(data[colorOffset + 6] << 16) |
|
|
(data[colorOffset + 7] << 24);
|
|
|
|
// DXT5 alpha: two endpoints plus 3 bit indices.
|
|
let a0 = 0, a1 = 0, alphaBits0 = 0, alphaBits1 = 0;
|
|
if (format === 5) {
|
|
a0 = data[offset];
|
|
a1 = data[offset + 1];
|
|
alphaBits0 = data[offset + 2] | (data[offset + 3] << 8) | (data[offset + 4] << 16);
|
|
alphaBits1 = data[offset + 5] | (data[offset + 6] << 8) | (data[offset + 7] << 16);
|
|
}
|
|
|
|
for (let py = 0; py < 4; py++) {
|
|
for (let px = 0; px < 4; px++) {
|
|
const x = bx * 4 + px;
|
|
const y = by * 4 + py;
|
|
if (x >= width || y >= height) continue;
|
|
const pixel = py * 4 + px;
|
|
const idx = ((bits >>> (pixel * 2)) & 3) * 4;
|
|
const dst = (y * width + x) * 4;
|
|
out[dst] = palette[idx];
|
|
out[dst + 1] = palette[idx + 1];
|
|
out[dst + 2] = palette[idx + 2];
|
|
|
|
if (format === 1) {
|
|
out[dst + 3] = palette[idx + 3];
|
|
} else if (format === 3) {
|
|
// 4 bits of alpha per pixel, straight from the first 8 bytes.
|
|
const nibble = data[offset + (pixel >> 1)];
|
|
out[dst + 3] = (pixel & 1 ? nibble >> 4 : nibble & 0x0f) * 17;
|
|
} else {
|
|
const shift = (pixel % 8) * 3;
|
|
const code = ((pixel < 8 ? alphaBits0 : alphaBits1) >>> shift) & 7;
|
|
let a;
|
|
if (code === 0) a = a0;
|
|
else if (code === 1) a = a1;
|
|
else if (a0 > a1) a = ((8 - code) * a0 + (code - 1) * a1) / 7;
|
|
else if (code === 6) a = 0;
|
|
else if (code === 7) a = 255;
|
|
else a = ((6 - code) * a0 + (code - 1) * a1) / 5;
|
|
out[dst + 3] = a;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|