• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
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 
load(AssetInfo info)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      */
load(InputStream in, boolean flip)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 
getBitsAsByte(byte[] data, int offset, int length)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      */
flipEndian(short signedShort)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
toString()513         public String toString() {
514             return "entry: "+red+","+green+","+blue+","+alpha;
515         }
516     }
517 }
518