• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package com.jme3.texture.plugins;
33 
34 import com.jme3.asset.AssetInfo;
35 import com.jme3.asset.AssetLoader;
36 import com.jme3.asset.TextureKey;
37 import com.jme3.texture.Image;
38 import com.jme3.texture.Image.Format;
39 import com.jme3.texture.Texture.Type;
40 import com.jme3.util.BufferUtils;
41 import com.jme3.util.LittleEndien;
42 import java.io.DataInput;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.nio.ByteBuffer;
46 import java.util.ArrayList;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49 
50 /**
51  *
52  * <code>DDSLoader</code> is an image loader that reads in a DirectX DDS file.
53  * Supports DXT1, DXT3, DXT5, RGB, RGBA, Grayscale, Alpha pixel formats.
54  * 2D images, mipmapped 2D images, and cubemaps.
55  *
56  * @author Gareth Jenkins-Jones
57  * @author Kirill Vainer
58  * @version $Id: DDSLoader.java,v 2.0 2008/8/15
59  */
60 public class DDSLoader implements AssetLoader {
61 
62     private static final Logger logger = Logger.getLogger(DDSLoader.class.getName());
63     private static final boolean forceRGBA = false;
64     private static final int DDSD_MANDATORY = 0x1007;
65     private static final int DDSD_MANDATORY_DX10 = 0x6;
66     private static final int DDSD_MIPMAPCOUNT = 0x20000;
67     private static final int DDSD_LINEARSIZE = 0x80000;
68     private static final int DDSD_DEPTH = 0x800000;
69     private static final int DDPF_ALPHAPIXELS = 0x1;
70     private static final int DDPF_FOURCC = 0x4;
71     private static final int DDPF_RGB = 0x40;
72     // used by compressonator to mark grayscale images, red channel mask is used for data and bitcount is 8
73     private static final int DDPF_GRAYSCALE = 0x20000;
74     // used by compressonator to mark alpha images, alpha channel mask is used for data and bitcount is 8
75     private static final int DDPF_ALPHA = 0x2;
76     // used by NVTextureTools to mark normal images.
77     private static final int DDPF_NORMAL = 0x80000000;
78     private static final int SWIZZLE_xGxR = 0x78477852;
79     private static final int DDSCAPS_COMPLEX = 0x8;
80     private static final int DDSCAPS_TEXTURE = 0x1000;
81     private static final int DDSCAPS_MIPMAP = 0x400000;
82     private static final int DDSCAPS2_CUBEMAP = 0x200;
83     private static final int DDSCAPS2_VOLUME = 0x200000;
84     private static final int PF_DXT1 = 0x31545844;
85     private static final int PF_DXT3 = 0x33545844;
86     private static final int PF_DXT5 = 0x35545844;
87     private static final int PF_ATI1 = 0x31495441;
88     private static final int PF_ATI2 = 0x32495441; // 0x41544932;
89     private static final int PF_DX10 = 0x30315844; // a DX10 format
90     private static final int DX10DIM_BUFFER = 0x1,
91             DX10DIM_TEXTURE1D = 0x2,
92             DX10DIM_TEXTURE2D = 0x3,
93             DX10DIM_TEXTURE3D = 0x4;
94     private static final int DX10MISC_GENERATE_MIPS = 0x1,
95             DX10MISC_TEXTURECUBE = 0x4;
96     private static final double LOG2 = Math.log(2);
97     private int width;
98     private int height;
99     private int depth;
100     private int flags;
101     private int pitchOrSize;
102     private int mipMapCount;
103     private int caps1;
104     private int caps2;
105     private boolean directx10;
106     private boolean compressed;
107     private boolean texture3D;
108     private boolean grayscaleOrAlpha;
109     private boolean normal;
110     private Format pixelFormat;
111     private int bpp;
112     private int[] sizes;
113     private int redMask, greenMask, blueMask, alphaMask;
114     private DataInput in;
115 
DDSLoader()116     public DDSLoader() {
117     }
118 
load(AssetInfo info)119     public Object load(AssetInfo info) throws IOException {
120         if (!(info.getKey() instanceof TextureKey)) {
121             throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
122         }
123 
124         InputStream stream = null;
125         try {
126             stream = info.openStream();
127             in = new LittleEndien(stream);
128             loadHeader();
129             if (texture3D) {
130                 ((TextureKey) info.getKey()).setTextureTypeHint(Type.ThreeDimensional);
131             } else if (depth > 1) {
132                 ((TextureKey) info.getKey()).setTextureTypeHint(Type.CubeMap);
133             }
134             ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
135             return new Image(pixelFormat, width, height, depth, data, sizes);
136         } finally {
137             if (stream != null){
138                 stream.close();
139             }
140         }
141     }
142 
load(InputStream stream)143     public Image load(InputStream stream) throws IOException {
144         in = new LittleEndien(stream);
145         loadHeader();
146         ArrayList<ByteBuffer> data = readData(false);
147         return new Image(pixelFormat, width, height, depth, data, sizes);
148     }
149 
loadDX10Header()150     private void loadDX10Header() throws IOException {
151         int dxgiFormat = in.readInt();
152         if (dxgiFormat != 83) {
153             throw new IOException("Only DXGI_FORMAT_BC5_UNORM "
154                     + "is supported for DirectX10 DDS! Got: " + dxgiFormat);
155         }
156         pixelFormat = Format.LATC;
157         bpp = 8;
158         compressed = true;
159 
160         int resDim = in.readInt();
161         if (resDim == DX10DIM_TEXTURE3D) {
162             texture3D = true;
163         }
164         int miscFlag = in.readInt();
165         int arraySize = in.readInt();
166         if (is(miscFlag, DX10MISC_TEXTURECUBE)) {
167             // mark texture as cube
168             if (arraySize != 6) {
169                 throw new IOException("Cubemaps should consist of 6 images!");
170             }
171         }
172 
173         in.skipBytes(4); // skip reserved value
174     }
175 
176     /**
177      * Reads the header (first 128 bytes) of a DDS File
178      */
loadHeader()179     private void loadHeader() throws IOException {
180         if (in.readInt() != 0x20534444 || in.readInt() != 124) {
181             throw new IOException("Not a DDS file");
182         }
183 
184         flags = in.readInt();
185 
186         if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) {
187             throw new IOException("Mandatory flags missing");
188         }
189 
190         height = in.readInt();
191         width = in.readInt();
192         pitchOrSize = in.readInt();
193         depth = in.readInt();
194         mipMapCount = in.readInt();
195         in.skipBytes(44);
196         pixelFormat = null;
197         directx10 = false;
198         readPixelFormat();
199         caps1 = in.readInt();
200         caps2 = in.readInt();
201         in.skipBytes(12);
202         texture3D = false;
203 
204         if (!directx10) {
205             if (!is(caps1, DDSCAPS_TEXTURE)) {
206                 throw new IOException("File is not a texture");
207             }
208 
209             if (depth <= 0) {
210                 depth = 1;
211             }
212 
213             if (is(caps2, DDSCAPS2_CUBEMAP)) {
214                 depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap
215             }
216 
217             if (is(caps2, DDSCAPS2_VOLUME)) {
218                 texture3D = true;
219             }
220         }
221 
222         int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2);
223 
224         if (is(caps1, DDSCAPS_MIPMAP)) {
225             if (!is(flags, DDSD_MIPMAPCOUNT)) {
226                 mipMapCount = expectedMipmaps;
227             } else if (mipMapCount != expectedMipmaps) {
228                 // changed to warning- images often do not have the required amount,
229                 // or specify that they have mipmaps but include only the top level..
230                 logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}",
231                         new Object[]{mipMapCount, expectedMipmaps});
232             }
233         } else {
234             mipMapCount = 1;
235         }
236 
237         if (directx10) {
238             loadDX10Header();
239         }
240 
241         loadSizes();
242     }
243 
244     /**
245      * Reads the PixelFormat structure in a DDS file
246      */
readPixelFormat()247     private void readPixelFormat() throws IOException {
248         int pfSize = in.readInt();
249         if (pfSize != 32) {
250             throw new IOException("Pixel format size is " + pfSize + ", not 32");
251         }
252 
253         int pfFlags = in.readInt();
254         normal = is(pfFlags, DDPF_NORMAL);
255 
256         if (is(pfFlags, DDPF_FOURCC)) {
257             compressed = true;
258             int fourcc = in.readInt();
259             int swizzle = in.readInt();
260             in.skipBytes(16);
261 
262             switch (fourcc) {
263                 case PF_DXT1:
264                     bpp = 4;
265                     if (is(pfFlags, DDPF_ALPHAPIXELS)) {
266                         pixelFormat = Image.Format.DXT1A;
267                     } else {
268                         pixelFormat = Image.Format.DXT1;
269                     }
270                     break;
271                 case PF_DXT3:
272                     bpp = 8;
273                     pixelFormat = Image.Format.DXT3;
274                     break;
275                 case PF_DXT5:
276                     bpp = 8;
277                     pixelFormat = Image.Format.DXT5;
278                     if (swizzle == SWIZZLE_xGxR) {
279                         normal = true;
280                     }
281                     break;
282                 case PF_ATI1:
283                     bpp = 4;
284                     pixelFormat = Image.Format.LTC;
285                     break;
286                 case PF_ATI2:
287                     bpp = 8;
288                     pixelFormat = Image.Format.LATC;
289                     break;
290                 case PF_DX10:
291                     compressed = false;
292                     directx10 = true;
293                     // exit here, the rest of the structure is not valid
294                     // the real format will be available in the DX10 header
295                     return;
296                 default:
297                     throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
298             }
299 
300             int size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2;
301 
302             if (is(flags, DDSD_LINEARSIZE)) {
303                 if (pitchOrSize == 0) {
304                     logger.warning("Must use linear size with fourcc");
305                     pitchOrSize = size;
306                 } else if (pitchOrSize != size) {
307                     logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
308                             new Object[]{size, pitchOrSize});
309                 }
310             } else {
311                 pitchOrSize = size;
312             }
313         } else {
314             compressed = false;
315 
316             // skip fourCC
317             in.readInt();
318 
319             bpp = in.readInt();
320             redMask = in.readInt();
321             greenMask = in.readInt();
322             blueMask = in.readInt();
323             alphaMask = in.readInt();
324 
325             if (is(pfFlags, DDPF_RGB)) {
326                 if (is(pfFlags, DDPF_ALPHAPIXELS)) {
327                     pixelFormat = Format.RGBA8;
328                 } else {
329                     pixelFormat = Format.RGB8;
330                 }
331             } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) {
332                 switch (bpp) {
333                     case 16:
334                         pixelFormat = Format.Luminance8Alpha8;
335                         break;
336                     case 32:
337                         pixelFormat = Format.Luminance16Alpha16;
338                         break;
339                     default:
340                         throw new IOException("Unsupported GrayscaleAlpha BPP: " + bpp);
341                 }
342                 grayscaleOrAlpha = true;
343             } else if (is(pfFlags, DDPF_GRAYSCALE)) {
344                 switch (bpp) {
345                     case 8:
346                         pixelFormat = Format.Luminance8;
347                         break;
348                     case 16:
349                         pixelFormat = Format.Luminance16;
350                         break;
351                     default:
352                         throw new IOException("Unsupported Grayscale BPP: " + bpp);
353                 }
354                 grayscaleOrAlpha = true;
355             } else if (is(pfFlags, DDPF_ALPHA)) {
356                 switch (bpp) {
357                     case 8:
358                         pixelFormat = Format.Alpha8;
359                         break;
360                     case 16:
361                         pixelFormat = Format.Alpha16;
362                         break;
363                     default:
364                         throw new IOException("Unsupported Alpha BPP: " + bpp);
365                 }
366                 grayscaleOrAlpha = true;
367             } else {
368                 throw new IOException("Unknown PixelFormat in DDS file");
369             }
370 
371             int size = (bpp / 8 * width);
372 
373             if (is(flags, DDSD_LINEARSIZE)) {
374                 if (pitchOrSize == 0) {
375                     logger.warning("Linear size said to contain valid value but does not");
376                     pitchOrSize = size;
377                 } else if (pitchOrSize != size) {
378                     logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
379                             new Object[]{size, pitchOrSize});
380                 }
381             } else {
382                 pitchOrSize = size;
383             }
384         }
385     }
386 
387     /**
388      * Computes the sizes of each mipmap level in bytes, and stores it in sizes_[].
389      */
loadSizes()390     private void loadSizes() {
391         int mipWidth = width;
392         int mipHeight = height;
393 
394         sizes = new int[mipMapCount];
395         int outBpp = pixelFormat.getBitsPerPixel();
396         for (int i = 0; i < mipMapCount; i++) {
397             int size;
398             if (compressed) {
399                 size = ((mipWidth + 3) / 4) * ((mipHeight + 3) / 4) * outBpp * 2;
400             } else {
401                 size = mipWidth * mipHeight * outBpp / 8;
402             }
403 
404             sizes[i] = ((size + 3) / 4) * 4;
405 
406             mipWidth = Math.max(mipWidth / 2, 1);
407             mipHeight = Math.max(mipHeight / 2, 1);
408         }
409     }
410 
411     /**
412      * Flips the given image data on the Y axis.
413      * @param data Data array containing image data (without mipmaps)
414      * @param scanlineSize Size of a single scanline = width * bytesPerPixel
415      * @param height Height of the image in pixels
416      * @return The new data flipped by the Y axis
417      */
flipData(byte[] data, int scanlineSize, int height)418     public byte[] flipData(byte[] data, int scanlineSize, int height) {
419         byte[] newData = new byte[data.length];
420 
421         for (int y = 0; y < height; y++) {
422             System.arraycopy(data, y * scanlineSize,
423                     newData, (height - y - 1) * scanlineSize,
424                     scanlineSize);
425         }
426 
427         return newData;
428     }
429 
430     /**
431      * Reads a grayscale image with mipmaps from the InputStream
432      * @param flip Flip the loaded image by Y axis
433      * @param totalSize Total size of the image in bytes including the mipmaps
434      * @return A ByteBuffer containing the grayscale image data with mips.
435      * @throws java.io.IOException If an error occured while reading from InputStream
436      */
readGrayscale2D(boolean flip, int totalSize)437     public ByteBuffer readGrayscale2D(boolean flip, int totalSize) throws IOException {
438         ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
439 
440         if (bpp == 8) {
441             logger.finest("Source image format: R8");
442         }
443 
444         assert bpp == pixelFormat.getBitsPerPixel();
445 
446         int mipWidth = width;
447         int mipHeight = height;
448 
449         for (int mip = 0; mip < mipMapCount; mip++) {
450             byte[] data = new byte[sizes[mip]];
451             in.readFully(data);
452             if (flip) {
453                 data = flipData(data, mipWidth * bpp / 8, mipHeight);
454             }
455             buffer.put(data);
456 
457             mipWidth = Math.max(mipWidth / 2, 1);
458             mipHeight = Math.max(mipHeight / 2, 1);
459         }
460 
461         return buffer;
462     }
463 
464     /**
465      * Reads an uncompressed RGB or RGBA image.
466      *
467      * @param flip Flip the image on the Y axis
468      * @param totalSize Size of the image in bytes including mipmaps
469      * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
470      * @throws java.io.IOException If an error occured while reading from InputStream
471      */
readRGB2D(boolean flip, int totalSize)472     public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException {
473         int redCount = count(redMask),
474                 blueCount = count(blueMask),
475                 greenCount = count(greenMask),
476                 alphaCount = count(alphaMask);
477 
478         if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
479             if (alphaMask == 0xFF000000 && bpp == 32) {
480                 logger.finest("Data source format: BGRA8");
481             } else if (bpp == 24) {
482                 logger.finest("Data source format: BGR8");
483             }
484         }
485 
486         int sourcebytesPP = bpp / 8;
487         int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
488 
489         ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
490 
491         int mipWidth = width;
492         int mipHeight = height;
493 
494         int offset = 0;
495         byte[] b = new byte[sourcebytesPP];
496         for (int mip = 0; mip < mipMapCount; mip++) {
497             for (int y = 0; y < mipHeight; y++) {
498                 for (int x = 0; x < mipWidth; x++) {
499                     in.readFully(b);
500 
501                     int i = byte2int(b);
502 
503                     byte red = (byte) (((i & redMask) >> redCount));
504                     byte green = (byte) (((i & greenMask) >> greenCount));
505                     byte blue = (byte) (((i & blueMask) >> blueCount));
506                     byte alpha = (byte) (((i & alphaMask) >> alphaCount));
507 
508                     if (flip) {
509                         dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
510                     }
511                     //else
512                     //    dataBuffer.position(offset + (y * width + x) * targetBytesPP);
513 
514                     if (alphaMask == 0) {
515                         dataBuffer.put(red).put(green).put(blue);
516                     } else {
517                         dataBuffer.put(red).put(green).put(blue).put(alpha);
518                     }
519                 }
520             }
521 
522             offset += mipWidth * mipHeight * targetBytesPP;
523 
524             mipWidth = Math.max(mipWidth / 2, 1);
525             mipHeight = Math.max(mipHeight / 2, 1);
526         }
527 
528         return dataBuffer;
529     }
530 
531     /**
532      * Reads a DXT compressed image from the InputStream
533      *
534      * @param totalSize Total size of the image in bytes, including mipmaps
535      * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
536      * @throws java.io.IOException If an error occured while reading from InputStream
537      */
readDXT2D(boolean flip, int totalSize)538     public ByteBuffer readDXT2D(boolean flip, int totalSize) throws IOException {
539         logger.finest("Source image format: DXT");
540 
541         ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
542 
543         int mipWidth = width;
544         int mipHeight = height;
545 
546         for (int mip = 0; mip < mipMapCount; mip++) {
547             if (flip) {
548                 byte[] data = new byte[sizes[mip]];
549                 in.readFully(data);
550                 ByteBuffer wrapped = ByteBuffer.wrap(data);
551                 wrapped.rewind();
552                 ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
553                 buffer.put(flipped);
554             } else {
555                 byte[] data = new byte[sizes[mip]];
556                 in.readFully(data);
557                 buffer.put(data);
558             }
559 
560             mipWidth = Math.max(mipWidth / 2, 1);
561             mipHeight = Math.max(mipHeight / 2, 1);
562         }
563         buffer.rewind();
564 
565         return buffer;
566     }
567 
568     /**
569      * Reads a grayscale image with mipmaps from the InputStream
570      * @param flip Flip the loaded image by Y axis
571      * @param totalSize Total size of the image in bytes including the mipmaps
572      * @return A ByteBuffer containing the grayscale image data with mips.
573      * @throws java.io.IOException If an error occured while reading from InputStream
574      */
readGrayscale3D(boolean flip, int totalSize)575     public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException {
576         ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
577 
578         if (bpp == 8) {
579             logger.finest("Source image format: R8");
580         }
581 
582         assert bpp == pixelFormat.getBitsPerPixel();
583 
584 
585         for (int i = 0; i < depth; i++) {
586             int mipWidth = width;
587             int mipHeight = height;
588 
589             for (int mip = 0; mip < mipMapCount; mip++) {
590                 byte[] data = new byte[sizes[mip]];
591                 in.readFully(data);
592                 if (flip) {
593                     data = flipData(data, mipWidth * bpp / 8, mipHeight);
594                 }
595                 buffer.put(data);
596 
597                 mipWidth = Math.max(mipWidth / 2, 1);
598                 mipHeight = Math.max(mipHeight / 2, 1);
599             }
600         }
601         buffer.rewind();
602         return buffer;
603     }
604 
605     /**
606      * Reads an uncompressed RGB or RGBA image.
607      *
608      * @param flip Flip the image on the Y axis
609      * @param totalSize Size of the image in bytes including mipmaps
610      * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
611      * @throws java.io.IOException If an error occured while reading from InputStream
612      */
readRGB3D(boolean flip, int totalSize)613     public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException {
614         int redCount = count(redMask),
615                 blueCount = count(blueMask),
616                 greenCount = count(greenMask),
617                 alphaCount = count(alphaMask);
618 
619         if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
620             if (alphaMask == 0xFF000000 && bpp == 32) {
621                 logger.finest("Data source format: BGRA8");
622             } else if (bpp == 24) {
623                 logger.finest("Data source format: BGR8");
624             }
625         }
626 
627         int sourcebytesPP = bpp / 8;
628         int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
629 
630         ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth);
631 
632         for (int k = 0; k < depth; k++) {
633             //   ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
634             int mipWidth = width;
635             int mipHeight = height;
636             int offset = k * totalSize;
637             byte[] b = new byte[sourcebytesPP];
638             for (int mip = 0; mip < mipMapCount; mip++) {
639                 for (int y = 0; y < mipHeight; y++) {
640                     for (int x = 0; x < mipWidth; x++) {
641                         in.readFully(b);
642 
643                         int i = byte2int(b);
644 
645                         byte red = (byte) (((i & redMask) >> redCount));
646                         byte green = (byte) (((i & greenMask) >> greenCount));
647                         byte blue = (byte) (((i & blueMask) >> blueCount));
648                         byte alpha = (byte) (((i & alphaMask) >> alphaCount));
649 
650                         if (flip) {
651                             dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
652                         }
653                         //else
654                         //    dataBuffer.position(offset + (y * width + x) * targetBytesPP);
655 
656                         if (alphaMask == 0) {
657                             dataBuffer.put(red).put(green).put(blue);
658                         } else {
659                             dataBuffer.put(red).put(green).put(blue).put(alpha);
660                         }
661                     }
662                 }
663 
664                 offset += (mipWidth * mipHeight * targetBytesPP);
665 
666                 mipWidth = Math.max(mipWidth / 2, 1);
667                 mipHeight = Math.max(mipHeight / 2, 1);
668             }
669         }
670         dataBuffer.rewind();
671         return dataBuffer;
672     }
673 
674     /**
675      * Reads a DXT compressed image from the InputStream
676      *
677      * @param totalSize Total size of the image in bytes, including mipmaps
678      * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
679      * @throws java.io.IOException If an error occured while reading from InputStream
680      */
readDXT3D(boolean flip, int totalSize)681     public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException {
682         logger.finest("Source image format: DXT");
683 
684         ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth);
685 
686         for (int i = 0; i < depth; i++) {
687             ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
688             int mipWidth = width;
689             int mipHeight = height;
690             for (int mip = 0; mip < mipMapCount; mip++) {
691                 if (flip) {
692                     byte[] data = new byte[sizes[mip]];
693                     in.readFully(data);
694                     ByteBuffer wrapped = ByteBuffer.wrap(data);
695                     wrapped.rewind();
696                     ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
697                     flipped.rewind();
698                     buffer.put(flipped);
699                 } else {
700                     byte[] data = new byte[sizes[mip]];
701                     in.readFully(data);
702                     buffer.put(data);
703                 }
704 
705                 mipWidth = Math.max(mipWidth / 2, 1);
706                 mipHeight = Math.max(mipHeight / 2, 1);
707             }
708             buffer.rewind();
709             bufferAll.put(buffer);
710         }
711 
712         return bufferAll;
713     }
714 
715     /**
716      * Reads the image data from the InputStream in the required format.
717      * If the file contains a cubemap image, it is loaded as 6 ByteBuffers
718      * (potentially containing mipmaps if they were specified), otherwise
719      * a single ByteBuffer is returned for a 2D image.
720      *
721      * @param flip Flip the image data or not.
722      *        For cubemaps, each of the cubemap faces is flipped individually.
723      *        If the image is DXT compressed, no flipping is done.
724      * @return An ArrayList containing a single ByteBuffer for a 2D image, or 6 ByteBuffers for a cubemap.
725      *         The cubemap ByteBuffer order is PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ.
726      *
727      * @throws java.io.IOException If an error occured while reading from the stream.
728      */
readData(boolean flip)729     public ArrayList<ByteBuffer> readData(boolean flip) throws IOException {
730         int totalSize = 0;
731 
732         for (int i = 0; i < sizes.length; i++) {
733             totalSize += sizes[i];
734         }
735 
736         ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
737         if (depth > 1 && !texture3D) {
738             for (int i = 0; i < depth; i++) {
739                 if (compressed) {
740                     allMaps.add(readDXT2D(flip, totalSize));
741                 } else if (grayscaleOrAlpha) {
742                     allMaps.add(readGrayscale2D(flip, totalSize));
743                 } else {
744                     allMaps.add(readRGB2D(flip, totalSize));
745                 }
746             }
747         } else if (texture3D) {
748             if (compressed) {
749                 allMaps.add(readDXT3D(flip, totalSize));
750             } else if (grayscaleOrAlpha) {
751                 allMaps.add(readGrayscale3D(flip, totalSize));
752             } else {
753                 allMaps.add(readRGB3D(flip, totalSize));
754             }
755 
756         } else {
757             if (compressed) {
758                 allMaps.add(readDXT2D(flip, totalSize));
759             } else if (grayscaleOrAlpha) {
760                 allMaps.add(readGrayscale2D(flip, totalSize));
761             } else {
762                 allMaps.add(readRGB2D(flip, totalSize));
763             }
764         }
765 
766         return allMaps;
767     }
768 
769     /**
770      * Checks if flags contains the specified mask
771      */
is(int flags, int mask)772     private static boolean is(int flags, int mask) {
773         return (flags & mask) == mask;
774     }
775 
776     /**
777      * Counts the amount of bits needed to shift till bitmask n is at zero
778      * @param n Bitmask to test
779      */
count(int n)780     private static int count(int n) {
781         if (n == 0) {
782             return 0;
783         }
784 
785         int i = 0;
786         while ((n & 0x1) == 0) {
787             n = n >> 1;
788             i++;
789             if (i > 32) {
790                 throw new RuntimeException(Integer.toHexString(n));
791             }
792         }
793 
794         return i;
795     }
796 
797     /**
798      * Converts a 1 to 4 sized byte array to an integer
799      */
byte2int(byte[] b)800     private static int byte2int(byte[] b) {
801         if (b.length == 1) {
802             return b[0] & 0xFF;
803         } else if (b.length == 2) {
804             return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8);
805         } else if (b.length == 3) {
806             return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16);
807         } else if (b.length == 4) {
808             return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16) | ((b[3] & 0xFF) << 24);
809         } else {
810             return 0;
811         }
812     }
813 
814     /**
815      * Converts a int representing a FourCC into a String
816      */
string(int value)817     private static String string(int value) {
818         StringBuilder buf = new StringBuilder();
819 
820         buf.append((char) (value & 0xFF));
821         buf.append((char) ((value & 0xFF00) >> 8));
822         buf.append((char) ((value & 0xFF0000) >> 16));
823         buf.append((char) ((value & 0xFF00000) >> 24));
824 
825         return buf.toString();
826     }
827 }
828