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 */
32package com.jme3.texture.plugins;
33
34import com.jme3.asset.AssetInfo;
35import com.jme3.asset.AssetLoader;
36import com.jme3.asset.TextureKey;
37import com.jme3.texture.Image;
38import com.jme3.texture.Image.Format;
39import com.jme3.texture.Texture.Type;
40import com.jme3.util.BufferUtils;
41import com.jme3.util.LittleEndien;
42import java.io.DataInput;
43import java.io.IOException;
44import java.io.InputStream;
45import java.nio.ByteBuffer;
46import java.util.ArrayList;
47import java.util.logging.Level;
48import 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 */
60public 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
116    public DDSLoader() {
117    }
118
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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