/src/serenity/Userland/Libraries/LibGfx/ImageFormats/DDSLoader.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, the SerenityOS developers. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Debug.h> |
8 | | #include <AK/Endian.h> |
9 | | #include <AK/Error.h> |
10 | | #include <AK/MemoryStream.h> |
11 | | #include <AK/StringBuilder.h> |
12 | | #include <AK/Try.h> |
13 | | #include <AK/Vector.h> |
14 | | #include <LibGfx/ImageFormats/DDSLoader.h> |
15 | | #include <fcntl.h> |
16 | | #include <math.h> |
17 | | #include <stdio.h> |
18 | | #include <string.h> |
19 | | #include <sys/mman.h> |
20 | | #include <sys/stat.h> |
21 | | #include <unistd.h> |
22 | | |
23 | | namespace Gfx { |
24 | | |
25 | | struct DDSLoadingContext { |
26 | | DDSLoadingContext(FixedMemoryStream stream) |
27 | 3.53k | : stream(move(stream)) |
28 | 3.53k | { |
29 | 3.53k | } |
30 | | |
31 | | enum State { |
32 | | NotDecoded = 0, |
33 | | Error, |
34 | | HeaderDecoded, |
35 | | BitmapDecoded, |
36 | | }; |
37 | | |
38 | | State state { State::NotDecoded }; |
39 | | |
40 | | FixedMemoryStream stream; |
41 | | |
42 | | DDSHeader header; |
43 | | DDSHeaderDXT10 header10; |
44 | | DXGIFormat format; |
45 | | RefPtr<Gfx::Bitmap> bitmap; |
46 | | |
47 | | void dump_debug(); |
48 | | }; |
49 | | |
50 | | static constexpr u32 create_four_cc(char c0, char c1, char c2, char c3) |
51 | 14.9k | { |
52 | 14.9k | return c0 | c1 << 8 | c2 << 16 | c3 << 24; |
53 | 14.9k | } |
54 | | |
55 | | static u64 get_width(DDSHeader header, size_t mipmap_level) |
56 | 441 | { |
57 | 441 | if (mipmap_level >= header.mip_map_count) { |
58 | 19 | return header.width; |
59 | 19 | } |
60 | | |
61 | 422 | return header.width >> mipmap_level; |
62 | 441 | } |
63 | | |
64 | | static u64 get_height(DDSHeader header, size_t mipmap_level) |
65 | 441 | { |
66 | 441 | if (mipmap_level >= header.mip_map_count) { |
67 | 19 | return header.height; |
68 | 19 | } |
69 | | |
70 | 422 | return header.height >> mipmap_level; |
71 | 441 | } |
72 | | |
73 | | static constexpr bool has_bitmask(DDSPixelFormat format, u32 r, u32 g, u32 b, u32 a) |
74 | 9.67k | { |
75 | 9.67k | return format.r_bit_mask == r && format.g_bit_mask == g && format.b_bit_mask == b && format.a_bit_mask == a; |
76 | 9.67k | } |
77 | | |
78 | | static DXGIFormat get_format(DDSPixelFormat format) |
79 | 3.41k | { |
80 | 3.41k | if ((format.flags & PixelFormatFlags::DDPF_RGB) == PixelFormatFlags::DDPF_RGB) { |
81 | 1.48k | switch (format.rgb_bit_count) { |
82 | 722 | case 32: { |
83 | 722 | if (has_bitmask(format, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000)) |
84 | 1 | return DXGI_FORMAT_R8G8B8A8_UNORM; |
85 | 721 | if (has_bitmask(format, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)) |
86 | 1 | return DXGI_FORMAT_B8G8R8A8_UNORM; |
87 | 720 | if (has_bitmask(format, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000)) |
88 | 1 | return DXGI_FORMAT_B8G8R8X8_UNORM; |
89 | 719 | if (has_bitmask(format, 0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000)) |
90 | 1 | return DXGI_FORMAT_R10G10B10A2_UNORM; |
91 | 718 | if (has_bitmask(format, 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000)) |
92 | 1 | return DXGI_FORMAT_R16G16_UNORM; |
93 | 717 | if (has_bitmask(format, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000)) |
94 | 1 | return DXGI_FORMAT_R32_FLOAT; |
95 | 716 | break; |
96 | 717 | } |
97 | 716 | case 24: |
98 | 0 | break; |
99 | 601 | case 16: { |
100 | 601 | if (has_bitmask(format, 0x7C00, 0x03E0, 0x001F, 0x8000)) |
101 | 1 | return DXGI_FORMAT_B5G5R5A1_UNORM; |
102 | 600 | if (has_bitmask(format, 0xF800, 0x07E0, 0x001F, 0x0000)) |
103 | 1 | return DXGI_FORMAT_B5G6R5_UNORM; |
104 | 599 | if (has_bitmask(format, 0xF800, 0x07E0, 0x001F, 0x0000)) |
105 | 0 | return DXGI_FORMAT_B5G6R5_UNORM; |
106 | 599 | if (has_bitmask(format, 0x0F00, 0x00F0, 0x000F, 0xF000)) |
107 | 1 | return DXGI_FORMAT_B4G4R4A4_UNORM; |
108 | 598 | if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00)) |
109 | 1 | return DXGI_FORMAT_R8G8_UNORM; |
110 | 597 | if (has_bitmask(format, 0xFFFF, 0x0000, 0x0000, 0x0000)) |
111 | 1 | return DXGI_FORMAT_R16_UNORM; |
112 | 596 | break; |
113 | 597 | } |
114 | 596 | case 8: { |
115 | 156 | if (has_bitmask(format, 0xFF, 0x00, 0x00, 0x00)) |
116 | 1 | return DXGI_FORMAT_R8_UNORM; |
117 | 155 | break; |
118 | 156 | } |
119 | 1.48k | } |
120 | 1.92k | } else if ((format.flags & PixelFormatFlags::DDPF_LUMINANCE) == PixelFormatFlags::DDPF_LUMINANCE) { |
121 | 462 | switch (format.rgb_bit_count) { |
122 | 283 | case 16: { |
123 | 283 | if (has_bitmask(format, 0xFFFF, 0x0000, 0x0000, 0x0000)) |
124 | 1 | return DXGI_FORMAT_R16_UNORM; |
125 | 282 | if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00)) |
126 | 1 | return DXGI_FORMAT_R8G8_UNORM; |
127 | 281 | break; |
128 | 282 | } |
129 | 281 | case 8: { |
130 | 174 | if (has_bitmask(format, 0xFF, 0x00, 0x00, 0x00)) |
131 | 1 | return DXGI_FORMAT_R8_UNORM; |
132 | | |
133 | | // Some writers mistakenly write this as 8 bpp. |
134 | 173 | if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00)) |
135 | 1 | return DXGI_FORMAT_R8G8_UNORM; |
136 | 172 | break; |
137 | 173 | } |
138 | 462 | } |
139 | 1.46k | } else if ((format.flags & PixelFormatFlags::DDPF_ALPHA) == PixelFormatFlags::DDPF_ALPHA) { |
140 | 40 | if (format.rgb_bit_count == 8) |
141 | 1 | return DXGI_FORMAT_A8_UNORM; |
142 | 1.42k | } else if ((format.flags & PixelFormatFlags::DDPF_BUMPDUDV) == PixelFormatFlags::DDPF_BUMPDUDV) { |
143 | 425 | switch (format.rgb_bit_count) { |
144 | 276 | case 32: { |
145 | 276 | if (has_bitmask(format, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000)) |
146 | 1 | return DXGI_FORMAT_R8G8B8A8_SNORM; |
147 | 275 | if (has_bitmask(format, 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000)) |
148 | 1 | return DXGI_FORMAT_R16G16_SNORM; |
149 | 274 | break; |
150 | 275 | } |
151 | 274 | case 16: { |
152 | 147 | if (has_bitmask(format, 0x00FF, 0xFF00, 0x0000, 0x0000)) |
153 | 1 | return DXGI_FORMAT_R8G8_SNORM; |
154 | 146 | break; |
155 | 147 | } |
156 | 425 | } |
157 | 1.00k | } else if ((format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) { |
158 | 1.00k | if (format.four_cc == create_four_cc('D', 'X', 'T', '1')) |
159 | 113 | return DXGI_FORMAT_BC1_UNORM; |
160 | 888 | if (format.four_cc == create_four_cc('D', 'X', 'T', '2')) |
161 | 97 | return DXGI_FORMAT_BC2_UNORM; |
162 | 791 | if (format.four_cc == create_four_cc('D', 'X', 'T', '3')) |
163 | 71 | return DXGI_FORMAT_BC2_UNORM; |
164 | 720 | if (format.four_cc == create_four_cc('D', 'X', 'T', '4')) |
165 | 113 | return DXGI_FORMAT_BC3_UNORM; |
166 | 607 | if (format.four_cc == create_four_cc('D', 'X', 'T', '5')) |
167 | 47 | return DXGI_FORMAT_BC3_UNORM; |
168 | 560 | if (format.four_cc == create_four_cc('A', 'T', 'I', '1')) |
169 | 1 | return DXGI_FORMAT_BC4_UNORM; |
170 | 559 | if (format.four_cc == create_four_cc('B', 'C', '4', 'U')) |
171 | 1 | return DXGI_FORMAT_BC4_UNORM; |
172 | 558 | if (format.four_cc == create_four_cc('B', 'C', '4', 'S')) |
173 | 1 | return DXGI_FORMAT_BC4_SNORM; |
174 | 557 | if (format.four_cc == create_four_cc('A', 'T', 'I', '2')) |
175 | 1 | return DXGI_FORMAT_BC5_UNORM; |
176 | 556 | if (format.four_cc == create_four_cc('B', 'C', '5', 'U')) |
177 | 1 | return DXGI_FORMAT_BC5_UNORM; |
178 | 555 | if (format.four_cc == create_four_cc('B', 'C', '5', 'S')) |
179 | 1 | return DXGI_FORMAT_BC5_SNORM; |
180 | 554 | if (format.four_cc == create_four_cc('R', 'G', 'B', 'G')) |
181 | 1 | return DXGI_FORMAT_R8G8_B8G8_UNORM; |
182 | 553 | if (format.four_cc == create_four_cc('G', 'R', 'G', 'B')) |
183 | 1 | return DXGI_FORMAT_G8R8_G8B8_UNORM; |
184 | 552 | if (format.four_cc == create_four_cc('Y', 'U', 'Y', '2')) |
185 | 1 | return DXGI_FORMAT_YUY2; |
186 | | |
187 | 551 | switch (format.four_cc) { |
188 | 1 | case 36: |
189 | 1 | return DXGI_FORMAT_R16G16B16A16_UNORM; |
190 | 1 | case 110: |
191 | 1 | return DXGI_FORMAT_R16G16B16A16_SNORM; |
192 | 1 | case 111: |
193 | 1 | return DXGI_FORMAT_R16_FLOAT; |
194 | 1 | case 112: |
195 | 1 | return DXGI_FORMAT_R16G16_FLOAT; |
196 | 1 | case 113: |
197 | 1 | return DXGI_FORMAT_R16G16B16A16_FLOAT; |
198 | 1 | case 114: |
199 | 1 | return DXGI_FORMAT_R32_FLOAT; |
200 | 1 | case 115: |
201 | 1 | return DXGI_FORMAT_R32G32_FLOAT; |
202 | 1 | case 116: |
203 | 1 | return DXGI_FORMAT_R32G32B32A32_FLOAT; |
204 | 551 | } |
205 | 551 | } |
206 | | |
207 | 2.93k | return DXGI_FORMAT_UNKNOWN; |
208 | 3.41k | } |
209 | | |
210 | | static ErrorOr<void> decode_dx5_alpha_block(Stream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y) |
211 | 80.6k | { |
212 | 80.6k | auto color0 = TRY(stream.read_value<LittleEndian<u8>>()); |
213 | 80.6k | auto color1 = TRY(stream.read_value<LittleEndian<u8>>()); |
214 | | |
215 | 80.6k | auto code0 = TRY(stream.read_value<LittleEndian<u8>>()); |
216 | 80.6k | auto code1 = TRY(stream.read_value<LittleEndian<u8>>()); |
217 | 80.6k | auto code2 = TRY(stream.read_value<LittleEndian<u8>>()); |
218 | 80.6k | auto code3 = TRY(stream.read_value<LittleEndian<u8>>()); |
219 | 80.6k | auto code4 = TRY(stream.read_value<LittleEndian<u8>>()); |
220 | 80.6k | auto code5 = TRY(stream.read_value<LittleEndian<u8>>()); |
221 | | |
222 | 80.6k | u32 codes[6] = { 0 }; |
223 | 80.6k | codes[0] = code0 + 256 * (code1 + 256); |
224 | 80.6k | codes[1] = code1 + 256 * (code2 + 256); |
225 | 80.6k | codes[2] = code2 + 256 * (code3 + 256); |
226 | 80.6k | codes[3] = code3 + 256 * (code4 + 256); |
227 | 80.6k | codes[4] = code4 + 256 * code5; |
228 | 80.6k | codes[5] = code5; |
229 | | |
230 | 80.6k | u32 color[8] = { 0 }; |
231 | | |
232 | 80.6k | if (color0 > 128) { |
233 | 52.3k | color[0] = color0; |
234 | 52.3k | } |
235 | | |
236 | 80.6k | if (color1 > 128) { |
237 | 52.2k | color[1] = color1; |
238 | 52.2k | } |
239 | | |
240 | 80.6k | if (color0 > color1) { |
241 | 1.92k | color[2] = (6 * color[0] + 1 * color[1]) / 7; |
242 | 1.92k | color[3] = (5 * color[0] + 2 * color[1]) / 7; |
243 | 1.92k | color[4] = (4 * color[0] + 3 * color[1]) / 7; |
244 | 1.92k | color[5] = (3 * color[0] + 4 * color[1]) / 7; |
245 | 1.92k | color[6] = (2 * color[0] + 5 * color[1]) / 7; |
246 | 1.92k | color[7] = (1 * color[0] + 6 * color[1]) / 7; |
247 | 78.7k | } else { |
248 | 78.7k | color[2] = (4 * color[0] + 1 * color[1]) / 5; |
249 | 78.7k | color[3] = (3 * color[0] + 2 * color[1]) / 5; |
250 | 78.7k | color[4] = (2 * color[0] + 3 * color[1]) / 5; |
251 | 78.7k | color[5] = (1 * color[0] + 4 * color[1]) / 5; |
252 | 78.7k | color[6] = 0; |
253 | 78.7k | color[7] = 255; |
254 | 78.7k | } |
255 | | |
256 | 388k | for (size_t y = 0; y < 4 && bitmap_y + y < static_cast<u64>(context.bitmap->height()); y++) { |
257 | 1.49M | for (size_t x = 0; x < 4 && bitmap_x + x < static_cast<u64>(context.bitmap->width()); x++) { |
258 | 1.19M | u8 index = 3 * (4 * y + x); |
259 | 1.19M | u8 bit_location = floor(index / 8.0); |
260 | 1.19M | u8 adjusted_index = index - (bit_location * 8); |
261 | | |
262 | 1.19M | u8 code = (codes[bit_location] >> adjusted_index) & 7; |
263 | 1.19M | u8 alpha = color[code]; |
264 | | |
265 | 1.19M | Color color = Color(0, 0, 0, alpha); |
266 | 1.19M | context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color); |
267 | 1.19M | } |
268 | 308k | } |
269 | | |
270 | 80.6k | return {}; |
271 | 80.6k | } |
272 | | |
273 | | static ErrorOr<void> decode_dx3_alpha_block(Stream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y) |
274 | 129k | { |
275 | 129k | auto a0 = TRY(stream.read_value<LittleEndian<u8>>()); |
276 | 129k | auto a1 = TRY(stream.read_value<LittleEndian<u8>>()); |
277 | 129k | auto a2 = TRY(stream.read_value<LittleEndian<u8>>()); |
278 | 129k | auto a3 = TRY(stream.read_value<LittleEndian<u8>>()); |
279 | 129k | auto a4 = TRY(stream.read_value<LittleEndian<u8>>()); |
280 | 129k | auto a5 = TRY(stream.read_value<LittleEndian<u8>>()); |
281 | 129k | auto a6 = TRY(stream.read_value<LittleEndian<u8>>()); |
282 | 129k | auto a7 = TRY(stream.read_value<LittleEndian<u8>>()); |
283 | | |
284 | 129k | u64 alpha_0 = a0 + 256u * (a1 + 256u * (a2 + 256u * (a3 + 256u))); |
285 | 129k | u64 alpha_1 = a4 + 256u * (a5 + 256u * (a6 + 256u * a7)); |
286 | | |
287 | 648k | for (size_t y = 0; y < 4 && bitmap_y + y < static_cast<u64>(context.bitmap->height()); y++) { |
288 | 2.45M | for (size_t x = 0; x < 4 && bitmap_x + x < static_cast<u64>(context.bitmap->width()); x++) { |
289 | 1.93M | u8 code = 4 * (4 * y + x); |
290 | | |
291 | 1.93M | if (code >= 32) { |
292 | 966k | code = code - 32; |
293 | 966k | u8 alpha = ((alpha_1 >> code) & 0x0F) * 17; |
294 | | |
295 | 966k | Color color = Color(0, 0, 0, alpha); |
296 | 966k | context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color); |
297 | 969k | } else { |
298 | 969k | u8 alpha = ((alpha_0 >> code) & 0x0F) * 17; |
299 | | |
300 | 969k | Color color = Color(0, 0, 0, alpha); |
301 | 969k | context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color); |
302 | 969k | } |
303 | 1.93M | } |
304 | 518k | } |
305 | | |
306 | 129k | return {}; |
307 | 129k | } |
308 | | |
309 | | static void unpack_rbg_565(u32 rgb, u8* output) |
310 | 439k | { |
311 | 439k | u8 r = (rgb >> 11) & 0x1F; |
312 | 439k | u8 g = (rgb >> 5) & 0x3F; |
313 | 439k | u8 b = rgb & 0x1F; |
314 | | |
315 | 439k | output[0] = (r << 3) | (r >> 2); |
316 | 439k | output[1] = (g << 2) | (g >> 4); |
317 | 439k | output[2] = (b << 3) | (b >> 2); |
318 | 439k | output[3] = 255; |
319 | 439k | } |
320 | | |
321 | | static ErrorOr<void> decode_color_block(Stream& stream, DDSLoadingContext& context, bool dxt1, u64 bitmap_x, u64 bitmap_y) |
322 | 219k | { |
323 | 219k | auto c0_low = TRY(stream.read_value<LittleEndian<u8>>()); |
324 | 219k | auto c0_high = TRY(stream.read_value<LittleEndian<u8>>()); |
325 | 219k | auto c1_low = TRY(stream.read_value<LittleEndian<u8>>()); |
326 | 219k | auto c1_high = TRY(stream.read_value<LittleEndian<u8>>()); |
327 | | |
328 | 219k | auto codes_0 = TRY(stream.read_value<LittleEndian<u8>>()); |
329 | 219k | auto codes_1 = TRY(stream.read_value<LittleEndian<u8>>()); |
330 | 219k | auto codes_2 = TRY(stream.read_value<LittleEndian<u8>>()); |
331 | 219k | auto codes_3 = TRY(stream.read_value<LittleEndian<u8>>()); |
332 | | |
333 | 219k | u64 code = codes_0 + 256ul * (codes_1 + 256ul * (codes_2 + 256ul * codes_3)); |
334 | 219k | u32 color_0 = c0_low + (c0_high * 256); |
335 | 219k | u32 color_1 = c1_low + (c1_high * 256); |
336 | | |
337 | 219k | u8 rgba[4][4]; |
338 | 219k | unpack_rbg_565(color_0, rgba[0]); |
339 | 219k | unpack_rbg_565(color_1, rgba[1]); |
340 | | |
341 | 219k | if (color_0 > color_1) { |
342 | 34.5k | for (size_t i = 0; i < 3; i++) { |
343 | 25.9k | rgba[2][i] = (2 * rgba[0][i] + rgba[1][i]) / 3; |
344 | 25.9k | rgba[3][i] = (rgba[0][i] + 2 * rgba[1][i]) / 3; |
345 | 25.9k | } |
346 | | |
347 | 8.64k | rgba[2][3] = 255; |
348 | 8.64k | rgba[3][3] = 255; |
349 | 211k | } else { |
350 | 844k | for (size_t i = 0; i < 3; i++) { |
351 | 633k | rgba[2][i] = (rgba[0][i] + rgba[1][i]) / 2; |
352 | 633k | rgba[3][i] = 0; |
353 | 633k | } |
354 | | |
355 | 211k | rgba[2][3] = 255; |
356 | 211k | rgba[3][3] = dxt1 ? 0 : 255; |
357 | 211k | } |
358 | | |
359 | 219k | size_t i = 0; |
360 | 1.08M | for (size_t y = 0; y < 4 && bitmap_y + y < static_cast<u64>(context.bitmap->height()); y++) { |
361 | 4.12M | for (size_t x = 0; x < 4 && bitmap_x + x < static_cast<u64>(context.bitmap->width()); x++) { |
362 | 3.26M | u8 code_byte = (code >> (i * 2)) & 3; |
363 | 3.26M | u8 r = rgba[code_byte][0]; |
364 | 3.26M | u8 g = rgba[code_byte][1]; |
365 | 3.26M | u8 b = rgba[code_byte][2]; |
366 | 3.26M | u8 a = dxt1 ? rgba[code_byte][3] : context.bitmap->get_pixel(bitmap_x + x, bitmap_y + y).alpha(); |
367 | | |
368 | 3.26M | Color color = Color(r, g, b, a); |
369 | 3.26M | context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color); |
370 | 3.26M | i++; |
371 | 3.26M | } |
372 | 861k | } |
373 | | |
374 | 219k | return {}; |
375 | 219k | } |
376 | | |
377 | | static ErrorOr<void> decode_dxt(Stream& stream, DDSLoadingContext& context, u64 width, u64 y) |
378 | 24.7k | { |
379 | 24.7k | if (context.format == DXGI_FORMAT_BC1_UNORM) { |
380 | 9.75k | for (size_t x = 0; x < width; x += 4) { |
381 | 9.48k | TRY(decode_color_block(stream, context, true, x, y)); |
382 | 9.40k | } |
383 | 352 | } |
384 | | |
385 | 24.7k | if (context.format == DXGI_FORMAT_BC2_UNORM) { |
386 | 144k | for (size_t x = 0; x < width; x += 4) { |
387 | 129k | TRY(decode_dx3_alpha_block(stream, context, x, y)); |
388 | 129k | TRY(decode_color_block(stream, context, false, x, y)); |
389 | 129k | } |
390 | 14.8k | } |
391 | | |
392 | 24.6k | if (context.format == DXGI_FORMAT_BC3_UNORM) { |
393 | 90.1k | for (size_t x = 0; x < width; x += 4) { |
394 | 80.6k | TRY(decode_dx5_alpha_block(stream, context, x, y)); |
395 | 80.6k | TRY(decode_color_block(stream, context, false, x, y)); |
396 | 80.5k | } |
397 | 9.55k | } |
398 | | |
399 | 24.5k | return {}; |
400 | 24.5k | } |
401 | | static ErrorOr<void> decode_bitmap(Stream& stream, DDSLoadingContext& context, u64 width, u64 height) |
402 | 339 | { |
403 | 339 | static constexpr Array dxt_formats = { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC3_UNORM }; |
404 | 339 | if (dxt_formats.contains_slow(context.format)) { |
405 | 24.7k | for (u64 y = 0; y < height; y += 4) { |
406 | 24.7k | TRY(decode_dxt(stream, context, width, y)); |
407 | 24.4k | } |
408 | 339 | } |
409 | | |
410 | | // FIXME: Support more encodings (ATI, YUV, RAW, etc...). |
411 | 339 | return {}; |
412 | 339 | } |
413 | | |
414 | | static ErrorOr<void> decode_header(DDSLoadingContext& context) |
415 | 3.53k | { |
416 | | // All valid DDS files are at least 128 bytes long. |
417 | 7.06k | if (TRY(context.stream.size()) < 128) { |
418 | 18 | dbgln_if(DDS_DEBUG, "File is too short for DDS"); |
419 | 18 | context.state = DDSLoadingContext::State::Error; |
420 | 18 | return Error::from_string_literal("File is too short for DDS"); |
421 | 18 | } |
422 | | |
423 | 3.51k | auto magic = TRY(context.stream.read_value<u32>()); |
424 | | |
425 | 3.51k | if (magic != create_four_cc('D', 'D', 'S', ' ')) { |
426 | 52 | dbgln_if(DDS_DEBUG, "Missing magic number"); |
427 | 52 | context.state = DDSLoadingContext::State::Error; |
428 | 52 | return Error::from_string_literal("Missing magic number"); |
429 | 52 | } |
430 | | |
431 | 3.46k | context.header = TRY(context.stream.read_value<DDSHeader>()); |
432 | | |
433 | 3.46k | if (context.header.size != 124) { |
434 | 14 | dbgln_if(DDS_DEBUG, "Header size is malformed"); |
435 | 14 | context.state = DDSLoadingContext::State::Error; |
436 | 14 | return Error::from_string_literal("Header size is malformed"); |
437 | 14 | } |
438 | 3.45k | if (context.header.pixel_format.size != 32) { |
439 | 31 | dbgln_if(DDS_DEBUG, "Pixel format size is malformed"); |
440 | 31 | context.state = DDSLoadingContext::State::Error; |
441 | 31 | return Error::from_string_literal("Pixel format size is malformed"); |
442 | 31 | } |
443 | | |
444 | 3.41k | if ((context.header.pixel_format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) { |
445 | 2.39k | if (context.header.pixel_format.four_cc == create_four_cc('D', 'X', '1', '0')) { |
446 | 88 | if (TRY(context.stream.size()) < 148) { |
447 | 9 | dbgln_if(DDS_DEBUG, "DX10 header is too short"); |
448 | 9 | context.state = DDSLoadingContext::State::Error; |
449 | 9 | return Error::from_string_literal("DX10 header is too short"); |
450 | 9 | } |
451 | | |
452 | 35 | context.header10 = TRY(context.stream.read_value<DDSHeaderDXT10>()); |
453 | 35 | } |
454 | 2.39k | } |
455 | | |
456 | | if constexpr (DDS_DEBUG) { |
457 | | context.dump_debug(); |
458 | | } |
459 | | |
460 | 3.41k | context.format = get_format(context.header.pixel_format); |
461 | | |
462 | 3.41k | static constexpr Array supported_formats = { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC3_UNORM }; |
463 | 3.41k | if (!supported_formats.contains_slow(context.format)) { |
464 | 2.96k | dbgln_if(DDS_DEBUG, "Format of type {} is not supported at the moment", to_underlying(context.format)); |
465 | 2.96k | context.state = DDSLoadingContext::State::Error; |
466 | 2.96k | return Error::from_string_literal("Format type is not supported at the moment"); |
467 | 2.96k | } |
468 | | |
469 | 441 | context.state = DDSLoadingContext::HeaderDecoded; |
470 | | |
471 | 441 | return {}; |
472 | 3.41k | } |
473 | | |
474 | | static ErrorOr<void> decode_dds(DDSLoadingContext& context) |
475 | 441 | { |
476 | 441 | VERIFY(context.state == DDSLoadingContext::HeaderDecoded); |
477 | | |
478 | | // We support parsing mipmaps, but we only care about the largest one :^) (At least for now) |
479 | 441 | if (size_t mipmap_level = 0; mipmap_level < max(context.header.mip_map_count, 1u)) { |
480 | 441 | u64 width = get_width(context.header, mipmap_level); |
481 | 441 | u64 height = get_height(context.header, mipmap_level); |
482 | | |
483 | 441 | context.bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, { width, height })); |
484 | | |
485 | 339 | TRY(decode_bitmap(context.stream, context, width, height)); |
486 | 11 | } |
487 | | |
488 | 441 | context.state = DDSLoadingContext::State::BitmapDecoded; |
489 | | |
490 | 11 | return {}; |
491 | 441 | } |
492 | | |
493 | | void DDSLoadingContext::dump_debug() |
494 | 0 | { |
495 | 0 | StringBuilder builder; |
496 | |
|
497 | 0 | builder.append("\nDDS:\n"sv); |
498 | 0 | builder.appendff("\tHeader Size: {}\n", header.size); |
499 | |
|
500 | 0 | builder.append("\tFlags:"sv); |
501 | 0 | if ((header.flags & DDSFlags::DDSD_CAPS) == DDSFlags::DDSD_CAPS) |
502 | 0 | builder.append(" DDSD_CAPS"sv); |
503 | 0 | if ((header.flags & DDSFlags::DDSD_HEIGHT) == DDSFlags::DDSD_HEIGHT) |
504 | 0 | builder.append(" DDSD_HEIGHT"sv); |
505 | 0 | if ((header.flags & DDSFlags::DDSD_WIDTH) == DDSFlags::DDSD_WIDTH) |
506 | 0 | builder.append(" DDSD_WIDTH"sv); |
507 | 0 | if ((header.flags & DDSFlags::DDSD_PITCH) == DDSFlags::DDSD_PITCH) |
508 | 0 | builder.append(" DDSD_PITCH"sv); |
509 | 0 | if ((header.flags & DDSFlags::DDSD_PIXELFORMAT) == DDSFlags::DDSD_PIXELFORMAT) |
510 | 0 | builder.append(" DDSD_PIXELFORMAT"sv); |
511 | 0 | if ((header.flags & DDSFlags::DDSD_MIPMAPCOUNT) == DDSFlags::DDSD_MIPMAPCOUNT) |
512 | 0 | builder.append(" DDSD_MIPMAPCOUNT"sv); |
513 | 0 | if ((header.flags & DDSFlags::DDSD_LINEARSIZE) == DDSFlags::DDSD_LINEARSIZE) |
514 | 0 | builder.append(" DDSD_LINEARSIZE"sv); |
515 | 0 | if ((header.flags & DDSFlags::DDSD_DEPTH) == DDSFlags::DDSD_DEPTH) |
516 | 0 | builder.append(" DDSD_DEPTH"sv); |
517 | 0 | builder.append("\n"sv); |
518 | |
|
519 | 0 | builder.appendff("\tHeight: {}\n", header.height); |
520 | 0 | builder.appendff("\tWidth: {}\n", header.width); |
521 | 0 | builder.appendff("\tPitch: {}\n", header.pitch); |
522 | 0 | builder.appendff("\tDepth: {}\n", header.depth); |
523 | 0 | builder.appendff("\tMipmap Count: {}\n", header.mip_map_count); |
524 | |
|
525 | 0 | builder.append("\tCaps:"sv); |
526 | 0 | if ((header.caps1 & Caps1Flags::DDSCAPS_COMPLEX) == Caps1Flags::DDSCAPS_COMPLEX) |
527 | 0 | builder.append(" DDSCAPS_COMPLEX"sv); |
528 | 0 | if ((header.caps1 & Caps1Flags::DDSCAPS_MIPMAP) == Caps1Flags::DDSCAPS_MIPMAP) |
529 | 0 | builder.append(" DDSCAPS_MIPMAP"sv); |
530 | 0 | if ((header.caps1 & Caps1Flags::DDSCAPS_TEXTURE) == Caps1Flags::DDSCAPS_TEXTURE) |
531 | 0 | builder.append(" DDSCAPS_TEXTURE"sv); |
532 | 0 | builder.append("\n"sv); |
533 | |
|
534 | 0 | builder.append("\tCaps2:"sv); |
535 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP) == Caps2Flags::DDSCAPS2_CUBEMAP) |
536 | 0 | builder.append(" DDSCAPS2_CUBEMAP"sv); |
537 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX) |
538 | 0 | builder.append(" DDSCAPS2_CUBEMAP_POSITIVEX"sv); |
539 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX) |
540 | 0 | builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEX"sv); |
541 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY) |
542 | 0 | builder.append(" DDSCAPS2_CUBEMAP_POSITIVEY"sv); |
543 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY) |
544 | 0 | builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEY"sv); |
545 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ) |
546 | 0 | builder.append(" DDSCAPS2_CUBEMAP_POSITIVEZ"sv); |
547 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ) |
548 | 0 | builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEZ"sv); |
549 | 0 | if ((header.caps2 & Caps2Flags::DDSCAPS2_VOLUME) == Caps2Flags::DDSCAPS2_VOLUME) |
550 | 0 | builder.append(" DDSCAPS2_VOLUME"sv); |
551 | 0 | builder.append("\n"sv); |
552 | |
|
553 | 0 | builder.append("Pixel Format:\n"sv); |
554 | 0 | builder.appendff("\tStruct Size: {}\n", header.pixel_format.size); |
555 | |
|
556 | 0 | builder.append("\tFlags:"sv); |
557 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHAPIXELS) == PixelFormatFlags::DDPF_ALPHAPIXELS) |
558 | 0 | builder.append(" DDPF_ALPHAPIXELS"sv); |
559 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHA) == PixelFormatFlags::DDPF_ALPHA) |
560 | 0 | builder.append(" DDPF_ALPHA"sv); |
561 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) |
562 | 0 | builder.append(" DDPF_FOURCC"sv); |
563 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_PALETTEINDEXED8) == PixelFormatFlags::DDPF_PALETTEINDEXED8) |
564 | 0 | builder.append(" DDPF_PALETTEINDEXED8"sv); |
565 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_RGB) == PixelFormatFlags::DDPF_RGB) |
566 | 0 | builder.append(" DDPF_RGB"sv); |
567 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_YUV) == PixelFormatFlags::DDPF_YUV) |
568 | 0 | builder.append(" DDPF_YUV"sv); |
569 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_LUMINANCE) == PixelFormatFlags::DDPF_LUMINANCE) |
570 | 0 | builder.append(" DDPF_LUMINANCE"sv); |
571 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_BUMPDUDV) == PixelFormatFlags::DDPF_BUMPDUDV) |
572 | 0 | builder.append(" DDPF_BUMPDUDV"sv); |
573 | 0 | if ((header.pixel_format.flags & PixelFormatFlags::DDPF_NORMAL) == PixelFormatFlags::DDPF_NORMAL) |
574 | 0 | builder.append(" DDPF_NORMAL"sv); |
575 | 0 | builder.append("\n"sv); |
576 | |
|
577 | 0 | builder.append("\tFour CC: "sv); |
578 | 0 | builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 0)) & 0xFF); |
579 | 0 | builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 1)) & 0xFF); |
580 | 0 | builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 2)) & 0xFF); |
581 | 0 | builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 3)) & 0xFF); |
582 | 0 | builder.append("\n"sv); |
583 | 0 | builder.appendff("\tRGB Bit Count: {}\n", header.pixel_format.rgb_bit_count); |
584 | 0 | builder.appendff("\tR Bit Mask: {}\n", header.pixel_format.r_bit_mask); |
585 | 0 | builder.appendff("\tG Bit Mask: {}\n", header.pixel_format.g_bit_mask); |
586 | 0 | builder.appendff("\tB Bit Mask: {}\n", header.pixel_format.b_bit_mask); |
587 | 0 | builder.appendff("\tA Bit Mask: {}\n", header.pixel_format.a_bit_mask); |
588 | |
|
589 | 0 | builder.append("DDS10:\n"sv); |
590 | 0 | builder.appendff("\tFormat: {}\n", static_cast<u32>(header10.format)); |
591 | |
|
592 | 0 | builder.append("\tResource Dimension:"sv); |
593 | 0 | if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_UNKNOWN) == ResourceDimensions::DDS_DIMENSION_UNKNOWN) |
594 | 0 | builder.append(" DDS_DIMENSION_UNKNOWN"sv); |
595 | 0 | if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_BUFFER) == ResourceDimensions::DDS_DIMENSION_BUFFER) |
596 | 0 | builder.append(" DDS_DIMENSION_BUFFER"sv); |
597 | 0 | if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE1D) == ResourceDimensions::DDS_DIMENSION_TEXTURE1D) |
598 | 0 | builder.append(" DDS_DIMENSION_TEXTURE1D"sv); |
599 | 0 | if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE2D) == ResourceDimensions::DDS_DIMENSION_TEXTURE2D) |
600 | 0 | builder.append(" DDS_DIMENSION_TEXTURE2D"sv); |
601 | 0 | if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE3D) == ResourceDimensions::DDS_DIMENSION_TEXTURE3D) |
602 | 0 | builder.append(" DDS_DIMENSION_TEXTURE3D"sv); |
603 | 0 | builder.append("\n"sv); |
604 | |
|
605 | 0 | builder.appendff("\tArray Size: {}\n", header10.array_size); |
606 | |
|
607 | 0 | builder.append("\tMisc Flags:"sv); |
608 | 0 | if ((header10.misc_flag & MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE) == MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE) |
609 | 0 | builder.append(" DDS_RESOURCE_MISC_TEXTURECUBE"sv); |
610 | 0 | builder.append("\n"sv); |
611 | |
|
612 | 0 | builder.append("\tMisc Flags 2:"sv); |
613 | 0 | if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_UNKNOWN) == Misc2Flags::DDS_ALPHA_MODE_UNKNOWN) |
614 | 0 | builder.append(" DDS_ALPHA_MODE_UNKNOWN"sv); |
615 | 0 | if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_STRAIGHT) == Misc2Flags::DDS_ALPHA_MODE_STRAIGHT) |
616 | 0 | builder.append(" DDS_ALPHA_MODE_STRAIGHT"sv); |
617 | 0 | if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED) == Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED) |
618 | 0 | builder.append(" DDS_ALPHA_MODE_PREMULTIPLIED"sv); |
619 | 0 | if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_OPAQUE) == Misc2Flags::DDS_ALPHA_MODE_OPAQUE) |
620 | 0 | builder.append(" DDS_ALPHA_MODE_OPAQUE"sv); |
621 | 0 | if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_CUSTOM) == Misc2Flags::DDS_ALPHA_MODE_CUSTOM) |
622 | 0 | builder.append(" DDS_ALPHA_MODE_CUSTOM"sv); |
623 | 0 | builder.append("\n"sv); |
624 | |
|
625 | 0 | dbgln("{}", builder.to_byte_string()); |
626 | 0 | } |
627 | | |
628 | | DDSImageDecoderPlugin::DDSImageDecoderPlugin(FixedMemoryStream stream) |
629 | 3.53k | { |
630 | 3.53k | m_context = make<DDSLoadingContext>(move(stream)); |
631 | 3.53k | } |
632 | | |
633 | 3.53k | DDSImageDecoderPlugin::~DDSImageDecoderPlugin() = default; |
634 | | |
635 | | IntSize DDSImageDecoderPlugin::size() |
636 | 0 | { |
637 | 0 | return { m_context->header.width, m_context->header.height }; |
638 | 0 | } |
639 | | |
640 | | bool DDSImageDecoderPlugin::sniff(ReadonlyBytes data) |
641 | 0 | { |
642 | | // The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS. |
643 | 0 | return data.size() > 128 |
644 | 0 | && data.data()[0] == 0x44 |
645 | 0 | && data.data()[1] == 0x44 |
646 | 0 | && data.data()[2] == 0x53 |
647 | 0 | && data.data()[3] == 0x20; |
648 | 0 | } |
649 | | |
650 | | ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> DDSImageDecoderPlugin::create(ReadonlyBytes data) |
651 | 3.53k | { |
652 | 3.53k | FixedMemoryStream stream { data }; |
653 | 3.53k | auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DDSImageDecoderPlugin(move(stream)))); |
654 | 3.53k | TRY(decode_header(*plugin->m_context)); |
655 | 441 | return plugin; |
656 | 3.53k | } |
657 | | |
658 | | ErrorOr<ImageFrameDescriptor> DDSImageDecoderPlugin::frame(size_t index, Optional<IntSize>) |
659 | 441 | { |
660 | 441 | if (index > 0) |
661 | 0 | return Error::from_string_literal("DDSImageDecoderPlugin: Invalid frame index"); |
662 | | |
663 | 441 | if (m_context->state == DDSLoadingContext::State::Error) |
664 | 0 | return Error::from_string_literal("DDSImageDecoderPlugin: Decoding failed"); |
665 | | |
666 | 441 | if (m_context->state < DDSLoadingContext::State::BitmapDecoded) { |
667 | 441 | TRY(decode_dds(*m_context)); |
668 | 11 | } |
669 | | |
670 | 441 | VERIFY(m_context->bitmap); |
671 | 11 | return ImageFrameDescriptor { m_context->bitmap, 0 }; |
672 | 11 | } |
673 | | |
674 | | } |