PageSourceSearch

https://caldariprimeponyclub.com/src/formats/Dds.mjs

js caldariprimeponyclub.com collected 2026-10-07 19:38:26 UTC 9,239 bytes, 370 lines download raw bytes

1const MAGIC = 0x20534444;
2
3const HEADER = Object.freeze({
4    size: 4,
5    flags: 8,
6    height: 12,
7    width: 16,
8    pitch: 20,
9    depth: 24,
10    mips: 28,
11    pixelFormatFlags: 80,
12    fourCC: 84,
13    rgbBitCount: 88,
14    redMask: 92,
15    greenMask: 96,
16    blueMask: 100,
17    alphaMask: 104,
18    caps2: 112
19});
20
21const DATA = 128;
22const DATA_DX10 = 148;
23
24const FOURCC_FLAG = 0x4;
25
26const CUBEMAP = 0x200;
27
28const DXGI = Object.freeze({
29    28: { name: "R8G8B8A8_UNORM", codec: "rgba" },
30    29: { name: "R8G8B8A8_UNORM_SRGB", codec: "rgba" },
31    71: { name: "BC1_UNORM", codec: "bc1" },
32    72: { name: "BC1_UNORM_SRGB", codec: "bc1" },
33    74: { name: "BC2_UNORM", codec: "bc2" },
34    75: { name: "BC2_UNORM_SRGB", codec: "bc2" },
35    77: { name: "BC3_UNORM", codec: "bc3" },
36    78: { name: "BC3_UNORM_SRGB", codec: "bc3" },
37    80: { name: "BC4_UNORM", codec: "bc4" },
38    83: { name: "BC5_UNORM", codec: "bc5" },
39    95: { name: "BC6H_UF16", codec: null },
40    96: { name: "BC6H_SF16", codec: null },
41    98: { name: "BC7_UNORM", codec: "bc7" },
42    99: { name: "BC7_UNORM_SRGB", codec: "bc7" },
43    87: { name: "B8G8R8A8_UNORM", codec: "bgra" }
44});
45
46const FOURCC = Object.freeze({
47    DXT1: { name: "DXT1", codec: "bc1" },
48    DXT2: { name: "DXT2", codec: "bc2" },
49    DXT3: { name: "DXT3", codec: "bc2" },
50    DXT4: { name: "DXT4", codec: "bc3" },
51    DXT5: { name: "DXT5", codec: "bc3" },
52    ATI1: { name: "ATI1 (BC4)", codec: "bc4" },
53    BC4U: { name: "BC4_UNORM", codec: "bc4" },
54    ATI2: { name: "ATI2 (BC5)", codec: "bc5" },
55    BC5U: { name: "BC5_UNORM", codec: "bc5" }
56});
57
58const BLOCK_BYTES = Object.freeze({ bc1: 8, bc4: 8, bc2: 16, bc3: 16, bc5: 16, bc7: 16 });
59
60const DECODES = new Set([ "bc1", "bc2", "bc3", "bc4", "bc5", "rgba", "bgra" ]);
61
62export function Describe(bytes)
63{
64    const view = View(bytes);
65
66    if (!view || view.byteLength < DATA) return null;
67    if (view.getUint32(0, true) !== MAGIC) return null;
68
69    const width = view.getUint32(HEADER.width, true);
70    const height = view.getUint32(HEADER.height, true);
71
72    const mips = Math.max(1, view.getUint32(HEADER.mips, true));
73    const cube = Boolean(view.getUint32(HEADER.caps2, true) & CUBEMAP);
74    const fourCC = FourCC(view);
75
76    let format = null;
77    let codec = null;
78    let dataOffset = DATA;
79
80    if (fourCC === "DX10")
81    {
82
83        if (view.byteLength < DATA_DX10) return null;
84
85        const dxgi = view.getUint32(DATA, true);
86        const known = DXGI[dxgi];
87
88        format = known?.name ?? `DXGI ${dxgi}`;
89        codec = known?.codec ?? null;
90        dataOffset = DATA_DX10;
91    }
92    else if (fourCC && (view.getUint32(HEADER.pixelFormatFlags, true) & FOURCC_FLAG))
93    {
94        const known = FOURCC[fourCC];
95
96        format = known?.name ?? fourCC;
97        codec = known?.codec ?? null;
98    }
99    else
100    {
101
102        const bits = view.getUint32(HEADER.rgbBitCount, true);
103        const red = view.getUint32(HEADER.redMask, true);
104
105        if (bits === 32 && red === 0x00ff0000) { format = "B8G8R8A8"; codec = "bgra"; }
106        else if (bits === 32 && red === 0x000000ff) { format = "R8G8B8A8"; codec = "rgba"; }
107        else format = `${bits}-bit uncompressed`;
108    }
109
110    return {
111        width,
112        height,
113        mips,
114        cube,
115        format,
116        codec,
117        blockSize: BLOCK_BYTES[codec] ?? 0,
118        dataOffset,
119        canDecode: Boolean(codec) && DECODES.has(codec),
120        bytes: view.byteLength
121    };
122}
123
124export function Decode(bytes)
125{
126    const info = Describe(bytes);
127
128    if (!info?.canDecode) return null;
129
130    const data = Payload(bytes, info.dataOffset);
131    const { width, height } = info;
132    const pixels = new Uint8ClampedArray(width * height * 4);
133
134    if (info.codec === "rgba" || info.codec === "bgra")
135    {
136        const swap = info.codec === "bgra";
137
138        for (let at = 0, out = 0; out < pixels.length; at += 4, out += 4)
139        {
140            if (at + 3 >= data.length) break;
141
142            pixels[out] = data[at + (swap ? 2 : 0)];
143            pixels[out + 1] = data[at + 1];
144            pixels[out + 2] = data[at + (swap ? 0 : 2)];
145            pixels[out + 3] = data[at + 3];
146        }
147
148        return { width, height, pixels };
149    }
150
151    Blocks(data, info, pixels);
152
153    return { width, height, pixels };
154}
155
156function Blocks(data, info, pixels)
157{
158    const { width, height, codec, blockSize } = info;
159    const across = Math.max(1, Math.ceil(width / 4));
160    const down = Math.max(1, Math.ceil(height / 4));
161    const colour = new Uint8Array(64);
162
163    for (let row = 0; row < down; row++)
164    {
165        for (let column = 0; column < across; column++)
166        {
167            const at = (row * across + column) * blockSize;
168
169            if (at + blockSize > data.length) return pixels;
170
171            Block(data, at, codec, colour);
172
173            for (let y = 0; y < 4; y++)
174            {
175                const py = row * 4 + y;
176
177                if (py >= height) break;
178
179                for (let x = 0; x < 4; x++)
180                {
181                    const px = column * 4 + x;
182
183                    if (px >= width) break;
184
185                    const from = (y * 4 + x) * 4;
186                    const to = (py * width + px) * 4;
187
188                    pixels[to] = colour[from];
189                    pixels[to + 1] = colour[from + 1];
190                    pixels[to + 2] = colour[from + 2];
191                    pixels[to + 3] = colour[from + 3];
192                }
193            }
194        }
195    }
196
197    return pixels;
198}
199
200function Block(data, at, codec, out)
201{
202    if (codec === "bc4")
203    {
204        Alpha(data, at, out, 0);
205
206        for (let i = 0; i < 16; i++)
207        {
208            out[i * 4 + 1] = out[i * 4];
209            out[i * 4 + 2] = out[i * 4];
210            out[i * 4 + 3] = 255;
211        }
212
213        return out;
214    }
215
216    if (codec === "bc5")
217    {
218
219        Alpha(data, at, out, 0);
220        Alpha(data, at + 8, out, 1);
221
222        for (let i = 0; i < 16; i++)
223        {
224            out[i * 4 + 2] = 0;
225            out[i * 4 + 3] = 255;
226        }
227
228        return out;
229    }
230
231    const colourAt = codec === "bc1" ? at : at + 8;
232
233    Colour(data, colourAt, codec === "bc1", out);
234
235    if (codec === "bc2") Bc2Alpha(data, at, out);
236    else if (codec === "bc3") Alpha(data, at, out, 3);
237
238    return out;
239}
240
241function Colour(data, at, punchThrough, out)
242{
243    const c0 = data[at] | (data[at + 1] << 8);
244    const c1 = data[at + 2] | (data[at + 3] << 8);
245    const palette = new Uint8Array(16);
246
247    Unpack565(c0, palette, 0);
248    Unpack565(c1, palette, 4);
249
250    const third = !punchThrough || c0 > c1;
251
252    for (let channel = 0; channel < 3; channel++)
253    {
254        const a = palette[channel];
255        const b = palette[4 + channel];
256
257        palette[8 + channel] = third ? (2 * a + b) / 3 : (a + b) / 2;
258        palette[12 + channel] = third ? (a + 2 * b) / 3 : 0;
259    }
260
261    palette[3] = 255;
262    palette[7] = 255;
263    palette[11] = 255;
264    palette[15] = third ? 255 : 0;
265
266    for (let i = 0; i < 16; i++)
267    {
268        const index = (data[at + 4 + (i >> 2)] >> ((i & 3) * 2)) & 3;
269
270        out[i * 4] = palette[index * 4];
271        out[i * 4 + 1] = palette[index * 4 + 1];
272        out[i * 4 + 2] = palette[index * 4 + 2];
273        out[i * 4 + 3] = palette[index * 4 + 3];
274    }
275
276    return out;
277}
278
279function Unpack565(value, into, at)
280{
281    const r = (value >> 11) & 31;
282    const g = (value >> 5) & 63;
283    const b = value & 31;
284
285    into[at] = (r << 3) | (r >> 2);
286    into[at + 1] = (g << 2) | (g >> 4);
287    into[at + 2] = (b << 3) | (b >> 2);
288
289    return into;
290}
291
292function Alpha(data, at, out, channel)
293{
294    const a0 = data[at];
295    const a1 = data[at + 1];
296    const ramp = new Uint8Array(8);
297
298    ramp[0] = a0;
299    ramp[1] = a1;
300
301    if (a0 > a1)
302    {
303        for (let i = 2; i < 8; i++) ramp[i] = ((8 - i) * a0 + (i - 1) * a1) / 7;
304    }
305    else
306    {
307        for (let i = 2; i < 6; i++) ramp[i] = ((6 - i) * a0 + (i - 1) * a1) / 5;
308        ramp[6] = 0;
309        ramp[7] = 255;
310    }
311
312    for (let half = 0; half < 2; half++)
313    {
314        const base = at + 2 + half * 3;
315        const bits = data[base] | (data[base + 1] << 8) | (data[base + 2] << 16);
316
317        for (let i = 0; i < 8; i++)
318        {
319            out[(half * 8 + i) * 4 + channel] = ramp[(bits >> (i * 3)) & 7];
320        }
321    }
322
323    return out;
324}
325
326function Bc2Alpha(data, at, out)
327{
328    for (let i = 0; i < 16; i++)
329    {
330        const nibble = (data[at + (i >> 1)] >> ((i & 1) * 4)) & 15;
331
332        out[i * 4 + 3] = (nibble << 4) | nibble;
333    }
334
335    return out;
336}
337
338function View(bytes)
339{
340    if (bytes instanceof DataView) return bytes;
341    if (bytes instanceof ArrayBuffer) return new DataView(bytes);
342    if (ArrayBuffer.isView(bytes)) return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
343
344    return null;
345}
346
347function Payload(bytes, offset)
348{
349    if (bytes instanceof ArrayBuffer) return new Uint8Array(bytes, offset);
350
351    return new Uint8Array(bytes.buffer, bytes.byteOffset + offset, bytes.byteLength - offset);
352}
353
354function FourCC(view)
355{
356    let text = "";
357
358    for (let i = 0; i < 4; i++)
359    {
360        const code = view.getUint8(HEADER.fourCC + i);
361
362        if (!code) return null;
363
364        text += String.fromCharCode(code);
365    }
366
367    return text;
368}
369
370export default Describe;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.