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 
     33 package com.jme3.texture.plugins;
     34 
     35 import com.jme3.asset.AssetInfo;
     36 import com.jme3.asset.AssetLoader;
     37 import com.jme3.asset.TextureKey;
     38 import com.jme3.math.FastMath;
     39 import com.jme3.texture.Image;
     40 import com.jme3.texture.Image.Format;
     41 import com.jme3.util.BufferUtils;
     42 import java.io.BufferedInputStream;
     43 import java.io.DataInputStream;
     44 import java.io.IOException;
     45 import java.io.InputStream;
     46 import java.nio.ByteBuffer;
     47 
     48 /**
     49  * <code>TextureManager</code> provides static methods for building a
     50  * <code>Texture</code> object. Typically, the information supplied is the
     51  * filename and the texture properties.
     52  *
     53  * @author Mark Powell
     54  * @author Joshua Slack - cleaned, commented, added ability to read 16bit true color and color-mapped TGAs.
     55  * @author Kirill Vainer - ported to jME3
     56  * @version $Id: TGALoader.java 4131 2009-03-19 20:15:28Z blaine.dev $
     57  */
     58 public final class TGALoader implements AssetLoader {
     59 
     60     // 0 - no image data in file
     61     public static final int TYPE_NO_IMAGE = 0;
     62 
     63     // 1 - uncompressed, color-mapped image
     64     public static final int TYPE_COLORMAPPED = 1;
     65 
     66     // 2 - uncompressed, true-color image
     67     public static final int TYPE_TRUECOLOR = 2;
     68 
     69     // 3 - uncompressed, black and white image
     70     public static final int TYPE_BLACKANDWHITE = 3;
     71 
     72     // 9 - run-length encoded, color-mapped image
     73     public static final int TYPE_COLORMAPPED_RLE = 9;
     74 
     75     // 10 - run-length encoded, true-color image
     76     public static final int TYPE_TRUECOLOR_RLE = 10;
     77 
     78     // 11 - run-length encoded, black and white image
     79     public static final int TYPE_BLACKANDWHITE_RLE = 11;
     80 
     81     public Object load(AssetInfo info) throws IOException{
     82         if (!(info.getKey() instanceof TextureKey))
     83             throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
     84 
     85         boolean flip = ((TextureKey)info.getKey()).isFlipY();
     86         InputStream in = null;
     87         try {
     88             in = info.openStream();
     89             Image img = load(in, flip);
     90             return img;
     91         } finally {
     92             if (in != null){
     93                 in.close();
     94             }
     95         }
     96     }
     97 
     98     /**
     99      * <code>loadImage</code> is a manual image loader which is entirely
    100      * independent of AWT. OUT: RGB888 or RGBA8888 Image object
    101      *
    102      * @return <code>Image</code> object that contains the
    103      *         image, either as a RGB888 or RGBA8888
    104      * @param flip
    105      *            Flip the image vertically
    106      * @param exp32
    107      *            Add a dummy Alpha channel to 24b RGB image.
    108      * @param fis
    109      *            InputStream of an uncompressed 24b RGB or 32b RGBA TGA
    110      * @throws java.io.IOException
    111      */
    112     public static Image load(InputStream in, boolean flip) throws IOException {
    113         boolean flipH = false;
    114 
    115         // open a stream to the file
    116         DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
    117 
    118         // ---------- Start Reading the TGA header ---------- //
    119         // length of the image id (1 byte)
    120         int idLength = dis.readUnsignedByte();
    121 
    122         // Type of color map (if any) included with the image
    123         // 0 - no color map data is included
    124         // 1 - a color map is included
    125         int colorMapType = dis.readUnsignedByte();
    126 
    127         // Type of image being read:
    128         int imageType = dis.readUnsignedByte();
    129 
    130         // Read Color Map Specification (5 bytes)
    131         // Index of first color map entry (if we want to use it, uncomment and remove extra read.)
    132 //        short cMapStart = flipEndian(dis.readShort());
    133         dis.readShort();
    134         // number of entries in the color map
    135         short cMapLength = flipEndian(dis.readShort());
    136         // number of bits per color map entry
    137         int cMapDepth = dis.readUnsignedByte();
    138 
    139         // Read Image Specification (10 bytes)
    140         // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
    141 //        int xOffset = flipEndian(dis.readShort());
    142         dis.readShort();
    143         // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
    144 //        int yOffset = flipEndian(dis.readShort());
    145         dis.readShort();
    146         // width of image - in pixels
    147         int width = flipEndian(dis.readShort());
    148         // height of image - in pixels
    149         int height = flipEndian(dis.readShort());
    150         // bits per pixel in image.
    151         int pixelDepth = dis.readUnsignedByte();
    152         int imageDescriptor = dis.readUnsignedByte();
    153         if ((imageDescriptor & 32) != 0) // bit 5 : if 1, flip top/bottom ordering
    154             flip = !flip;
    155         if ((imageDescriptor & 16) != 0) // bit 4 : if 1, flip left/right ordering
    156             flipH = !flipH;
    157 
    158         // ---------- Done Reading the TGA header ---------- //
    159 
    160         // Skip image ID
    161         if (idLength > 0)
    162             in.skip(idLength);
    163 
    164         ColorMapEntry[] cMapEntries = null;
    165         if (colorMapType != 0) {
    166             // read the color map.
    167             int bytesInColorMap = (cMapDepth * cMapLength) >> 3;
    168             int bitsPerColor = Math.min(cMapDepth/3 , 8);
    169 
    170             byte[] cMapData = new byte[bytesInColorMap];
    171             in.read(cMapData);
    172 
    173             // Only go to the trouble of constructing the color map
    174             // table if this is declared a color mapped image.
    175             if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) {
    176                 cMapEntries = new ColorMapEntry[cMapLength];
    177                 int alphaSize = cMapDepth - (3*bitsPerColor);
    178                 float scalar = 255f / (FastMath.pow(2, bitsPerColor) - 1);
    179                 float alphaScalar = 255f / (FastMath.pow(2, alphaSize) - 1);
    180                 for (int i = 0; i < cMapLength; i++) {
    181                     ColorMapEntry entry = new ColorMapEntry();
    182                     int offset = cMapDepth * i;
    183                     entry.red = (byte)(int)(getBitsAsByte(cMapData, offset, bitsPerColor) * scalar);
    184                     entry.green = (byte)(int)(getBitsAsByte(cMapData, offset+bitsPerColor, bitsPerColor) * scalar);
    185                     entry.blue = (byte)(int)(getBitsAsByte(cMapData, offset+(2*bitsPerColor), bitsPerColor) * scalar);
    186                     if (alphaSize <= 0)
    187                         entry.alpha = (byte)255;
    188                     else
    189                         entry.alpha = (byte)(int)(getBitsAsByte(cMapData, offset+(3*bitsPerColor), alphaSize) * alphaScalar);
    190 
    191                     cMapEntries[i] = entry;
    192                 }
    193             }
    194         }
    195 
    196 
    197         // Allocate image data array
    198         Format format;
    199         byte[] rawData = null;
    200         int dl;
    201         if (pixelDepth == 32) {
    202             rawData = new byte[width * height * 4];
    203             dl = 4;
    204         } else {
    205             rawData = new byte[width * height * 3];
    206             dl = 3;
    207         }
    208         int rawDataIndex = 0;
    209 
    210         if (imageType == TYPE_TRUECOLOR) {
    211             byte red = 0;
    212             byte green = 0;
    213             byte blue = 0;
    214             byte alpha = 0;
    215 
    216             // Faster than doing a 16-or-24-or-32 check on each individual pixel,
    217             // just make a seperate loop for each.
    218             if (pixelDepth == 16) {
    219                 byte[] data = new byte[2];
    220                 float scalar = 255f/31f;
    221                 for (int i = 0; i <= (height - 1); i++) {
    222                     if (!flip)
    223                         rawDataIndex = (height - 1 - i) * width * dl;
    224                     for (int j = 0; j < width; j++) {
    225                         data[1] = dis.readByte();
    226                         data[0] = dis.readByte();
    227                         rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 1, 5) * scalar);
    228                         rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 6, 5) * scalar);
    229                         rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 11, 5) * scalar);
    230                         if (dl == 4) {
    231                             // create an alpha channel
    232                             alpha = getBitsAsByte(data, 0, 1);
    233                             if (alpha == 1) alpha = (byte)255;
    234                             rawData[rawDataIndex++] = alpha;
    235                         }
    236                     }
    237                 }
    238 
    239                 format = dl == 4 ? Format.RGBA8 : Format.RGB8;
    240             } else if (pixelDepth == 24){
    241                 for (int y = 0; y < height; y++) {
    242                     if (!flip)
    243                         rawDataIndex = (height - 1 - y) * width * dl;
    244                     else
    245                         rawDataIndex = y * width * dl;
    246 
    247                     dis.readFully(rawData, rawDataIndex, width * dl);
    248 //                    for (int x = 0; x < width; x++) {
    249                         //read scanline
    250 //                        blue = dis.readByte();
    251 //                        green = dis.readByte();
    252 //                        red = dis.readByte();
    253 //                        rawData[rawDataIndex++] = red;
    254 //                        rawData[rawDataIndex++] = green;
    255 //                        rawData[rawDataIndex++] = blue;
    256 //                    }
    257                 }
    258                 format = Format.BGR8;
    259             } else if (pixelDepth == 32){
    260                 for (int i = 0; i <= (height - 1); i++) {
    261                     if (!flip)
    262                         rawDataIndex = (height - 1 - i) * width * dl;
    263 
    264                     for (int j = 0; j < width; j++) {
    265                         blue = dis.readByte();
    266                         green = dis.readByte();
    267                         red = dis.readByte();
    268                         alpha = dis.readByte();
    269                         rawData[rawDataIndex++] = red;
    270                         rawData[rawDataIndex++] = green;
    271                         rawData[rawDataIndex++] = blue;
    272                         rawData[rawDataIndex++] = alpha;
    273                     }
    274                 }
    275                 format = Format.RGBA8;
    276             }else{
    277                 throw new IOException("Unsupported TGA true color depth: "+pixelDepth);
    278             }
    279         } else if( imageType == TYPE_TRUECOLOR_RLE ) {
    280             byte red = 0;
    281             byte green = 0;
    282             byte blue = 0;
    283             byte alpha = 0;
    284             // Faster than doing a 16-or-24-or-32 check on each individual pixel,
    285             // just make a seperate loop for each.
    286             if( pixelDepth == 32 ){
    287                 for( int i = 0; i <= ( height - 1 ); ++i ){
    288                     if( !flip ){
    289                         rawDataIndex = ( height - 1 - i ) * width * dl;
    290                     }
    291 
    292                     for( int j = 0; j < width; ++j ){
    293                         // Get the number of pixels the next chunk covers (either packed or unpacked)
    294                         int count = dis.readByte();
    295                         if( ( count & 0x80 ) != 0 ){
    296                             // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
    297                             count &= 0x07f;
    298                             j += count;
    299                             blue = dis.readByte();
    300                             green = dis.readByte();
    301                             red = dis.readByte();
    302                             alpha = dis.readByte();
    303                             while( count-- >= 0 ){
    304                                 rawData[rawDataIndex++] = red;
    305                                 rawData[rawDataIndex++] = green;
    306                                 rawData[rawDataIndex++] = blue;
    307                                 rawData[rawDataIndex++] = alpha;
    308                             }
    309                         } else{
    310                             // Its not RLE packed, but the next <count> pixels are raw.
    311                             j += count;
    312                             while( count-- >= 0 ){
    313                                 blue = dis.readByte();
    314                                 green = dis.readByte();
    315                                 red = dis.readByte();
    316                                 alpha = dis.readByte();
    317                                 rawData[rawDataIndex++] = red;
    318                                 rawData[rawDataIndex++] = green;
    319                                 rawData[rawDataIndex++] = blue;
    320                                 rawData[rawDataIndex++] = alpha;
    321                             }
    322                         }
    323                     }
    324                 }
    325                 format = Format.RGBA8;
    326             } else if( pixelDepth == 24 ){
    327                 for( int i = 0; i <= ( height - 1 ); i++ ){
    328                     if( !flip ){
    329                         rawDataIndex = ( height - 1 - i ) * width * dl;
    330                     }
    331                     for( int j = 0; j < width; ++j ){
    332                         // Get the number of pixels the next chunk covers (either packed or unpacked)
    333                         int count = dis.readByte();
    334                         if( ( count & 0x80 ) != 0 ){
    335                             // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
    336                             count &= 0x07f;
    337                             j += count;
    338                             blue = dis.readByte();
    339                             green = dis.readByte();
    340                             red = dis.readByte();
    341                             while( count-- >= 0 ){
    342                                 rawData[rawDataIndex++] = red;
    343                                 rawData[rawDataIndex++] = green;
    344                                 rawData[rawDataIndex++] = blue;
    345                             }
    346                         } else{
    347                             // Its not RLE packed, but the next <count> pixels are raw.
    348                             j += count;
    349                             while( count-- >= 0 ){
    350                                 blue = dis.readByte();
    351                                 green = dis.readByte();
    352                                 red = dis.readByte();
    353                                 rawData[rawDataIndex++] = red;
    354                                 rawData[rawDataIndex++] = green;
    355                                 rawData[rawDataIndex++] = blue;
    356                             }
    357                         }
    358                     }
    359                 }
    360                 format = Format.RGB8;
    361             } else if( pixelDepth == 16 ){
    362                 byte[] data = new byte[ 2 ];
    363                 float scalar = 255f / 31f;
    364                 for( int i = 0; i <= ( height - 1 ); i++ ){
    365                     if( !flip ){
    366                         rawDataIndex = ( height - 1 - i ) * width * dl;
    367                     }
    368                     for( int j = 0; j < width; j++ ){
    369                         // Get the number of pixels the next chunk covers (either packed or unpacked)
    370                         int count = dis.readByte();
    371                         if( ( count & 0x80 ) != 0 ){
    372                             // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
    373                             count &= 0x07f;
    374                             j += count;
    375                             data[1] = dis.readByte();
    376                             data[0] = dis.readByte();
    377                             blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar );
    378                             green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar );
    379                             red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar );
    380                             while( count-- >= 0 ){
    381                                 rawData[rawDataIndex++] = red;
    382                                 rawData[rawDataIndex++] = green;
    383                                 rawData[rawDataIndex++] = blue;
    384                             }
    385                         } else{
    386                             // Its not RLE packed, but the next <count> pixels are raw.
    387                             j += count;
    388                             while( count-- >= 0 ){
    389                                 data[1] = dis.readByte();
    390                                 data[0] = dis.readByte();
    391                                 blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar );
    392                                 green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar );
    393                                 red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar );
    394                                 rawData[rawDataIndex++] = red;
    395                                 rawData[rawDataIndex++] = green;
    396                                 rawData[rawDataIndex++] = blue;
    397                             }
    398                         }
    399                     }
    400                 }
    401                 format = Format.RGB8;
    402             } else{
    403                 throw new IOException( "Unsupported TGA true color depth: " + pixelDepth );
    404             }
    405 
    406         } else if( imageType == TYPE_COLORMAPPED ){
    407             int bytesPerIndex = pixelDepth / 8;
    408 
    409             if (bytesPerIndex == 1) {
    410                 for (int i = 0; i <= (height - 1); i++) {
    411                     if (!flip)
    412                         rawDataIndex = (height - 1 - i) * width * dl;
    413                     for (int j = 0; j < width; j++) {
    414                         int index = dis.readUnsignedByte();
    415                         if (index >= cMapEntries.length || index < 0)
    416                             throw new IOException("TGA: Invalid color map entry referenced: "+index);
    417 
    418                         ColorMapEntry entry = cMapEntries[index];
    419                         rawData[rawDataIndex++] = entry.red;
    420                         rawData[rawDataIndex++] = entry.green;
    421                         rawData[rawDataIndex++] = entry.blue;
    422                         if (dl == 4) {
    423                             rawData[rawDataIndex++] = entry.alpha;
    424                         }
    425 
    426                     }
    427                 }
    428             } else if (bytesPerIndex == 2) {
    429                 for (int i = 0; i <= (height - 1); i++) {
    430                     if (!flip)
    431                         rawDataIndex = (height - 1 - i) * width * dl;
    432                     for (int j = 0; j < width; j++) {
    433                         int index = flipEndian(dis.readShort());
    434                         if (index >= cMapEntries.length || index < 0)
    435                             throw new IOException("TGA: Invalid color map entry referenced: "+index);
    436 
    437                         ColorMapEntry entry = cMapEntries[index];
    438                         rawData[rawDataIndex++] = entry.red;
    439                         rawData[rawDataIndex++] = entry.green;
    440                         rawData[rawDataIndex++] = entry.blue;
    441                         if (dl == 4) {
    442                             rawData[rawDataIndex++] = entry.alpha;
    443                         }
    444                     }
    445                 }
    446             } else {
    447                 throw new IOException("TGA: unknown colormap indexing size used: "+bytesPerIndex);
    448             }
    449 
    450             format = dl == 4 ? Format.RGBA8 : Format.RGB8;
    451         } else {
    452             throw new IOException("Grayscale TGA not supported");
    453         }
    454 
    455 
    456         in.close();
    457         // Get a pointer to the image memory
    458         ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length);
    459         scratch.clear();
    460         scratch.put(rawData);
    461         scratch.rewind();
    462         // Create the Image object
    463         Image textureImage = new Image();
    464         textureImage.setFormat(format);
    465         textureImage.setWidth(width);
    466         textureImage.setHeight(height);
    467         textureImage.setData(scratch);
    468         return textureImage;
    469     }
    470 
    471     private static byte getBitsAsByte(byte[] data, int offset, int length) {
    472         int offsetBytes = offset / 8;
    473         int indexBits = offset % 8;
    474         int rVal = 0;
    475 
    476         // start at data[offsetBytes]...  spill into next byte as needed.
    477         for (int i = length; --i >=0;) {
    478             byte b = data[offsetBytes];
    479             int test = indexBits == 7 ? 1 : 2 << (6-indexBits);
    480             if ((b & test) != 0) {
    481                 if (i == 0)
    482                     rVal++;
    483                 else
    484                     rVal += (2 << i-1);
    485             }
    486             indexBits++;
    487             if (indexBits == 8) {
    488                 indexBits = 0;
    489                 offsetBytes++;
    490             }
    491         }
    492 
    493         return (byte)rVal;
    494     }
    495 
    496     /**
    497      * <code>flipEndian</code> is used to flip the endian bit of the header
    498      * file.
    499      *
    500      * @param signedShort
    501      *            the bit to flip.
    502      * @return the flipped bit.
    503      */
    504     private static short flipEndian(short signedShort) {
    505         int input = signedShort & 0xFFFF;
    506         return (short) (input << 8 | (input & 0xFF00) >>> 8);
    507     }
    508 
    509     static class ColorMapEntry {
    510         byte red, green, blue, alpha;
    511 
    512         @Override
    513         public String toString() {
    514             return "entry: "+red+","+green+","+blue+","+alpha;
    515         }
    516     }
    517 }
    518