Files
mtgodot-poc/extension/src/dxt.cpp
T

183 lines
7.1 KiB
C++
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.
// engine/dxt —— DDS(DXT1/3/5) + 未压缩 BGRA8 → RGBA8 软解。M1 T3。
#include "dxt.h"
#include <cstdio>
#include <cstring>
namespace mtgodot {
namespace {
inline uint32_t rd_le32(const uint8_t* p) {
return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
}
// 5:6:5 → r,g,b (0..255)
inline void unpack565(uint16_t c, int& r, int& g, int& b) {
r = ((c >> 11) & 0x1f); r = (r << 3) | (r >> 2);
g = ((c >> 5) & 0x3f); g = (g << 2) | (g >> 4);
b = (c & 0x1f); b = (b << 3) | (b >> 2);
}
// 解一个 DXT1 颜色块(8B)→ 16 像素 RGB(不写 alphadxt1_alpha=true 时按 1-bit alpha 写)
void decode_color_block(const uint8_t* blk, uint8_t out[16][4], bool dxt1_alpha) {
uint16_t c0 = uint16_t(blk[0] | (blk[1] << 8));
uint16_t c1 = uint16_t(blk[2] | (blk[3] << 8));
int r[4], g[4], b[4], a[4] = {255, 255, 255, 255};
unpack565(c0, r[0], g[0], b[0]);
unpack565(c1, r[1], g[1], b[1]);
if (c0 > c1 || !dxt1_alpha) {
r[2] = (2 * r[0] + r[1]) / 3; g[2] = (2 * g[0] + g[1]) / 3; b[2] = (2 * b[0] + b[1]) / 3;
r[3] = (r[0] + 2 * r[1]) / 3; g[3] = (g[0] + 2 * g[1]) / 3; b[3] = (b[0] + 2 * b[1]) / 3;
} else {
r[2] = (r[0] + r[1]) / 2; g[2] = (g[0] + g[1]) / 2; b[2] = (b[0] + b[1]) / 2;
r[3] = g[3] = b[3] = 0; a[3] = 0; // 透明
}
uint32_t bits = rd_le32(blk + 4);
for (int i = 0; i < 16; ++i) {
int idx = (bits >> (i * 2)) & 3;
out[i][0] = uint8_t(r[idx]); out[i][1] = uint8_t(g[idx]);
out[i][2] = uint8_t(b[idx]); out[i][3] = uint8_t(a[idx]);
}
}
// DXT3alpha 块(8B= 16 个 4-bit alpha,直接展开
void decode_dxt3_alpha(const uint8_t* blk, uint8_t out[16][4]) {
for (int i = 0; i < 8; ++i) {
int a0 = blk[i] & 0x0f, a1 = (blk[i] >> 4) & 0x0f;
out[i * 2 + 0][3] = uint8_t(a0 * 17); // 0..15 → 0..255
out[i * 2 + 1][3] = uint8_t(a1 * 17);
}
}
// DXT5alpha 块(8B= 2 端点 + 16×3-bit 索引
void decode_dxt5_alpha(const uint8_t* blk, uint8_t out[16][4]) {
int a0 = blk[0], a1 = blk[1];
int a[8];
a[0] = a0; a[1] = a1;
if (a0 > a1) {
for (int i = 1; i < 7; ++i) a[i + 1] = ((7 - i) * a0 + i * a1) / 7;
} else {
for (int i = 1; i < 5; ++i) a[i + 1] = ((5 - i) * a0 + i * a1) / 5;
a[6] = 0; a[7] = 255;
}
uint64_t bits = 0;
for (int i = 0; i < 6; ++i) bits |= uint64_t(blk[2 + i]) << (8 * i);
for (int i = 0; i < 16; ++i) {
int idx = int((bits >> (i * 3)) & 7);
out[i][3] = uint8_t(a[idx]);
}
}
enum Fmt { F_NONE, F_DXT1, F_DXT3, F_DXT5, F_RGB };
bool valid_mask(uint32_t mask, uint32_t bits) {
if (!mask || (bits < 32 && (mask >> bits))) return false;
while (!(mask & 1)) mask >>= 1;
return (mask & (mask + 1)) == 0; // contiguous channel bits
}
uint8_t channel(uint32_t pixel, uint32_t mask) {
if (!mask) return 255;
while (!(mask & 1)) { mask >>= 1; pixel >>= 1; }
return uint8_t((uint64_t(pixel & mask) * 255 + mask / 2) / mask);
}
Image decode(const uint8_t* d, size_t len) {
Image img;
if (len < 128 || std::memcmp(d, "DDS ", 4) != 0) return img;
uint32_t hsize = rd_le32(d + 4);
if (hsize != 124) return img;
uint32_t h = rd_le32(d + 12);
uint32_t w = rd_le32(d + 16);
uint32_t pf_flags = rd_le32(d + 80);
const uint8_t* fourcc = d + 84;
uint32_t rgb_bitcount = rd_le32(d + 88);
Fmt fmt = F_NONE;
if (pf_flags & 0x4) { // DDPF_FOURCC
if (!std::memcmp(fourcc, "DXT1", 4)) fmt = F_DXT1;
else if (!std::memcmp(fourcc, "DXT3", 4)) fmt = F_DXT3;
else if (!std::memcmp(fourcc, "DXT5", 4)) fmt = F_DXT5;
} else if ((pf_flags & 0x40) && (rgb_bitcount == 16 || rgb_bitcount == 24 || rgb_bitcount == 32)) {
fmt = F_RGB;
}
if (fmt == F_NONE || w == 0 || h == 0 || w > 8192 || h > 8192) return img;
const uint8_t* src = d + 128;
size_t avail = len - 128;
img.w = uint16_t(w);
img.h = uint16_t(h);
img.rgba.assign(size_t(w) * h * 4, 0);
if (fmt == F_RGB) {
const uint32_t r = rd_le32(d + 92), g = rd_le32(d + 96), b = rd_le32(d + 100);
const uint32_t a = (pf_flags & 1) ? rd_le32(d + 104) : 0;
if (!valid_mask(r, rgb_bitcount) || !valid_mask(g, rgb_bitcount) || !valid_mask(b, rgb_bitcount) ||
(a && !valid_mask(a, rgb_bitcount)) || ((pf_flags & 1) && !a) ||
(r & g) || (r & b) || (g & b) || (a & (r | g | b))) return {};
const size_t bytes = rgb_bitcount / 8, row_bytes = size_t(w) * bytes;
const size_t pitch = (rd_le32(d + 8) & 8) ? rd_le32(d + 20) : row_bytes;
if (pitch < row_bytes || avail < row_bytes || pitch > avail || (h - 1) > (avail - row_bytes) / pitch) return {};
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x) {
uint32_t pixel = 0;
for (size_t k = 0; k < bytes; ++k) pixel |= uint32_t(src[y * pitch + x * bytes + k]) << (8 * k);
auto* out = &img.rgba[(y * w + x) * 4];
out[0] = channel(pixel, r); out[1] = channel(pixel, g);
out[2] = channel(pixel, b); out[3] = channel(pixel, a);
}
}
img.format = rgb_bitcount == 32 && r == 0xff0000 && b == 0xff ? "BGRA8" : "RGB_MASKED";
return img;
}
const int block_bytes = (fmt == F_DXT1) ? 8 : 16;
const size_t bx = (w + 3) / 4, by = (h + 3) / 4;
if (avail < bx * by * block_bytes) { img = Image{}; return img; }
for (size_t byi = 0; byi < by; ++byi) {
for (size_t bxi = 0; bxi < bx; ++bxi) {
const uint8_t* blk = src + (byi * bx + bxi) * block_bytes;
uint8_t px[16][4];
if (fmt == F_DXT1) {
decode_color_block(blk, px, /*dxt1_alpha=*/true);
} else {
decode_color_block(blk + 8, px, /*dxt1_alpha=*/false);
if (fmt == F_DXT3) decode_dxt3_alpha(blk, px);
else decode_dxt5_alpha(blk, px);
}
for (int py = 0; py < 4; ++py) {
size_t y = byi * 4 + py;
if (y >= h) break;
for (int pxx = 0; pxx < 4; ++pxx) {
size_t x = bxi * 4 + pxx;
if (x >= w) break;
uint8_t* o = &img.rgba[(y * w + x) * 4];
const uint8_t* s = px[py * 4 + pxx];
o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; o[3] = s[3];
}
}
}
}
img.format = (fmt == F_DXT1) ? "DXT1" : (fmt == F_DXT3) ? "DXT3" : "DXT5";
return img;
}
} // namespace
Image load_dds(const uint8_t* bytes, size_t len) { return decode(bytes, len); }
Image load_dds_path(const char* path) {
FILE* f = std::fopen(path, "rb");
if (!f) return {};
std::fseek(f, 0, SEEK_END);
long n = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
std::vector<uint8_t> buf(n > 0 ? size_t(n) : 0);
size_t rd = buf.empty() ? 0 : std::fread(buf.data(), 1, buf.size(), f);
std::fclose(f);
if (rd != buf.size()) return {};
return decode(buf.data(), buf.size());
}
} // namespace mtgodot