Home | History | Annotate | Download | only in plugins
      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 
    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