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.