1 | /* This file is part of the KDE project |
2 | Copyright (C) 2003 Ignacio CastaƱo <castano@ludicon.com> |
3 | |
4 | This program is free software; you can redistribute it and/or |
5 | modify it under the terms of the Lesser GNU General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2 of the License, or (at your option) any later version. |
8 | |
9 | Almost all this code is based on nVidia's DDS-loading example |
10 | and the DevIl's source code by Denton Woods. |
11 | */ |
12 | |
13 | /* this code supports: |
14 | * reading: |
15 | * rgb and dxt dds files |
16 | * cubemap dds files |
17 | * volume dds files -- TODO |
18 | * writing: |
19 | * rgb dds files only -- TODO |
20 | */ |
21 | |
22 | #include "dds.h" |
23 | |
24 | #include <QtCore/QStringList> |
25 | #include <QtGui/QImage> |
26 | #include <QtCore/QDataStream> |
27 | |
28 | #include <kdebug.h> |
29 | |
30 | #include <math.h> // sqrtf |
31 | |
32 | #ifndef __USE_ISOC99 |
33 | #define sqrtf(x) ((float)sqrt(x)) |
34 | #endif |
35 | |
36 | typedef quint32 uint; |
37 | typedef quint16 ushort; |
38 | typedef quint8 uchar; |
39 | |
40 | #if !defined(MAKEFOURCC) |
41 | # define MAKEFOURCC(ch0, ch1, ch2, ch3) \ |
42 | (uint(uchar(ch0)) | (uint(uchar(ch1)) << 8) | \ |
43 | (uint(uchar(ch2)) << 16) | (uint(uchar(ch3)) << 24 )) |
44 | #endif |
45 | |
46 | #define HORIZONTAL 1 |
47 | #define VERTICAL 2 |
48 | #define CUBE_LAYOUT HORIZONTAL |
49 | |
50 | struct Color8888 |
51 | { |
52 | uchar r, g, b, a; |
53 | }; |
54 | |
55 | union Color565 |
56 | { |
57 | struct { |
58 | ushort b : 5; |
59 | ushort g : 6; |
60 | ushort r : 5; |
61 | } c; |
62 | ushort u; |
63 | }; |
64 | |
65 | union Color1555 { |
66 | struct { |
67 | ushort b : 5; |
68 | ushort g : 5; |
69 | ushort r : 5; |
70 | ushort a : 1; |
71 | } c; |
72 | ushort u; |
73 | }; |
74 | |
75 | union Color4444 { |
76 | struct { |
77 | ushort b : 4; |
78 | ushort g : 4; |
79 | ushort r : 4; |
80 | ushort a : 4; |
81 | } c; |
82 | ushort u; |
83 | }; |
84 | |
85 | |
86 | static const uint FOURCC_DDS = MAKEFOURCC('D', 'D', 'S', ' '); |
87 | static const uint FOURCC_DXT1 = MAKEFOURCC('D', 'X', 'T', '1'); |
88 | static const uint FOURCC_DXT2 = MAKEFOURCC('D', 'X', 'T', '2'); |
89 | static const uint FOURCC_DXT3 = MAKEFOURCC('D', 'X', 'T', '3'); |
90 | static const uint FOURCC_DXT4 = MAKEFOURCC('D', 'X', 'T', '4'); |
91 | static const uint FOURCC_DXT5 = MAKEFOURCC('D', 'X', 'T', '5'); |
92 | static const uint FOURCC_RXGB = MAKEFOURCC('R', 'X', 'G', 'B'); |
93 | static const uint FOURCC_ATI2 = MAKEFOURCC('A', 'T', 'I', '2'); |
94 | |
95 | static const uint DDSD_CAPS = 0x00000001l; |
96 | static const uint DDSD_PIXELFORMAT = 0x00001000l; |
97 | static const uint DDSD_WIDTH = 0x00000004l; |
98 | static const uint DDSD_HEIGHT = 0x00000002l; |
99 | static const uint DDSD_PITCH = 0x00000008l; |
100 | |
101 | static const uint DDSCAPS_TEXTURE = 0x00001000l; |
102 | static const uint DDSCAPS2_VOLUME = 0x00200000l; |
103 | static const uint DDSCAPS2_CUBEMAP = 0x00000200l; |
104 | |
105 | static const uint DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400l; |
106 | static const uint DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800l; |
107 | static const uint DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000l; |
108 | static const uint DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000l; |
109 | static const uint DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000l; |
110 | static const uint DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000l; |
111 | |
112 | static const uint DDPF_RGB = 0x00000040l; |
113 | static const uint DDPF_FOURCC = 0x00000004l; |
114 | static const uint DDPF_ALPHAPIXELS = 0x00000001l; |
115 | |
116 | enum DDSType { |
117 | DDS_A8R8G8B8 = 0, |
118 | DDS_A1R5G5B5 = 1, |
119 | DDS_A4R4G4B4 = 2, |
120 | DDS_R8G8B8 = 3, |
121 | DDS_R5G6B5 = 4, |
122 | DDS_DXT1 = 5, |
123 | DDS_DXT2 = 6, |
124 | DDS_DXT3 = 7, |
125 | DDS_DXT4 = 8, |
126 | DDS_DXT5 = 9, |
127 | DDS_RXGB = 10, |
128 | DDS_ATI2 = 11, |
129 | DDS_UNKNOWN |
130 | }; |
131 | |
132 | |
133 | struct DDSPixelFormat { |
134 | uint size; |
135 | uint flags; |
136 | uint fourcc; |
137 | uint bitcount; |
138 | uint rmask; |
139 | uint gmask; |
140 | uint bmask; |
141 | uint amask; |
142 | }; |
143 | |
144 | static QDataStream & operator>> ( QDataStream & s, DDSPixelFormat & pf ) |
145 | { |
146 | s >> pf.size; |
147 | s >> pf.flags; |
148 | s >> pf.fourcc; |
149 | s >> pf.bitcount; |
150 | s >> pf.rmask; |
151 | s >> pf.gmask; |
152 | s >> pf.bmask; |
153 | s >> pf.amask; |
154 | return s; |
155 | } |
156 | |
157 | struct DDSCaps { |
158 | uint caps1; |
159 | uint caps2; |
160 | uint caps3; |
161 | uint caps4; |
162 | }; |
163 | |
164 | static QDataStream & operator>> ( QDataStream & s, DDSCaps & caps ) |
165 | { |
166 | s >> caps.caps1; |
167 | s >> caps.caps2; |
168 | s >> caps.caps3; |
169 | s >> caps.caps4; |
170 | return s; |
171 | } |
172 | |
173 | struct { |
174 | uint ; |
175 | uint ; |
176 | uint ; |
177 | uint ; |
178 | uint ; |
179 | uint ; |
180 | uint ; |
181 | uint [11]; |
182 | DDSPixelFormat ; |
183 | DDSCaps ; |
184 | uint ; |
185 | }; |
186 | |
187 | static QDataStream & ( QDataStream & s, DDSHeader & ) |
188 | { |
189 | s >> header.size; |
190 | s >> header.flags; |
191 | s >> header.height; |
192 | s >> header.width; |
193 | s >> header.pitch; |
194 | s >> header.depth; |
195 | s >> header.mipmapcount; |
196 | for( int i = 0; i < 11; i++ ) { |
197 | s >> header.reserved[i]; |
198 | } |
199 | s >> header.pf; |
200 | s >> header.caps; |
201 | s >> header.notused; |
202 | return s; |
203 | } |
204 | |
205 | static bool ( const DDSHeader & ) |
206 | { |
207 | if( header.size != 124 ) { |
208 | return false; |
209 | } |
210 | const uint required = (DDSD_WIDTH|DDSD_HEIGHT|DDSD_PIXELFORMAT); |
211 | if( (header.flags & required) != required ) { |
212 | return false; |
213 | } |
214 | if( header.pf.size != 32 ) { |
215 | return false; |
216 | } |
217 | if( !(header.caps.caps1 & DDSCAPS_TEXTURE) ) { |
218 | return false; |
219 | } |
220 | return true; |
221 | } |
222 | |
223 | |
224 | // Get supported type. We currently support 10 different types. |
225 | static DDSType ( const DDSHeader & ) |
226 | { |
227 | if( header.pf.flags & DDPF_RGB ) { |
228 | if( header.pf.flags & DDPF_ALPHAPIXELS ) { |
229 | switch( header.pf.bitcount ) { |
230 | case 16: |
231 | return (header.pf.amask == 0x8000) ? DDS_A1R5G5B5 : DDS_A4R4G4B4; |
232 | case 32: |
233 | return DDS_A8R8G8B8; |
234 | } |
235 | } |
236 | else { |
237 | switch( header.pf.bitcount ) { |
238 | case 16: |
239 | return DDS_R5G6B5; |
240 | case 24: |
241 | return DDS_R8G8B8; |
242 | } |
243 | } |
244 | } |
245 | else if( header.pf.flags & DDPF_FOURCC ) { |
246 | switch( header.pf.fourcc ) { |
247 | case FOURCC_DXT1: |
248 | return DDS_DXT1; |
249 | case FOURCC_DXT2: |
250 | return DDS_DXT2; |
251 | case FOURCC_DXT3: |
252 | return DDS_DXT3; |
253 | case FOURCC_DXT4: |
254 | return DDS_DXT4; |
255 | case FOURCC_DXT5: |
256 | return DDS_DXT5; |
257 | case FOURCC_RXGB: |
258 | return DDS_RXGB; |
259 | case FOURCC_ATI2: |
260 | return DDS_ATI2; |
261 | } |
262 | } |
263 | return DDS_UNKNOWN; |
264 | } |
265 | |
266 | static bool ( const DDSHeader & ) |
267 | { |
268 | return header.pf.flags & DDPF_ALPHAPIXELS; |
269 | } |
270 | |
271 | static bool ( const DDSHeader & ) |
272 | { |
273 | return header.caps.caps2 & DDSCAPS2_CUBEMAP; |
274 | } |
275 | |
276 | static bool ( const DDSHeader & ) |
277 | { |
278 | if( header.caps.caps2 & DDSCAPS2_VOLUME ) { |
279 | return false; |
280 | } |
281 | if( GetType(header) == DDS_UNKNOWN ) { |
282 | return false; |
283 | } |
284 | return true; |
285 | } |
286 | |
287 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
288 | { |
289 | const uint w = header.width; |
290 | const uint h = header.height; |
291 | |
292 | for( uint y = 0; y < h; y++ ) { |
293 | QRgb * scanline = (QRgb *) img.scanLine( y ); |
294 | for( uint x = 0; x < w; x++ ) { |
295 | uchar r, g, b, a; |
296 | s >> b >> g >> r >> a; |
297 | scanline[x] = qRgba(r, g, b, a); |
298 | } |
299 | } |
300 | |
301 | return true; |
302 | } |
303 | |
304 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
305 | { |
306 | const uint w = header.width; |
307 | const uint h = header.height; |
308 | |
309 | for( uint y = 0; y < h; y++ ) { |
310 | QRgb * scanline = (QRgb *) img.scanLine( y ); |
311 | for( uint x = 0; x < w; x++ ) { |
312 | uchar r, g, b; |
313 | s >> b >> g >> r; |
314 | scanline[x] = qRgb(r, g, b); |
315 | } |
316 | } |
317 | |
318 | return true; |
319 | } |
320 | |
321 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
322 | { |
323 | const uint w = header.width; |
324 | const uint h = header.height; |
325 | |
326 | for( uint y = 0; y < h; y++ ) { |
327 | QRgb * scanline = (QRgb *) img.scanLine( y ); |
328 | for( uint x = 0; x < w; x++ ) { |
329 | Color1555 color; |
330 | s >> color.u; |
331 | uchar a = (color.c.a != 0) ? 0xFF : 0; |
332 | uchar r = (color.c.r << 3) | (color.c.r >> 2); |
333 | uchar g = (color.c.g << 3) | (color.c.g >> 2); |
334 | uchar b = (color.c.b << 3) | (color.c.b >> 2); |
335 | scanline[x] = qRgba(r, g, b, a); |
336 | } |
337 | } |
338 | |
339 | return true; |
340 | } |
341 | |
342 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
343 | { |
344 | const uint w = header.width; |
345 | const uint h = header.height; |
346 | |
347 | for( uint y = 0; y < h; y++ ) { |
348 | QRgb * scanline = (QRgb *) img.scanLine( y ); |
349 | for( uint x = 0; x < w; x++ ) { |
350 | Color4444 color; |
351 | s >> color.u; |
352 | uchar a = (color.c.a << 4) | color.c.a; |
353 | uchar r = (color.c.r << 4) | color.c.r; |
354 | uchar g = (color.c.g << 4) | color.c.g; |
355 | uchar b = (color.c.b << 4) | color.c.b; |
356 | scanline[x] = qRgba(r, g, b, a); |
357 | } |
358 | } |
359 | |
360 | return true; |
361 | } |
362 | |
363 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
364 | { |
365 | const uint w = header.width; |
366 | const uint h = header.height; |
367 | |
368 | for( uint y = 0; y < h; y++ ) { |
369 | QRgb * scanline = (QRgb *) img.scanLine( y ); |
370 | for( uint x = 0; x < w; x++ ) { |
371 | Color565 color; |
372 | s >> color.u; |
373 | uchar r = (color.c.r << 3) | (color.c.r >> 2); |
374 | uchar g = (color.c.g << 2) | (color.c.g >> 4); |
375 | uchar b = (color.c.b << 3) | (color.c.b >> 2); |
376 | scanline[x] = qRgb(r, g, b); |
377 | } |
378 | } |
379 | |
380 | return true; |
381 | } |
382 | |
383 | static QDataStream & operator>> ( QDataStream & s, Color565 & c ) |
384 | { |
385 | return s >> c.u; |
386 | } |
387 | |
388 | |
389 | struct BlockDXT |
390 | { |
391 | Color565 col0; |
392 | Color565 col1; |
393 | uchar row[4]; |
394 | |
395 | void GetColors( Color8888 color_array[4] ) |
396 | { |
397 | color_array[0].r = (col0.c.r << 3) | (col0.c.r >> 2); |
398 | color_array[0].g = (col0.c.g << 2) | (col0.c.g >> 4); |
399 | color_array[0].b = (col0.c.b << 3) | (col0.c.b >> 2); |
400 | color_array[0].a = 0xFF; |
401 | |
402 | color_array[1].r = (col1.c.r << 3) | (col1.c.r >> 2); |
403 | color_array[1].g = (col1.c.g << 2) | (col1.c.g >> 4); |
404 | color_array[1].b = (col1.c.b << 3) | (col1.c.b >> 2); |
405 | color_array[1].a = 0xFF; |
406 | |
407 | if( col0.u > col1.u ) { |
408 | // Four-color block: derive the other two colors. |
409 | color_array[2].r = (2 * color_array[0].r + color_array[1].r) / 3; |
410 | color_array[2].g = (2 * color_array[0].g + color_array[1].g) / 3; |
411 | color_array[2].b = (2 * color_array[0].b + color_array[1].b) / 3; |
412 | color_array[2].a = 0xFF; |
413 | |
414 | color_array[3].r = (2 * color_array[1].r + color_array[0].r) / 3; |
415 | color_array[3].g = (2 * color_array[1].g + color_array[0].g) / 3; |
416 | color_array[3].b = (2 * color_array[1].b + color_array[0].b) / 3; |
417 | color_array[3].a = 0xFF; |
418 | } |
419 | else { |
420 | // Three-color block: derive the other color. |
421 | color_array[2].r = (color_array[0].r + color_array[1].r) / 2; |
422 | color_array[2].g = (color_array[0].g + color_array[1].g) / 2; |
423 | color_array[2].b = (color_array[0].b + color_array[1].b) / 2; |
424 | color_array[2].a = 0xFF; |
425 | |
426 | // Set all components to 0 to match DXT specs. |
427 | color_array[3].r = 0x00; // color_array[2].r; |
428 | color_array[3].g = 0x00; // color_array[2].g; |
429 | color_array[3].b = 0x00; // color_array[2].b; |
430 | color_array[3].a = 0x00; |
431 | } |
432 | } |
433 | }; |
434 | |
435 | |
436 | static QDataStream & operator>> ( QDataStream & s, BlockDXT & c ) |
437 | { |
438 | return s >> c.col0 >> c.col1 >> c.row[0] >> c.row[1] >> c.row[2] >> c.row[3]; |
439 | } |
440 | |
441 | struct BlockDXTAlphaExplicit { |
442 | ushort row[4]; |
443 | }; |
444 | |
445 | static QDataStream & operator>> ( QDataStream & s, BlockDXTAlphaExplicit & c ) |
446 | { |
447 | return s >> c.row[0] >> c.row[1] >> c.row[2] >> c.row[3]; |
448 | } |
449 | |
450 | struct BlockDXTAlphaLinear { |
451 | uchar alpha0; |
452 | uchar alpha1; |
453 | uchar bits[6]; |
454 | |
455 | void GetAlphas( uchar alpha_array[8] ) |
456 | { |
457 | alpha_array[0] = alpha0; |
458 | alpha_array[1] = alpha1; |
459 | |
460 | // 8-alpha or 6-alpha block? |
461 | if( alpha_array[0] > alpha_array[1] ) |
462 | { |
463 | // 8-alpha block: derive the other 6 alphas. |
464 | // 000 = alpha_0, 001 = alpha_1, others are interpolated |
465 | |
466 | alpha_array[2] = ( 6 * alpha0 + alpha1) / 7; // bit code 010 |
467 | alpha_array[3] = ( 5 * alpha0 + 2 * alpha1) / 7; // Bit code 011 |
468 | alpha_array[4] = ( 4 * alpha0 + 3 * alpha1) / 7; // Bit code 100 |
469 | alpha_array[5] = ( 3 * alpha0 + 4 * alpha1) / 7; // Bit code 101 |
470 | alpha_array[6] = ( 2 * alpha0 + 5 * alpha1) / 7; // Bit code 110 |
471 | alpha_array[7] = ( alpha0 + 6 * alpha1) / 7; // Bit code 111 |
472 | } |
473 | else |
474 | { |
475 | // 6-alpha block: derive the other alphas. |
476 | // 000 = alpha_0, 001 = alpha_1, others are interpolated |
477 | |
478 | alpha_array[2] = (4 * alpha0 + alpha1) / 5; // Bit code 010 |
479 | alpha_array[3] = (3 * alpha0 + 2 * alpha1) / 5; // Bit code 011 |
480 | alpha_array[4] = (2 * alpha0 + 3 * alpha1) / 5; // Bit code 100 |
481 | alpha_array[5] = ( alpha0 + 4 * alpha1) / 5; // Bit code 101 |
482 | alpha_array[6] = 0x00; // Bit code 110 |
483 | alpha_array[7] = 0xFF; // Bit code 111 |
484 | } |
485 | } |
486 | |
487 | void GetBits( uchar bit_array[16] ) |
488 | { |
489 | // Split 24 packed bits into 8 bytes, 3 bits at a time. |
490 | uint b = bits[0] | bits[1] << 8 | bits[2] << 16; |
491 | bit_array[0] = uchar(b & 0x07); b >>= 3; |
492 | bit_array[1] = uchar(b & 0x07); b >>= 3; |
493 | bit_array[2] = uchar(b & 0x07); b >>= 3; |
494 | bit_array[3] = uchar(b & 0x07); b >>= 3; |
495 | bit_array[4] = uchar(b & 0x07); b >>= 3; |
496 | bit_array[5] = uchar(b & 0x07); b >>= 3; |
497 | bit_array[6] = uchar(b & 0x07); b >>= 3; |
498 | bit_array[7] = uchar(b & 0x07); |
499 | |
500 | b = bits[3] | bits[4] << 8 | bits[5] << 16; |
501 | bit_array[8] = uchar(b & 0x07); b >>= 3; |
502 | bit_array[9] = uchar(b & 0x07); b >>= 3; |
503 | bit_array[10] = uchar(b & 0x07); b >>= 3; |
504 | bit_array[11] = uchar(b & 0x07); b >>= 3; |
505 | bit_array[12] = uchar(b & 0x07); b >>= 3; |
506 | bit_array[13] = uchar(b & 0x07); b >>= 3; |
507 | bit_array[14] = uchar(b & 0x07); b >>= 3; |
508 | bit_array[15] = uchar(b & 0x07); |
509 | } |
510 | }; |
511 | |
512 | static QDataStream & operator>> ( QDataStream & s, BlockDXTAlphaLinear & c ) |
513 | { |
514 | s >> c.alpha0 >> c.alpha1; |
515 | return s >> c.bits[0] >> c.bits[1] >> c.bits[2] >> c.bits[3] >> c.bits[4] >> c.bits[5]; |
516 | } |
517 | |
518 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
519 | { |
520 | const uint w = header.width; |
521 | const uint h = header.height; |
522 | |
523 | BlockDXT block; |
524 | QRgb * scanline[4]; |
525 | |
526 | for( uint y = 0; y < h; y += 4 ) { |
527 | for( uint j = 0; j < 4; j++ ) { |
528 | scanline[j] = (QRgb *) img.scanLine( y + j ); |
529 | } |
530 | for( uint x = 0; x < w; x += 4 ) { |
531 | |
532 | // Read 64bit color block. |
533 | s >> block; |
534 | |
535 | // Decode color block. |
536 | Color8888 color_array[4]; |
537 | block.GetColors(color_array); |
538 | |
539 | // bit masks = 00000011, 00001100, 00110000, 11000000 |
540 | const uint masks[4] = { 3, 3<<2, 3<<4, 3<<6 }; |
541 | const int shift[4] = { 0, 2, 4, 6 }; |
542 | |
543 | // Write color block. |
544 | for( uint j = 0; j < 4; j++ ) { |
545 | for( uint i = 0; i < 4; i++ ) { |
546 | if( img.valid( x+i, y+j ) ) { |
547 | uint idx = (block.row[j] & masks[i]) >> shift[i]; |
548 | scanline[j][x+i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a); |
549 | } |
550 | } |
551 | } |
552 | } |
553 | } |
554 | return true; |
555 | } |
556 | |
557 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
558 | { |
559 | const uint w = header.width; |
560 | const uint h = header.height; |
561 | |
562 | BlockDXT block; |
563 | BlockDXTAlphaExplicit alpha; |
564 | QRgb * scanline[4]; |
565 | |
566 | for( uint y = 0; y < h; y += 4 ) { |
567 | for( uint j = 0; j < 4; j++ ) { |
568 | scanline[j] = (QRgb *) img.scanLine( y + j ); |
569 | } |
570 | for( uint x = 0; x < w; x += 4 ) { |
571 | |
572 | // Read 128bit color block. |
573 | s >> alpha; |
574 | s >> block; |
575 | |
576 | // Decode color block. |
577 | Color8888 color_array[4]; |
578 | block.GetColors(color_array); |
579 | |
580 | // bit masks = 00000011, 00001100, 00110000, 11000000 |
581 | const uint masks[4] = { 3, 3<<2, 3<<4, 3<<6 }; |
582 | const int shift[4] = { 0, 2, 4, 6 }; |
583 | |
584 | // Write color block. |
585 | for( uint j = 0; j < 4; j++ ) { |
586 | ushort a = alpha.row[j]; |
587 | for( uint i = 0; i < 4; i++ ) { |
588 | if( img.valid( x+i, y+j ) ) { |
589 | uint idx = (block.row[j] & masks[i]) >> shift[i]; |
590 | color_array[idx].a = a & 0x0f; |
591 | color_array[idx].a = color_array[idx].a | (color_array[idx].a << 4); |
592 | scanline[j][x+i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a); |
593 | } |
594 | a >>= 4; |
595 | } |
596 | } |
597 | } |
598 | } |
599 | return true; |
600 | } |
601 | |
602 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
603 | { |
604 | if( !LoadDXT3(s, header, img) ) return false; |
605 | //UndoPremultiplyAlpha(img); |
606 | return true; |
607 | } |
608 | |
609 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
610 | { |
611 | const uint w = header.width; |
612 | const uint h = header.height; |
613 | |
614 | BlockDXT block; |
615 | BlockDXTAlphaLinear alpha; |
616 | QRgb * scanline[4]; |
617 | |
618 | for( uint y = 0; y < h; y += 4 ) { |
619 | for( uint j = 0; j < 4; j++ ) { |
620 | scanline[j] = (QRgb *) img.scanLine( y + j ); |
621 | } |
622 | for( uint x = 0; x < w; x += 4 ) { |
623 | |
624 | // Read 128bit color block. |
625 | s >> alpha; |
626 | s >> block; |
627 | |
628 | // Decode color block. |
629 | Color8888 color_array[4]; |
630 | block.GetColors(color_array); |
631 | |
632 | uchar alpha_array[8]; |
633 | alpha.GetAlphas(alpha_array); |
634 | |
635 | uchar bit_array[16]; |
636 | alpha.GetBits(bit_array); |
637 | |
638 | // bit masks = 00000011, 00001100, 00110000, 11000000 |
639 | const uint masks[4] = { 3, 3<<2, 3<<4, 3<<6 }; |
640 | const int shift[4] = { 0, 2, 4, 6 }; |
641 | |
642 | // Write color block. |
643 | for( uint j = 0; j < 4; j++ ) { |
644 | for( uint i = 0; i < 4; i++ ) { |
645 | if( img.valid( x+i, y+j ) ) { |
646 | uint idx = (block.row[j] & masks[i]) >> shift[i]; |
647 | color_array[idx].a = alpha_array[bit_array[j*4+i]]; |
648 | scanline[j][x+i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a); |
649 | } |
650 | } |
651 | } |
652 | } |
653 | } |
654 | |
655 | return true; |
656 | } |
657 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
658 | { |
659 | if( !LoadDXT5(s, header, img) ) return false; |
660 | //UndoPremultiplyAlpha(img); |
661 | return true; |
662 | } |
663 | |
664 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
665 | { |
666 | const uint w = header.width; |
667 | const uint h = header.height; |
668 | |
669 | BlockDXT block; |
670 | BlockDXTAlphaLinear alpha; |
671 | QRgb * scanline[4]; |
672 | |
673 | for( uint y = 0; y < h; y += 4 ) { |
674 | for( uint j = 0; j < 4; j++ ) { |
675 | scanline[j] = (QRgb *) img.scanLine( y + j ); |
676 | } |
677 | for( uint x = 0; x < w; x += 4 ) { |
678 | |
679 | // Read 128bit color block. |
680 | s >> alpha; |
681 | s >> block; |
682 | |
683 | // Decode color block. |
684 | Color8888 color_array[4]; |
685 | block.GetColors(color_array); |
686 | |
687 | uchar alpha_array[8]; |
688 | alpha.GetAlphas(alpha_array); |
689 | |
690 | uchar bit_array[16]; |
691 | alpha.GetBits(bit_array); |
692 | |
693 | // bit masks = 00000011, 00001100, 00110000, 11000000 |
694 | const uint masks[4] = { 3, 3<<2, 3<<4, 3<<6 }; |
695 | const int shift[4] = { 0, 2, 4, 6 }; |
696 | |
697 | // Write color block. |
698 | for( uint j = 0; j < 4; j++ ) { |
699 | for( uint i = 0; i < 4; i++ ) { |
700 | if( img.valid( x+i, y+j ) ) { |
701 | uint idx = (block.row[j] & masks[i]) >> shift[i]; |
702 | color_array[idx].a = alpha_array[bit_array[j*4+i]]; |
703 | scanline[j][x+i] = qRgb(color_array[idx].a, color_array[idx].g, color_array[idx].b); |
704 | } |
705 | } |
706 | } |
707 | } |
708 | } |
709 | |
710 | return true; |
711 | } |
712 | |
713 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
714 | { |
715 | const uint w = header.width; |
716 | const uint h = header.height; |
717 | |
718 | BlockDXTAlphaLinear xblock; |
719 | BlockDXTAlphaLinear yblock; |
720 | QRgb * scanline[4]; |
721 | |
722 | for( uint y = 0; y < h; y += 4 ) { |
723 | for( uint j = 0; j < 4; j++ ) { |
724 | scanline[j] = (QRgb *) img.scanLine( y + j ); |
725 | } |
726 | for( uint x = 0; x < w; x += 4 ) { |
727 | |
728 | // Read 128bit color block. |
729 | s >> xblock; |
730 | s >> yblock; |
731 | |
732 | // Decode color block. |
733 | uchar xblock_array[8]; |
734 | xblock.GetAlphas(xblock_array); |
735 | |
736 | uchar xbit_array[16]; |
737 | xblock.GetBits(xbit_array); |
738 | |
739 | uchar yblock_array[8]; |
740 | yblock.GetAlphas(yblock_array); |
741 | |
742 | uchar ybit_array[16]; |
743 | yblock.GetBits(ybit_array); |
744 | |
745 | // Write color block. |
746 | for( uint j = 0; j < 4; j++ ) { |
747 | for( uint i = 0; i < 4; i++ ) { |
748 | if( img.valid( x+i, y+j ) ) { |
749 | const uchar nx = xblock_array[xbit_array[j*4+i]]; |
750 | const uchar ny = yblock_array[ybit_array[j*4+i]]; |
751 | |
752 | const float fx = float(nx) / 127.5f - 1.0f; |
753 | const float fy = float(ny) / 127.5f - 1.0f; |
754 | const float fz = sqrtf(1.0f - fx*fx - fy*fy); |
755 | const uchar nz = uchar((fz + 1.0f) * 127.5f); |
756 | |
757 | scanline[j][x+i] = qRgb(nx, ny, nz); |
758 | } |
759 | } |
760 | } |
761 | } |
762 | } |
763 | |
764 | return true; |
765 | } |
766 | |
767 | |
768 | |
769 | typedef bool (* TextureLoader)( QDataStream & s, const DDSHeader & , QImage & img ); |
770 | |
771 | // Get an appropriate texture loader for the given type. |
772 | static TextureLoader GetTextureLoader( DDSType type ) { |
773 | switch( type ) { |
774 | case DDS_A8R8G8B8: |
775 | return LoadA8R8G8B8; |
776 | case DDS_A1R5G5B5: |
777 | return LoadA1R5G5B5; |
778 | case DDS_A4R4G4B4: |
779 | return LoadA4R4G4B4; |
780 | case DDS_R8G8B8: |
781 | return LoadR8G8B8; |
782 | case DDS_R5G6B5: |
783 | return LoadR5G6B5; |
784 | case DDS_DXT1: |
785 | return LoadDXT1; |
786 | case DDS_DXT2: |
787 | return LoadDXT2; |
788 | case DDS_DXT3: |
789 | return LoadDXT3; |
790 | case DDS_DXT4: |
791 | return LoadDXT4; |
792 | case DDS_DXT5: |
793 | return LoadDXT5; |
794 | case DDS_RXGB: |
795 | return LoadRXGB; |
796 | case DDS_ATI2: |
797 | return LoadATI2; |
798 | default: |
799 | return NULL; |
800 | }; |
801 | } |
802 | |
803 | |
804 | // Load a 2d texture. |
805 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
806 | { |
807 | // Create dst image. |
808 | img = QImage( header.width, header.height, QImage::Format_RGB32 ); |
809 | |
810 | // Read image. |
811 | DDSType type = GetType( header ); |
812 | |
813 | // Enable alpha buffer for transparent or DDS images. |
814 | if( HasAlpha( header ) || type >= DDS_DXT1 ) { |
815 | img = img.convertToFormat( QImage::Format_ARGB32 ); |
816 | } |
817 | |
818 | TextureLoader loader = GetTextureLoader( type ); |
819 | if( loader == NULL ) { |
820 | return false; |
821 | } |
822 | |
823 | return loader( s, header, img ); |
824 | } |
825 | |
826 | |
827 | static int ( const DDSHeader & ) { |
828 | |
829 | DDSType type = GetType( header ); |
830 | |
831 | int mipmap = qMax(header.mipmapcount, 1U); |
832 | int size = 0; |
833 | int w = header.width; |
834 | int h = header.height; |
835 | |
836 | if( type >= DDS_DXT1 ) { |
837 | int multiplier = (type == DDS_DXT1) ? 8 : 16; |
838 | do { |
839 | int face_size = qMax(w/4,1) * qMax(h/4,1) * multiplier; |
840 | size += face_size; |
841 | w >>= 1; |
842 | h >>= 1; |
843 | } while( --mipmap ); |
844 | } |
845 | else { |
846 | int multiplier = header.pf.bitcount / 8; |
847 | do { |
848 | int face_size = w * h * multiplier; |
849 | size += face_size; |
850 | w = qMax( w>>1, 1 ); |
851 | h = qMax( h>>1, 1 ); |
852 | } while( --mipmap ); |
853 | } |
854 | |
855 | return size; |
856 | } |
857 | |
858 | #if CUBE_LAYOUT == HORIZONTAL |
859 | static int face_offset[6][2] = { {2, 1}, {0, 1}, {1, 0}, {1, 2}, {1, 1}, {3, 1} }; |
860 | #elif CUBE_LAYOUT == VERTICAL |
861 | static int face_offset[6][2] = { {2, 1}, {0, 1}, {1, 0}, {1, 2}, {1, 1}, {1, 3} }; |
862 | #endif |
863 | static int face_flags[6] = { |
864 | DDSCAPS2_CUBEMAP_POSITIVEX, |
865 | DDSCAPS2_CUBEMAP_NEGATIVEX, |
866 | DDSCAPS2_CUBEMAP_POSITIVEY, |
867 | DDSCAPS2_CUBEMAP_NEGATIVEY, |
868 | DDSCAPS2_CUBEMAP_POSITIVEZ, |
869 | DDSCAPS2_CUBEMAP_NEGATIVEZ |
870 | }; |
871 | |
872 | // Load unwrapped cube map. |
873 | static bool ( QDataStream & s, const DDSHeader & , QImage & img ) |
874 | { |
875 | // Create dst image. |
876 | #if CUBE_LAYOUT == HORIZONTAL |
877 | img = QImage( 4 * header.width, 3 * header.height, QImage::Format_RGB32 ); |
878 | #elif CUBE_LAYOUT == VERTICAL |
879 | img = QImage( 3 * header.width, 4 * header.height, QImage::Format_RGB32 ); |
880 | #endif |
881 | |
882 | DDSType type = GetType( header ); |
883 | |
884 | // Enable alpha buffer for transparent or DDS images. |
885 | if( HasAlpha( header ) || type >= DDS_DXT1 ) { |
886 | img = img.convertToFormat( QImage::Format_ARGB32 ); |
887 | } |
888 | |
889 | // Select texture loader. |
890 | TextureLoader loader = GetTextureLoader( type ); |
891 | if( loader == NULL ) { |
892 | return false; |
893 | } |
894 | |
895 | // Clear background. |
896 | img.fill( 0 ); |
897 | |
898 | // Create face image. |
899 | QImage face(header.width, header.height, QImage::Format_RGB32); |
900 | |
901 | int offset = s.device()->pos(); |
902 | int size = FaceOffset( header ); |
903 | |
904 | for( int i = 0; i < 6; i++ ) { |
905 | |
906 | if( !(header.caps.caps2 & face_flags[i]) ) { |
907 | // Skip face. |
908 | continue; |
909 | } |
910 | |
911 | // Seek device. |
912 | s.device()->seek( offset ); |
913 | offset += size; |
914 | |
915 | // Load face from stream. |
916 | if( !loader( s, header, face ) ) { |
917 | return false; |
918 | } |
919 | |
920 | #if CUBE_LAYOUT == VERTICAL |
921 | if( i == 5 ) { |
922 | face = face.mirror(true, true); |
923 | } |
924 | #endif |
925 | |
926 | // Compute face offsets. |
927 | int offset_x = face_offset[i][0] * header.width; |
928 | int offset_y = face_offset[i][1] * header.height; |
929 | |
930 | // Copy face on the image. |
931 | for( uint y = 0; y < header.height; y++ ) { |
932 | QRgb * src = (QRgb *) face.scanLine( y ); |
933 | QRgb * dst = (QRgb *) img.scanLine( y + offset_y ) + offset_x; |
934 | memcpy( dst, src, sizeof(QRgb) * header.width ); |
935 | } |
936 | } |
937 | |
938 | return true; |
939 | } |
940 | |
941 | |
942 | |
943 | DDSHandler::DDSHandler() |
944 | { |
945 | } |
946 | |
947 | bool DDSHandler::canRead() const |
948 | { |
949 | if (canRead(device())) { |
950 | setFormat("dds" ); |
951 | return true; |
952 | } |
953 | return false; |
954 | } |
955 | |
956 | bool DDSHandler::read(QImage *image) |
957 | { |
958 | QDataStream s( device() ); |
959 | s.setByteOrder( QDataStream::LittleEndian ); |
960 | |
961 | // Validate header. |
962 | uint fourcc; |
963 | s >> fourcc; |
964 | if( fourcc != FOURCC_DDS ) { |
965 | kDebug(399) << "This is not a DDS file." ; |
966 | return false; |
967 | } |
968 | |
969 | // Read image header. |
970 | DDSHeader ; |
971 | s >> header; |
972 | |
973 | // Check image file format. |
974 | if( s.atEnd() || !IsValid( header ) ) { |
975 | kDebug(399) << "This DDS file is not valid." ; |
976 | return false; |
977 | } |
978 | |
979 | // Determine image type, by now, we only support 2d textures. |
980 | if( !IsSupported( header ) ) { |
981 | kDebug(399) << "This DDS file is not supported." ; |
982 | return false; |
983 | } |
984 | |
985 | bool result; |
986 | |
987 | if( IsCubeMap( header ) ) { |
988 | result = LoadCubeMap( s, header, *image ); |
989 | } |
990 | else { |
991 | result = LoadTexture( s, header, *image ); |
992 | } |
993 | |
994 | return result; |
995 | } |
996 | |
997 | bool DDSHandler::write(const QImage &) |
998 | { |
999 | // TODO Stub! |
1000 | return false; |
1001 | } |
1002 | |
1003 | QByteArray DDSHandler::name() const |
1004 | { |
1005 | return "dds" ; |
1006 | } |
1007 | |
1008 | bool DDSHandler::canRead(QIODevice *device) |
1009 | { |
1010 | if (!device) { |
1011 | qWarning("DDSHandler::canRead() called with no device" ); |
1012 | return false; |
1013 | } |
1014 | |
1015 | qint64 oldPos = device->pos(); |
1016 | |
1017 | char head[3]; |
1018 | qint64 readBytes = device->read(head, sizeof(head)); |
1019 | if (readBytes != sizeof(head)) { |
1020 | if (device->isSequential()) { |
1021 | while (readBytes > 0) |
1022 | device->ungetChar(head[readBytes-- - 1]); |
1023 | } else { |
1024 | device->seek(oldPos); |
1025 | } |
1026 | return false; |
1027 | } |
1028 | |
1029 | if (device->isSequential()) { |
1030 | while (readBytes > 0) |
1031 | device->ungetChar(head[readBytes-- - 1]); |
1032 | } else { |
1033 | device->seek(oldPos); |
1034 | } |
1035 | |
1036 | return qstrncmp(head, "DDS" , 3) == 0; |
1037 | } |
1038 | |
1039 | class DDSPlugin : public QImageIOPlugin |
1040 | { |
1041 | public: |
1042 | QStringList keys() const; |
1043 | Capabilities capabilities(QIODevice *device, const QByteArray &format) const; |
1044 | QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; |
1045 | }; |
1046 | |
1047 | QStringList DDSPlugin::keys() const |
1048 | { |
1049 | return QStringList() << "dds" ; |
1050 | } |
1051 | |
1052 | QImageIOPlugin::Capabilities DDSPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
1053 | { |
1054 | if (format == "dds" ) |
1055 | return Capabilities(CanRead); |
1056 | if (!format.isEmpty()) |
1057 | return 0; |
1058 | if (!device->isOpen()) |
1059 | return 0; |
1060 | |
1061 | Capabilities cap; |
1062 | if (device->isReadable() && DDSHandler::canRead(device)) |
1063 | cap |= CanRead; |
1064 | return cap; |
1065 | } |
1066 | |
1067 | QImageIOHandler *DDSPlugin::create(QIODevice *device, const QByteArray &format) const |
1068 | { |
1069 | QImageIOHandler *handler = new DDSHandler; |
1070 | handler->setDevice(device); |
1071 | handler->setFormat(format); |
1072 | return handler; |
1073 | } |
1074 | |
1075 | Q_EXPORT_STATIC_PLUGIN(DDSPlugin) |
1076 | Q_EXPORT_PLUGIN2(dds, DDSPlugin) |
1077 | |