JOGL v2.6.0-rc-20250712
JOGL, High-Performance Graphics Binding for Java™ (public API).
DDSImage.java
Go to the documentation of this file.
1/*
2 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * - Redistribution of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistribution in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * Neither the name of Sun Microsystems, Inc. or the names of
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * This software is provided "AS IS," without a warranty of any kind. ALL
20 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
21 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
22 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
23 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
24 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
25 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
26 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
27 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
28 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
29 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
30 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31 *
32 * You acknowledge that this software is not designed or intended for use
33 * in the design, construction, operation or maintenance of any nuclear
34 * facility.
35 *
36 * Sun gratefully acknowledges that this software was originally authored
37 * and developed by Kenneth Bradley Russell and Christopher John Kline.
38 */
39
40package com.jogamp.opengl.util.texture.spi;
41
42import java.io.BufferedInputStream;
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.io.InputStream;
48import java.io.PrintStream;
49import java.nio.ByteBuffer;
50import java.nio.ByteOrder;
51import java.nio.channels.FileChannel;
52
53import com.jogamp.opengl.GL;
54import com.jogamp.common.nio.Buffers;
55import com.jogamp.common.util.IOUtil;
56import com.jogamp.opengl.util.texture.ImageType;
57
58/** A reader and writer for DirectDraw Surface (.dds) files, which are
59 used to describe textures. These files can contain multiple mipmap
60 levels in one file. This class is currently minimal and does not
61 support all of the possible file formats. */
62
63public class DDSImage {
64
65 /** Simple class describing images and data; does not encapsulate
66 image format information. User is responsible for transmitting
67 that information in another way. */
68
69 public static class ImageInfo {
70 private final ByteBuffer data;
71 private final int width;
72 private final int height;
73 private final boolean isCompressed;
74 private final int compressionFormat;
75
76 public ImageInfo(final ByteBuffer data, final int width, final int height, final boolean compressed, final int compressionFormat) {
77 this.data = data; this.width = width; this.height = height;
78 this.isCompressed = compressed; this.compressionFormat = compressionFormat;
79 }
80 public int getWidth() { return width; }
81 public int getHeight() { return height; }
82 public ByteBuffer getData() { return data; }
83 public boolean isCompressed() { return isCompressed; }
84 public int getCompressionFormat() {
85 if (!isCompressed())
86 throw new RuntimeException("Should not call unless compressed");
87 return compressionFormat;
88 }
89 }
90
91 private FileInputStream fis;
92 private FileChannel chan;
93 private ByteBuffer buf;
94 private Header header;
95
96 //
97 // Selected bits in header flags
98 //
99
100 public static final int DDSD_CAPS = 0x00000001; // Capacities are valid
101 public static final int DDSD_HEIGHT = 0x00000002; // Height is valid
102 public static final int DDSD_WIDTH = 0x00000004; // Width is valid
103 public static final int DDSD_PITCH = 0x00000008; // Pitch is valid
104 public static final int DDSD_BACKBUFFERCOUNT = 0x00000020; // Back buffer count is valid
105 public static final int DDSD_ZBUFFERBITDEPTH = 0x00000040; // Z-buffer bit depth is valid (shouldn't be used in DDSURFACEDESC2)
106 public static final int DDSD_ALPHABITDEPTH = 0x00000080; // Alpha bit depth is valid
107 public static final int DDSD_LPSURFACE = 0x00000800; // lpSurface is valid
108 public static final int DDSD_PIXELFORMAT = 0x00001000; // ddpfPixelFormat is valid
109 public static final int DDSD_MIPMAPCOUNT = 0x00020000; // Mip map count is valid
110 public static final int DDSD_LINEARSIZE = 0x00080000; // dwLinearSize is valid
111 public static final int DDSD_DEPTH = 0x00800000; // dwDepth is valid
112
113 public static final int DDPF_ALPHAPIXELS = 0x00000001; // Alpha channel is present
114 public static final int DDPF_ALPHA = 0x00000002; // Only contains alpha information
115 public static final int DDPF_FOURCC = 0x00000004; // FourCC code is valid
116 public static final int DDPF_PALETTEINDEXED4 = 0x00000008; // Surface is 4-bit color indexed
117 public static final int DDPF_PALETTEINDEXEDTO8 = 0x00000010; // Surface is indexed into a palette which stores indices
118 // into the destination surface's 8-bit palette
119 public static final int DDPF_PALETTEINDEXED8 = 0x00000020; // Surface is 8-bit color indexed
120 public static final int DDPF_RGB = 0x00000040; // RGB data is present
121 public static final int DDPF_COMPRESSED = 0x00000080; // Surface will accept pixel data in the format specified
122 // and compress it during the write
123 public static final int DDPF_RGBTOYUV = 0x00000100; // Surface will accept RGB data and translate it during
124 // the write to YUV data. The format of the data to be written
125 // will be contained in the pixel format structure. The DDPF_RGB
126 // flag will be set.
127 public static final int DDPF_YUV = 0x00000200; // Pixel format is YUV - YUV data in pixel format struct is valid
128 public static final int DDPF_ZBUFFER = 0x00000400; // Pixel format is a z buffer only surface
129 public static final int DDPF_PALETTEINDEXED1 = 0x00000800; // Surface is 1-bit color indexed
130 public static final int DDPF_PALETTEINDEXED2 = 0x00001000; // Surface is 2-bit color indexed
131 public static final int DDPF_ZPIXELS = 0x00002000; // Surface contains Z information in the pixels
132
133 // Selected bits in DDS capabilities flags
134 public static final int DDSCAPS_TEXTURE = 0x00001000; // Can be used as a texture
135 public static final int DDSCAPS_MIPMAP = 0x00400000; // Is one level of a mip-map
136 public static final int DDSCAPS_COMPLEX = 0x00000008; // Complex surface structure, such as a cube map
137
138 // Selected bits in DDS extended capabilities flags
139 public static final int DDSCAPS2_CUBEMAP = 0x00000200;
140 public static final int DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400;
141 public static final int DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800;
142 public static final int DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000;
143 public static final int DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000;
144 public static final int DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000;
145 public static final int DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000;
146
147 // Known pixel formats
148 public static final int D3DFMT_UNKNOWN = 0;
149 public static final int D3DFMT_R8G8B8 = 20;
150 public static final int D3DFMT_A8R8G8B8 = 21;
151 public static final int D3DFMT_X8R8G8B8 = 22;
152 // The following are also valid FourCC codes
153 public static final int D3DFMT_DXT1 = 0x31545844;
154 public static final int D3DFMT_DXT2 = 0x32545844;
155 public static final int D3DFMT_DXT3 = 0x33545844;
156 public static final int D3DFMT_DXT4 = 0x34545844;
157 public static final int D3DFMT_DXT5 = 0x35545844;
158
159 /** Reads a DirectDraw surface from the specified file name,
160 returning the resulting DDSImage.
161
162 @param filename File name
163 @return DDS image object
164 @throws java.io.IOException if an I/O exception occurred
165 */
166 public static DDSImage read(final String filename) throws IOException {
167 return read(new File(filename));
168 }
169
170 /** Reads a DirectDraw surface from the specified file, returning
171 the resulting DDSImage.
172
173 @param file File object
174 @return DDS image object
175 @throws java.io.IOException if an I/O exception occurred
176 */
177 public static DDSImage read(final File file) throws IOException {
178 final DDSImage image = new DDSImage();
179 image.readFromFile(file);
180 return image;
181 }
182
183 /** Reads a DirectDraw surface from the specified ByteBuffer, returning
184 the resulting DDSImage.
185
186 @param buf Input data
187 @return DDS image object
188 @throws java.io.IOException if an I/O exception occurred
189 */
190 public static DDSImage read(final ByteBuffer buf) throws IOException {
191 final DDSImage image = new DDSImage();
192 image.readFromBuffer(buf);
193 return image;
194 }
195
196 /** Closes open files and resources associated with the open
197 DDSImage. No other methods may be called on this object once
198 this is called. */
199 public void close() {
200 try {
201 if (chan != null) {
202 chan.close();
203 chan = null;
204 }
205 if (fis != null) {
206 fis.close();
207 fis = null;
208 }
209 buf = null;
210 } catch (final IOException e) {
211 e.printStackTrace();
212 }
213 }
214
215 /**
216 * Creates a new DDSImage from data supplied by the user. The
217 * resulting DDSImage can be written to disk using the write()
218 * method.
219 *
220 * @param d3dFormat the D3DFMT_ constant describing the data; it is
221 * assumed that it is packed tightly
222 * @param width the width in pixels of the topmost mipmap image
223 * @param height the height in pixels of the topmost mipmap image
224 * @param mipmapData the data for each mipmap level of the resulting
225 * DDSImage; either only one mipmap level should
226 * be specified, or they all must be
227 * @throws IllegalArgumentException if the data does not match the
228 * specified arguments
229 * @return DDS image object
230 */
231 public static DDSImage createFromData(final int d3dFormat,
232 final int width,
233 final int height,
234 final ByteBuffer[] mipmapData) throws IllegalArgumentException {
235 final DDSImage image = new DDSImage();
236 image.initFromData(d3dFormat, width, height, mipmapData);
237 return image;
238 }
239
240 /**
241 * Writes this DDSImage to the specified file name.
242 * @param filename File name to write to
243 * @throws java.io.IOException if an I/O exception occurred
244 */
245 public void write(final String filename) throws IOException {
246 write(new File(filename));
247 }
248
249 /**
250 * Writes this DDSImage to the specified file name.
251 * @param file File object to write to
252 * @throws java.io.IOException if an I/O exception occurred
253 */
254 public void write(final File file) throws IOException {
255 final FileOutputStream stream = IOUtil.getFileOutputStream(file, true);
256 final FileChannel chan = stream.getChannel();
257 // Create ByteBuffer for header in case the start of our
258 // ByteBuffer isn't actually memory-mapped
259 final ByteBuffer hdr = ByteBuffer.allocate(Header.writtenSize());
260 hdr.order(ByteOrder.LITTLE_ENDIAN);
261 header.write(hdr);
262 hdr.rewind();
263 chan.write(hdr);
264 buf.position(Header.writtenSize());
265 chan.write(buf);
266 chan.force(true);
267 chan.close();
268 stream.close();
269 }
270
271 /** Test for presence/absence of surface description flags (DDSD_*)
272 * @param flag DDSD_* flags set to test
273 * @return true if flag present or false otherwise
274 */
275 public boolean isSurfaceDescFlagSet(final int flag) {
276 return ((header.flags & flag) != 0);
277 }
278
279 /** Test for presence/absence of pixel format flags (DDPF_*) */
280 public boolean isPixelFormatFlagSet(final int flag) {
281 return ((header.pfFlags & flag) != 0);
282 }
283
284 /** Gets the pixel format of this texture (D3DFMT_*) based on some
285 heuristics. Returns D3DFMT_UNKNOWN if could not recognize the
286 pixel format. */
287 public int getPixelFormat() {
288 if (isCompressed()) {
289 return getCompressionFormat();
290 } else if (isPixelFormatFlagSet(DDPF_RGB)) {
292 if (getDepth() == 32 &&
293 header.pfRBitMask == 0x00FF0000 &&
294 header.pfGBitMask == 0x0000FF00 &&
295 header.pfBBitMask == 0x000000FF &&
296 header.pfABitMask == 0xFF000000) {
297 return D3DFMT_A8R8G8B8;
298 }
299 } else {
300 if (getDepth() == 24 &&
301 header.pfRBitMask == 0x00FF0000 &&
302 header.pfGBitMask == 0x0000FF00 &&
303 header.pfBBitMask == 0x000000FF) {
304 return D3DFMT_R8G8B8;
305 } else if (getDepth() == 32 &&
306 header.pfRBitMask == 0x00FF0000 &&
307 header.pfGBitMask == 0x0000FF00 &&
308 header.pfBBitMask == 0x000000FF) {
309 return D3DFMT_X8R8G8B8;
310 }
311 }
312 }
313
314 return D3DFMT_UNKNOWN;
315 }
316
317 /**
318 * Indicates whether this texture is cubemap
319 * @return true if cubemap or false otherwise
320 */
321 public boolean isCubemap() {
322 return ((header.ddsCaps1 & DDSCAPS_COMPLEX) != 0) && ((header.ddsCaps2 & DDSCAPS2_CUBEMAP) != 0);
323 }
324
325 /**
326 * Indicates whethe this cubemap side present
327 * @param side Side to test
328 * @return true if side present or false otherwise
329 */
330 public boolean isCubemapSidePresent(final int side) {
331 return isCubemap() && (header.ddsCaps2 & side) != 0;
332 }
333
334 /** Indicates whether this texture is compressed. */
335 public boolean isCompressed() {
337 }
338
339 /** If this surface is compressed, returns the kind of compression
340 used (DXT1..DXT5). */
341 public int getCompressionFormat() {
342 return header.pfFourCC;
343 }
344
345 /** Width of the texture (or the top-most mipmap if mipmaps are
346 present) */
347 public int getWidth() {
348 return header.width;
349 }
350
351 /** Height of the texture (or the top-most mipmap if mipmaps are
352 present) */
353 public int getHeight() {
354 return header.height;
355 }
356
357 /** Total number of bits per pixel. Only valid if DDPF_RGB is
358 present. For A8R8G8B8, would be 32. */
359 public int getDepth() {
360 return header.pfRGBBitCount;
361 }
362
363 /** Number of mip maps in the texture */
364 public int getNumMipMaps() {
366 return 0;
367 }
368 return header.mipMapCountOrAux;
369 }
370
371 /** Gets the <i>i</i>th mipmap data (0..getNumMipMaps() - 1)
372 * @param map Mipmap index
373 * @return Image object
374 */
375 public ImageInfo getMipMap(final int map) {
376 return getMipMap( 0, map );
377 }
378
379 /**
380 * Gets the <i>i</i>th mipmap data (0..getNumMipMaps() - 1)
381 * @param side Cubemap side or 0 for 2D texture
382 * @param map Mipmap index
383 * @return Image object
384 */
385 public ImageInfo getMipMap(final int side, final int map) {
386 if (!isCubemap() && (side != 0)) {
387 throw new RuntimeException( "Illegal side for 2D texture: " + side );
388 }
389 if (isCubemap() && !isCubemapSidePresent(side)) {
390 throw new RuntimeException( "Illegal side, side not present: " + side );
391 }
392 if (getNumMipMaps() > 0 &&
393 ((map < 0) || (map >= getNumMipMaps()))) {
394 throw new RuntimeException("Illegal mipmap number " + map + " (0.." + (getNumMipMaps() - 1) + ")");
395 }
396
397 // Figure out how far to seek
398 int seek = Header.writtenSize();
399 if (isCubemap()) {
400 seek += sideShiftInBytes(side);
401 }
402 for (int i = 0; i < map; i++) {
403 seek += mipMapSizeInBytes(i);
404 }
405 buf.limit(seek + mipMapSizeInBytes(map));
406 buf.position(seek);
407 final ByteBuffer next = buf.slice();
408 buf.position(0);
409 buf.limit(buf.capacity());
410 return new ImageInfo(next, mipMapWidth(map), mipMapHeight(map), isCompressed(), getCompressionFormat());
411 }
412
413 /** Returns an array of ImageInfos corresponding to all mipmap
414 levels of this DDS file.
415 @return Mipmap image objects set
416 */
418 return getAllMipMaps(0);
419 }
420
421 /**
422 * Returns an array of ImageInfos corresponding to all mipmap
423 * levels of this DDS file.
424 * @param side Cubemap side or 0 for 2D texture
425 * @return Mipmap image objects set
426 */
427 public ImageInfo[] getAllMipMaps( final int side ) {
428 int numLevels = getNumMipMaps();
429 if (numLevels == 0) {
430 numLevels = 1;
431 }
432 final ImageInfo[] result = new ImageInfo[numLevels];
433 for (int i = 0; i < numLevels; i++) {
434 result[i] = getMipMap(side, i);
435 }
436 return result;
437 }
438
439 /** Converts e.g. DXT1 compression format constant (see {@link
440 #getCompressionFormat}) into "DXT1".
441 @param compressionFormat Compression format constant
442 @return String format code
443 */
444 public static String getCompressionFormatName(int compressionFormat) {
445 final StringBuilder buf = new StringBuilder();
446 for (int i = 0; i < 4; i++) {
447 final char c = (char) (compressionFormat & 0xFF);
448 buf.append(c);
449 compressionFormat = compressionFormat >> 8;
450 }
451 return buf.toString();
452 }
453
454 /** Allocates a temporary, empty ByteBuffer suitable for use in a
455 call to glCompressedTexImage2D. This is used by the Texture
456 class to expand non-power-of-two DDS compressed textures to
457 power-of-two sizes on hardware not supporting OpenGL 2.0 and the
458 NPOT texture extension. The specified OpenGL internal format
459 must be one of GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
460 GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
461 GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, or
462 GL_COMPRESSED_RGBA_S3TC_DXT5_EXT.
463 */
464 public static ByteBuffer allocateBlankBuffer(final int width,
465 final int height,
466 final int openGLInternalFormat) {
467 int size = width * height;
468 switch (openGLInternalFormat) {
471 size /= 2;
472 break;
473
476 break;
477
478 default:
479 throw new IllegalArgumentException("Illegal OpenGL texture internal format " +
480 openGLInternalFormat);
481 }
482 if (size == 0)
483 size = 1;
484 return Buffers.newDirectByteBuffer(size);
485 }
486
487 public void debugPrint() {
488 final PrintStream tty = System.err;
489 tty.println("Compressed texture: " + isCompressed());
490 if (isCompressed()) {
491 final int fmt = getCompressionFormat();
492 final String name = getCompressionFormatName(fmt);
493 tty.println("Compression format: 0x" + Integer.toHexString(fmt) + " (" + name + ")");
494 }
495 tty.println("Width: " + header.width + " Height: " + header.height);
496 tty.println("header.pitchOrLinearSize: " + header.pitchOrLinearSize);
497 tty.println("header.pfRBitMask: 0x" + Integer.toHexString(header.pfRBitMask));
498 tty.println("header.pfGBitMask: 0x" + Integer.toHexString(header.pfGBitMask));
499 tty.println("header.pfBBitMask: 0x" + Integer.toHexString(header.pfBBitMask));
500 tty.println("SurfaceDesc flags:");
501 boolean recognizedAny = false;
502 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_CAPS, "DDSD_CAPS");
503 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_HEIGHT, "DDSD_HEIGHT");
504 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_WIDTH, "DDSD_WIDTH");
505 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PITCH, "DDSD_PITCH");
506 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_BACKBUFFERCOUNT, "DDSD_BACKBUFFERCOUNT");
507 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ZBUFFERBITDEPTH, "DDSD_ZBUFFERBITDEPTH");
508 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ALPHABITDEPTH, "DDSD_ALPHABITDEPTH");
509 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LPSURFACE, "DDSD_LPSURFACE");
510 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PIXELFORMAT, "DDSD_PIXELFORMAT");
511 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_MIPMAPCOUNT, "DDSD_MIPMAPCOUNT");
512 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LINEARSIZE, "DDSD_LINEARSIZE");
513 recognizedAny |= printIfRecognized(tty, header.flags, DDSD_DEPTH, "DDSD_DEPTH");
514 if (!recognizedAny) {
515 tty.println("(none)");
516 }
517 tty.println("Raw SurfaceDesc flags: 0x" + Integer.toHexString(header.flags));
518 tty.println("Pixel format flags:");
519 recognizedAny = false;
520 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHAPIXELS, "DDPF_ALPHAPIXELS");
521 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHA, "DDPF_ALPHA");
522 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_FOURCC, "DDPF_FOURCC");
523 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED4, "DDPF_PALETTEINDEXED4");
524 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXEDTO8, "DDPF_PALETTEINDEXEDTO8");
525 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED8, "DDPF_PALETTEINDEXED8");
526 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGB, "DDPF_RGB");
527 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_COMPRESSED, "DDPF_COMPRESSED");
528 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGBTOYUV, "DDPF_RGBTOYUV");
529 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_YUV, "DDPF_YUV");
530 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZBUFFER, "DDPF_ZBUFFER");
531 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED1, "DDPF_PALETTEINDEXED1");
532 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED2, "DDPF_PALETTEINDEXED2");
533 recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZPIXELS, "DDPF_ZPIXELS");
534 if (!recognizedAny) {
535 tty.println("(none)");
536 }
537 tty.println("Raw pixel format flags: 0x" + Integer.toHexString(header.pfFlags));
538 tty.println("Depth: " + getDepth());
539 tty.println("Number of mip maps: " + getNumMipMaps());
540 final int fmt = getPixelFormat();
541 tty.print("Pixel format: ");
542 switch (fmt) {
543 case D3DFMT_R8G8B8: tty.println("D3DFMT_R8G8B8"); break;
544 case D3DFMT_A8R8G8B8: tty.println("D3DFMT_A8R8G8B8"); break;
545 case D3DFMT_X8R8G8B8: tty.println("D3DFMT_X8R8G8B8"); break;
546 case D3DFMT_DXT1: tty.println("D3DFMT_DXT1"); break;
547 case D3DFMT_DXT2: tty.println("D3DFMT_DXT2"); break;
548 case D3DFMT_DXT3: tty.println("D3DFMT_DXT3"); break;
549 case D3DFMT_DXT4: tty.println("D3DFMT_DXT4"); break;
550 case D3DFMT_DXT5: tty.println("D3DFMT_DXT5"); break;
551 case D3DFMT_UNKNOWN: tty.println("D3DFMT_UNKNOWN"); break;
552 default: tty.println("(unknown pixel format " + fmt + ")"); break;
553 }
554 }
555
556 //----------------------------------------------------------------------
557 // Internals only below this point
558 //
559
560 private static final int MAGIC = 0x20534444;
561
562 static class Header {
563 int size; // size of the DDSURFACEDESC structure
564 int flags; // determines what fields are valid
565 int height; // height of surface to be created
566 int width; // width of input surface
567 int pitchOrLinearSize;
568 int backBufferCountOrDepth;
569 int mipMapCountOrAux; // number of mip-map levels requested (in this context)
570 int alphaBitDepth; // depth of alpha buffer requested
571 int reserved1; // reserved
572 int surface; // pointer to the associated surface memory
573 // NOTE: following two entries are from DDCOLORKEY data structure
574 // Are overlaid with color for empty cubemap faces (unused in this reader)
575 int colorSpaceLowValue;
576 int colorSpaceHighValue;
577 int destBltColorSpaceLowValue;
578 int destBltColorSpaceHighValue;
579 int srcOverlayColorSpaceLowValue;
580 int srcOverlayColorSpaceHighValue;
581 int srcBltColorSpaceLowValue;
582 int srcBltColorSpaceHighValue;
583 // NOTE: following entries are from DDPIXELFORMAT data structure
584 // Are overlaid with flexible vertex format description of vertex
585 // buffers (unused in this reader)
586 int pfSize; // size of DDPIXELFORMAT structure
587 int pfFlags; // pixel format flags
588 int pfFourCC; // (FOURCC code)
589 // Following five entries have multiple interpretations, not just
590 // RGBA (but that's all we support right now)
591 int pfRGBBitCount; // how many bits per pixel
592 int pfRBitMask; // mask for red bits
593 int pfGBitMask; // mask for green bits
594 int pfBBitMask; // mask for blue bits
595 int pfABitMask; // mask for alpha channel
596 int ddsCaps1; // Texture and mip-map flags
597 int ddsCaps2; // Advanced capabilities including cubemap support
598 int ddsCapsReserved1;
599 int ddsCapsReserved2;
600 int textureStage; // stage in multitexture cascade
601
602 void read(final ByteBuffer buf) throws IOException {
603 final int magic = buf.getInt();
604 if (magic != MAGIC) {
605 throw new IOException("Incorrect magic number 0x" +
606 Integer.toHexString(magic) +
607 " (expected " + MAGIC + ")");
608 }
609
610 size = buf.getInt();
611 flags = buf.getInt();
612 height = buf.getInt();
613 width = buf.getInt();
614 pitchOrLinearSize = buf.getInt();
615 backBufferCountOrDepth = buf.getInt();
616 mipMapCountOrAux = buf.getInt();
617 alphaBitDepth = buf.getInt();
618 reserved1 = buf.getInt();
619 surface = buf.getInt();
620 colorSpaceLowValue = buf.getInt();
621 colorSpaceHighValue = buf.getInt();
622 destBltColorSpaceLowValue = buf.getInt();
623 destBltColorSpaceHighValue = buf.getInt();
624 srcOverlayColorSpaceLowValue = buf.getInt();
625 srcOverlayColorSpaceHighValue = buf.getInt();
626 srcBltColorSpaceLowValue = buf.getInt();
627 srcBltColorSpaceHighValue = buf.getInt();
628 pfSize = buf.getInt();
629 pfFlags = buf.getInt();
630 pfFourCC = buf.getInt();
631 pfRGBBitCount = buf.getInt();
632 pfRBitMask = buf.getInt();
633 pfGBitMask = buf.getInt();
634 pfBBitMask = buf.getInt();
635 pfABitMask = buf.getInt();
636 ddsCaps1 = buf.getInt();
637 ddsCaps2 = buf.getInt();
638 ddsCapsReserved1 = buf.getInt();
639 ddsCapsReserved2 = buf.getInt();
640 textureStage = buf.getInt();
641 }
642
643 // buf must be in little-endian byte order
644 void write(final ByteBuffer buf) {
645 buf.putInt(MAGIC);
646 buf.putInt(size);
647 buf.putInt(flags);
648 buf.putInt(height);
649 buf.putInt(width);
650 buf.putInt(pitchOrLinearSize);
651 buf.putInt(backBufferCountOrDepth);
652 buf.putInt(mipMapCountOrAux);
653 buf.putInt(alphaBitDepth);
654 buf.putInt(reserved1);
655 buf.putInt(surface);
656 buf.putInt(colorSpaceLowValue);
657 buf.putInt(colorSpaceHighValue);
658 buf.putInt(destBltColorSpaceLowValue);
659 buf.putInt(destBltColorSpaceHighValue);
660 buf.putInt(srcOverlayColorSpaceLowValue);
661 buf.putInt(srcOverlayColorSpaceHighValue);
662 buf.putInt(srcBltColorSpaceLowValue);
663 buf.putInt(srcBltColorSpaceHighValue);
664 buf.putInt(pfSize);
665 buf.putInt(pfFlags);
666 buf.putInt(pfFourCC);
667 buf.putInt(pfRGBBitCount);
668 buf.putInt(pfRBitMask);
669 buf.putInt(pfGBitMask);
670 buf.putInt(pfBBitMask);
671 buf.putInt(pfABitMask);
672 buf.putInt(ddsCaps1);
673 buf.putInt(ddsCaps2);
674 buf.putInt(ddsCapsReserved1);
675 buf.putInt(ddsCapsReserved2);
676 buf.putInt(textureStage);
677 }
678
679 private static int size() {
680 return 124;
681 }
682
683 private static int pfSize() {
684 return 32;
685 }
686
687 private static int writtenSize() {
688 return 128;
689 }
690 }
691
692 private DDSImage() {
693 }
694
695 private void readFromFile(final File file) throws IOException {
696 fis = new FileInputStream(file);
697 chan = fis.getChannel();
698 final ByteBuffer buf = chan.map(FileChannel.MapMode.READ_ONLY,
699 0, (int) file.length());
700 readFromBuffer(buf);
701 }
702
703 private void readFromBuffer(final ByteBuffer buf) throws IOException {
704 this.buf = buf;
705 buf.order(ByteOrder.LITTLE_ENDIAN);
706 header = new Header();
707 header.read(buf);
708 fixupHeader();
709 }
710
711 private void initFromData(final int d3dFormat,
712 final int width,
713 final int height,
714 final ByteBuffer[] mipmapData) throws IllegalArgumentException {
715 // Check size of mipmap data compared against format, width and
716 // height
717 int topmostMipmapSize = width * height;
718 int pitchOrLinearSize = width;
719 boolean isCompressed = false;
720 switch (d3dFormat) {
721 case D3DFMT_R8G8B8: topmostMipmapSize *= 3; pitchOrLinearSize *= 3; break;
722 case D3DFMT_A8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break;
723 case D3DFMT_X8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break;
724 case D3DFMT_DXT1:
725 case D3DFMT_DXT2:
726 case D3DFMT_DXT3:
727 case D3DFMT_DXT4:
728 case D3DFMT_DXT5:
729 topmostMipmapSize = computeCompressedBlockSize(width, height, 1, d3dFormat);
730 pitchOrLinearSize = topmostMipmapSize;
731 isCompressed = true;
732 break;
733 default:
734 throw new IllegalArgumentException("d3dFormat must be one of the known formats");
735 }
736
737 // Now check the mipmaps against this size
738 int curSize = topmostMipmapSize;
739 int mipmapWidth = width;
740 int mipmapHeight = height;
741 int totalSize = 0;
742 for (int i = 0; i < mipmapData.length; i++) {
743 if (mipmapData[i].remaining() != curSize) {
744 throw new IllegalArgumentException("Mipmap level " + i +
745 " didn't match expected data size (expected " + curSize + ", got " +
746 mipmapData[i].remaining() + ")");
747 }
748 // Compute next mipmap size
749 if (mipmapWidth > 1) mipmapWidth /= 2;
750 if (mipmapHeight > 1) mipmapHeight /= 2;
751 curSize = computeBlockSize(mipmapWidth, mipmapHeight, 1, d3dFormat);
752 totalSize += mipmapData[i].remaining();
753 }
754
755 // OK, create one large ByteBuffer to hold all of the mipmap data
756 totalSize += Header.writtenSize();
757 final ByteBuffer buf = ByteBuffer.allocate(totalSize);
758 buf.position(Header.writtenSize());
759 for (int i = 0; i < mipmapData.length; i++) {
760 buf.put(mipmapData[i]);
761 }
762 this.buf = buf;
763
764 // Allocate and initialize a Header
765 header = new Header();
766 header.size = Header.size();
768 if (mipmapData.length > 1) {
769 header.flags |= DDSD_MIPMAPCOUNT;
770 header.mipMapCountOrAux = mipmapData.length;
771 }
772 header.width = width;
773 header.height = height;
774 if (isCompressed) {
775 header.flags |= DDSD_LINEARSIZE;
776 header.pfFlags |= DDPF_FOURCC;
777 header.pfFourCC = d3dFormat;
778 } else {
779 header.flags |= DDSD_PITCH;
780 // Figure out the various settings from the pixel format
781 header.pfFlags |= DDPF_RGB;
782 switch (d3dFormat) {
783 case D3DFMT_R8G8B8: header.pfRGBBitCount = 24; break;
784 case D3DFMT_A8R8G8B8: header.pfRGBBitCount = 32; header.pfFlags |= DDPF_ALPHAPIXELS; break;
785 case D3DFMT_X8R8G8B8: header.pfRGBBitCount = 32; break;
786 }
787 header.pfRBitMask = 0x00FF0000;
788 header.pfGBitMask = 0x0000FF00;
789 header.pfBBitMask = 0x000000FF;
790 if (d3dFormat == D3DFMT_A8R8G8B8) {
791 header.pfABitMask = 0xFF000000;
792 }
793 }
794 header.pitchOrLinearSize = pitchOrLinearSize;
795 header.pfSize = Header.pfSize();
796 // Not sure whether we can get away with leaving the rest of the
797 // header blank
798 }
799
800 // Microsoft doesn't follow their own specifications and the
801 // simplest conversion using the DxTex tool to e.g. a DXT3 texture
802 // results in an illegal .dds file without either DDSD_PITCH or
803 // DDSD_LINEARSIZE set in the header's flags. This code, adapted
804 // from the DevIL library, fixes up the header in these situations.
805 private void fixupHeader() {
807 // Figure out how big the linear size should be
808 int depth = header.backBufferCountOrDepth;
809 if (depth == 0) {
810 depth = 1;
811 }
812
813 header.pitchOrLinearSize = computeCompressedBlockSize(getWidth(), getHeight(), depth, getCompressionFormat());
814 header.flags |= DDSD_LINEARSIZE;
815 }
816 }
817
818 private static int computeCompressedBlockSize(final int width,
819 final int height,
820 final int depth,
821 final int compressionFormat) {
822 int blockSize = ((width + 3)/4) * ((height + 3)/4) * ((depth + 3)/4);
823 switch (compressionFormat) {
824 case D3DFMT_DXT1: blockSize *= 8; break;
825 default: blockSize *= 16; break;
826 }
827 return blockSize;
828 }
829
830 private static int computeBlockSize(final int width,
831 final int height,
832 final int depth,
833 final int pixelFormat) {
834 int blocksize;
835 switch (pixelFormat) {
836 case D3DFMT_R8G8B8:
837 blocksize = width*height*3;
838 break;
839 case D3DFMT_A8R8G8B8:
840 case D3DFMT_X8R8G8B8:
841 blocksize = width*height*4;
842 break;
843 case D3DFMT_DXT1:
844 case D3DFMT_DXT2:
845 case D3DFMT_DXT3:
846 case D3DFMT_DXT4:
847 case D3DFMT_DXT5:
848 blocksize = computeCompressedBlockSize(width, height, 1, pixelFormat);
849 break;
850 default:
851 throw new IllegalArgumentException("d3dFormat must be one of the known formats");
852 }
853 return blocksize;
854 }
855
856 private int mipMapWidth(final int map) {
857 int width = getWidth();
858 for (int i = 0; i < map; i++) {
859 width >>= 1;
860 }
861 return Math.max(width, 1);
862 }
863
864 private int mipMapHeight(final int map) {
865 int height = getHeight();
866 for (int i = 0; i < map; i++) {
867 height >>= 1;
868 }
869 return Math.max(height, 1);
870 }
871
872 private int mipMapSizeInBytes(final int map) {
873 final int width = mipMapWidth(map);
874 final int height = mipMapHeight(map);
875 if (isCompressed()) {
876 final int blockSize = (getCompressionFormat() == D3DFMT_DXT1 ? 8 : 16);
877 return ((width+3)/4)*((height+3)/4)*blockSize;
878 } else {
879 return width * height * (getDepth() / 8);
880 }
881 }
882
883 private int sideSizeInBytes() {
884 int numLevels = getNumMipMaps();
885 if (numLevels == 0) {
886 numLevels = 1;
887 }
888
889 int size = 0;
890 for (int i = 0; i < numLevels; i++) {
891 size += mipMapSizeInBytes(i);
892 }
893
894 return size;
895 }
896
897 private int sideShiftInBytes(final int side) {
898 final int[] sides = {
905 };
906
907 int shift = 0;
908 final int sideSize = sideSizeInBytes();
909 for (int i = 0; i < sides.length; i++) {
910 final int temp = sides[i];
911 if ((temp & side) != 0) {
912 return shift;
913 }
914
915 shift += sideSize;
916 }
917
918 throw new RuntimeException("Illegal side: " + side);
919 }
920
921 private boolean printIfRecognized(final PrintStream tty, final int flags, final int flag, final String what) {
922 if ((flags & flag) != 0) {
923 tty.println(what);
924 return true;
925 }
926 return false;
927 }
928}
Simple class describing images and data; does not encapsulate image format information.
Definition: DDSImage.java:69
ImageInfo(final ByteBuffer data, final int width, final int height, final boolean compressed, final int compressionFormat)
Definition: DDSImage.java:76
A reader and writer for DirectDraw Surface (.dds) files, which are used to describe textures.
Definition: DDSImage.java:63
int getCompressionFormat()
If this surface is compressed, returns the kind of compression used (DXT1..DXT5).
Definition: DDSImage.java:341
void write(final String filename)
Writes this DDSImage to the specified file name.
Definition: DDSImage.java:245
boolean isSurfaceDescFlagSet(final int flag)
Test for presence/absence of surface description flags (DDSD_*)
Definition: DDSImage.java:275
ImageInfo getMipMap(final int side, final int map)
Gets the ith mipmap data (0..getNumMipMaps() - 1)
Definition: DDSImage.java:385
int getNumMipMaps()
Number of mip maps in the texture.
Definition: DDSImage.java:364
boolean isCompressed()
Indicates whether this texture is compressed.
Definition: DDSImage.java:335
void close()
Closes open files and resources associated with the open DDSImage.
Definition: DDSImage.java:199
static ByteBuffer allocateBlankBuffer(final int width, final int height, final int openGLInternalFormat)
Allocates a temporary, empty ByteBuffer suitable for use in a call to glCompressedTexImage2D.
Definition: DDSImage.java:464
boolean isCubemap()
Indicates whether this texture is cubemap.
Definition: DDSImage.java:321
void write(final File file)
Writes this DDSImage to the specified file name.
Definition: DDSImage.java:254
static DDSImage read(final ByteBuffer buf)
Reads a DirectDraw surface from the specified ByteBuffer, returning the resulting DDSImage.
Definition: DDSImage.java:190
static DDSImage read(final String filename)
Reads a DirectDraw surface from the specified file name, returning the resulting DDSImage.
Definition: DDSImage.java:166
int getWidth()
Width of the texture (or the top-most mipmap if mipmaps are present)
Definition: DDSImage.java:347
int getPixelFormat()
Gets the pixel format of this texture (D3DFMT_*) based on some heuristics.
Definition: DDSImage.java:287
boolean isPixelFormatFlagSet(final int flag)
Test for presence/absence of pixel format flags (DDPF_*)
Definition: DDSImage.java:280
int getDepth()
Total number of bits per pixel.
Definition: DDSImage.java:359
static DDSImage createFromData(final int d3dFormat, final int width, final int height, final ByteBuffer[] mipmapData)
Creates a new DDSImage from data supplied by the user.
Definition: DDSImage.java:231
ImageInfo[] getAllMipMaps(final int side)
Returns an array of ImageInfos corresponding to all mipmap levels of this DDS file.
Definition: DDSImage.java:427
boolean isCubemapSidePresent(final int side)
Indicates whethe this cubemap side present.
Definition: DDSImage.java:330
ImageInfo getMipMap(final int map)
Gets the ith mipmap data (0..getNumMipMaps() - 1)
Definition: DDSImage.java:375
static DDSImage read(final File file)
Reads a DirectDraw surface from the specified file, returning the resulting DDSImage.
Definition: DDSImage.java:177
int getHeight()
Height of the texture (or the top-most mipmap if mipmaps are present)
Definition: DDSImage.java:353
ImageInfo[] getAllMipMaps()
Returns an array of ImageInfos corresponding to all mipmap levels of this DDS file.
Definition: DDSImage.java:417
static String getCompressionFormatName(int compressionFormat)
Converts e.g.
Definition: DDSImage.java:444
static final int GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
GL_EXT_texture_compression_s3tc Define "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT" with expression '0x83F3',...
Definition: GL.java:100
static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
GL_EXT_texture_compression_s3tc Define "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT" with expression '0x83F2',...
Definition: GL.java:47
static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
GL_EXT_texture_compression_s3tc, GL_EXT_texture_compression_dxt1 Define "GL_COMPRESSED_RGBA_S3TC_DXT1...
Definition: GL.java:769
static final int GL_COMPRESSED_RGB_S3TC_DXT1_EXT
GL_EXT_texture_compression_s3tc, GL_EXT_texture_compression_dxt1 Define "GL_COMPRESSED_RGB_S3TC_DXT1_...
Definition: GL.java:647