001/**
002 * Copyright (c) 2008-2014 Ardor Labs, Inc.
003 *
004 * This file is part of Ardor3D.
005 *
006 * Ardor3D is free software: you can redistribute it and/or modify it 
007 * under the terms of its license which may be found in the accompanying
008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
009 */
010
011package com.ardor3d.image.util;
012
013import java.io.BufferedInputStream;
014import java.io.DataInputStream;
015import java.io.EOFException;
016import java.io.IOException;
017import java.io.InputStream;
018import java.nio.ByteBuffer;
019
020import com.ardor3d.image.Image;
021import com.ardor3d.image.ImageDataFormat;
022import com.ardor3d.image.PixelDataType;
023import com.ardor3d.util.Ardor3dException;
024import com.ardor3d.util.geom.BufferUtils;
025
026/**
027 * Loads image files in the Targa format. Handles RLE Targa files. Does not handle Targa files in Black-and-White
028 * format.
029 */
030public final class TgaLoader implements ImageLoader {
031
032    // 0 - no image data in file
033    public static final int TYPE_NO_IMAGE = 0;
034
035    // 1 - uncompressed, color-mapped image
036    public static final int TYPE_COLORMAPPED = 1;
037
038    // 2 - uncompressed, true-color image
039    public static final int TYPE_TRUECOLOR = 2;
040
041    // 3 - uncompressed, black and white image
042    public static final int TYPE_BLACKANDWHITE = 3;
043
044    // 9 - run-length encoded, color-mapped image
045    public static final int TYPE_COLORMAPPED_RLE = 9;
046
047    // 10 - run-length encoded, true-color image
048    public static final int TYPE_TRUECOLOR_RLE = 10;
049
050    // 11 - run-length encoded, black and white image
051    public static final int TYPE_BLACKANDWHITE_RLE = 11;
052
053    public TgaLoader() {}
054
055    /**
056     * Load an image from Targa format.
057     * 
058     * @param is
059     *            the input stream delivering the targa data.
060     * @param flip
061     *            if true, we will flip the given targa image on the vertical axis.
062     * @return the new loaded Image.
063     * @throws IOException
064     *             if an error occurs during read.
065     */
066    @Override
067    public Image load(final InputStream is, boolean flip) throws IOException {
068        boolean flipH = false;
069        // open a stream to the file
070        final BufferedInputStream bis = new BufferedInputStream(is, 8192);
071        final DataInputStream dis = new DataInputStream(bis);
072        boolean createAlpha = false;
073
074        // ---------- Start Reading the TGA header ---------- //
075        // length of the image id (1 byte)
076        final int idLength = dis.readUnsignedByte();
077
078        // Type of color map (if any) included with the image
079        // 0 - no color map data is included
080        // 1 - a color map is included
081        final int colorMapType = dis.readUnsignedByte();
082
083        // Type of image being read:
084        final int imageType = dis.readUnsignedByte();
085
086        // Read Color Map Specification (5 bytes)
087        // Index of first color map entry (if we want to use it, uncomment and remove extra read.)
088        // short cMapStart = flipEndian(dis.readShort());
089        dis.readShort();
090        // number of entries in the color map
091        final short cMapLength = flipEndian(dis.readShort());
092        // number of bits per color map entry
093        final int cMapDepth = dis.readUnsignedByte();
094
095        // Read Image Specification (10 bytes)
096        // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
097        // int xOffset = flipEndian(dis.readShort());
098        dis.readShort();
099        // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
100        // int yOffset = flipEndian(dis.readShort());
101        dis.readShort();
102        // width of image - in pixels
103        final int width = flipEndian(dis.readShort());
104        // height of image - in pixels
105        final int height = flipEndian(dis.readShort());
106        // bits per pixel in image.
107        final int pixelDepth = dis.readUnsignedByte();
108        final int imageDescriptor = dis.readUnsignedByte();
109        if ((imageDescriptor & 32) != 0) {
110            flip = !flip;
111        }
112        if ((imageDescriptor & 16) != 0) {
113            flipH = !flipH;
114        }
115
116        // ---------- Done Reading the TGA header ---------- //
117
118        // Skip image ID
119        if (idLength > 0) {
120            if (idLength != bis.skip(idLength)) {
121                throw new IOException("Unexpected number of bytes in file - too few.");
122            }
123        }
124
125        ColorMapEntry[] cMapEntries = null;
126        if (colorMapType != 0) {
127            // read the color map.
128            final int bytesInColorMap = (cMapDepth * cMapLength) >> 3;
129            final int bitsPerColor = Math.min(cMapDepth / 3, 8);
130
131            final byte[] cMapData = new byte[bytesInColorMap];
132            if (-1 == bis.read(cMapData)) {
133                throw new EOFException();
134            }
135
136            // Only go to the trouble of constructing the color map
137            // table if this is declared a color mapped image.
138            if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) {
139                cMapEntries = new ColorMapEntry[cMapLength];
140                final int alphaSize = cMapDepth - (3 * bitsPerColor);
141                final float scalar = 255f / (int) (Math.pow(2, bitsPerColor) - 1);
142                final float alphaScalar = 255f / (int) (Math.pow(2, alphaSize) - 1);
143                for (int i = 0; i < cMapLength; i++) {
144                    final ColorMapEntry entry = new ColorMapEntry();
145                    final int offset = cMapDepth * i;
146                    entry.red = (byte) (int) (getBitsAsByte(cMapData, offset, bitsPerColor) * scalar);
147                    entry.green = (byte) (int) (getBitsAsByte(cMapData, offset + bitsPerColor, bitsPerColor) * scalar);
148                    entry.blue = (byte) (int) (getBitsAsByte(cMapData, offset + (2 * bitsPerColor), bitsPerColor) * scalar);
149                    if (alphaSize <= 0) {
150                        entry.alpha = (byte) 255;
151                    } else {
152                        entry.alpha = (byte) (int) (getBitsAsByte(cMapData, offset + (3 * bitsPerColor), alphaSize) * alphaScalar);
153                    }
154
155                    cMapEntries[i] = entry;
156                }
157            }
158        }
159
160        // Allocate image data array
161        byte[] rawData = null;
162        int dl;
163        if ((pixelDepth == 32)) {
164            rawData = new byte[width * height * 4];
165            dl = 4;
166            createAlpha = true;
167        } else {
168            rawData = new byte[width * height * 3];
169            dl = 3;
170        }
171        int rawDataIndex = 0;
172
173        if (imageType == TYPE_TRUECOLOR) {
174            byte red = 0;
175            byte green = 0;
176            byte blue = 0;
177            byte alpha = 0;
178
179            // Faster than doing a 16-or-24-or-32 check on each individual pixel,
180            // just make a separate loop for each.
181            if (pixelDepth == 16) {
182                final byte[] data = new byte[2];
183                final float scalar = 255f / 31f;
184                for (int i = 0; i <= (height - 1); i++) {
185                    if (!flip) {
186                        rawDataIndex = (height - 1 - i) * width * dl;
187                    }
188                    for (int j = 0; j < width; j++) {
189                        data[1] = dis.readByte();
190                        data[0] = dis.readByte();
191                        rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
192                        rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
193                        rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
194                        if (createAlpha) {
195                            alpha = getBitsAsByte(data, 0, 1);
196                            if (alpha == 1) {
197                                alpha = (byte) 255;
198                            }
199                            rawData[rawDataIndex++] = alpha;
200                        }
201                    }
202                }
203            } else if (pixelDepth == 24) {
204                for (int i = 0; i <= (height - 1); i++) {
205                    if (!flip) {
206                        rawDataIndex = (height - 1 - i) * width * dl;
207                    }
208                    for (int j = 0; j < width; j++) {
209                        blue = dis.readByte();
210                        green = dis.readByte();
211                        red = dis.readByte();
212                        rawData[rawDataIndex++] = red;
213                        rawData[rawDataIndex++] = green;
214                        rawData[rawDataIndex++] = blue;
215                        if (createAlpha) {
216                            rawData[rawDataIndex++] = (byte) 255;
217                        }
218
219                    }
220                }
221            } else if (pixelDepth == 32) {
222                for (int i = 0; i <= (height - 1); i++) {
223                    if (!flip) {
224                        rawDataIndex = (height - 1 - i) * width * dl;
225                    }
226                    for (int j = 0; j < width; j++) {
227                        blue = dis.readByte();
228                        green = dis.readByte();
229                        red = dis.readByte();
230                        alpha = dis.readByte();
231                        rawData[rawDataIndex++] = red;
232                        rawData[rawDataIndex++] = green;
233                        rawData[rawDataIndex++] = blue;
234                        rawData[rawDataIndex++] = alpha;
235                    }
236                }
237            } else {
238                throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth);
239            }
240        } else if (imageType == TYPE_TRUECOLOR_RLE) {
241            byte red = 0;
242            byte green = 0;
243            byte blue = 0;
244            byte alpha = 0;
245
246            // Faster than doing a 16-or-24-or-32 check on each individual pixel,
247            // just make a separate loop for each.
248            if (pixelDepth == 32) {
249                for (int i = 0; i <= (height - 1); ++i) {
250                    if (!flip) {
251                        rawDataIndex = (height - 1 - i) * width * dl;
252                    }
253
254                    for (int j = 0; j < width; ++j) {
255                        // Get the number of pixels the next chunk covers (either packed or unpacked)
256                        int count = dis.readByte();
257                        if ((count & 0x80) != 0) {
258                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
259                            count &= 0x07f;
260                            j += count;
261                            blue = dis.readByte();
262                            green = dis.readByte();
263                            red = dis.readByte();
264                            alpha = dis.readByte();
265                            while (count-- >= 0) {
266                                rawData[rawDataIndex++] = red;
267                                rawData[rawDataIndex++] = green;
268                                rawData[rawDataIndex++] = blue;
269                                rawData[rawDataIndex++] = alpha;
270                            }
271                        } else {
272                            // Its not RLE packed, but the next <count> pixels are raw.
273                            j += count;
274                            while (count-- >= 0) {
275                                blue = dis.readByte();
276                                green = dis.readByte();
277                                red = dis.readByte();
278                                alpha = dis.readByte();
279                                rawData[rawDataIndex++] = red;
280                                rawData[rawDataIndex++] = green;
281                                rawData[rawDataIndex++] = blue;
282                                rawData[rawDataIndex++] = alpha;
283                            }
284                        }
285                    }
286                }
287
288            } else if (pixelDepth == 24) {
289                for (int i = 0; i <= (height - 1); i++) {
290                    if (!flip) {
291                        rawDataIndex = (height - 1 - i) * width * dl;
292                    }
293                    for (int j = 0; j < width; ++j) {
294                        // Get the number of pixels the next chunk covers (either packed or unpacked)
295                        int count = dis.readByte();
296                        if ((count & 0x80) != 0) {
297                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
298                            count &= 0x07f;
299                            j += count;
300                            blue = dis.readByte();
301                            green = dis.readByte();
302                            red = dis.readByte();
303                            while (count-- >= 0) {
304                                rawData[rawDataIndex++] = red;
305                                rawData[rawDataIndex++] = green;
306                                rawData[rawDataIndex++] = blue;
307                                if (createAlpha) {
308                                    rawData[rawDataIndex++] = (byte) 255;
309                                }
310                            }
311                        } else {
312                            // Its not RLE packed, but the next <count> pixels are raw.
313                            j += count;
314                            while (count-- >= 0) {
315                                blue = dis.readByte();
316                                green = dis.readByte();
317                                red = dis.readByte();
318                                rawData[rawDataIndex++] = red;
319                                rawData[rawDataIndex++] = green;
320                                rawData[rawDataIndex++] = blue;
321                                if (createAlpha) {
322                                    rawData[rawDataIndex++] = (byte) 255;
323                                }
324                            }
325                        }
326                    }
327                }
328
329            } else if (pixelDepth == 16) {
330                final byte[] data = new byte[2];
331                final float scalar = 255f / 31f;
332                for (int i = 0; i <= (height - 1); i++) {
333                    if (!flip) {
334                        rawDataIndex = (height - 1 - i) * width * dl;
335                    }
336                    for (int j = 0; j < width; j++) {
337                        // Get the number of pixels the next chunk covers (either packed or unpacked)
338                        int count = dis.readByte();
339                        if ((count & 0x80) != 0) {
340                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
341                            count &= 0x07f;
342                            j += count;
343                            data[1] = dis.readByte();
344                            data[0] = dis.readByte();
345                            blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
346                            green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
347                            red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
348                            while (count-- >= 0) {
349                                rawData[rawDataIndex++] = red;
350                                rawData[rawDataIndex++] = green;
351                                rawData[rawDataIndex++] = blue;
352                                if (createAlpha) {
353                                    rawData[rawDataIndex++] = (byte) 255;
354                                }
355                            }
356                        } else {
357                            // Its not RLE packed, but the next <count> pixels are raw.
358                            j += count;
359                            while (count-- >= 0) {
360                                data[1] = dis.readByte();
361                                data[0] = dis.readByte();
362                                blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
363                                green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
364                                red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
365                                rawData[rawDataIndex++] = red;
366                                rawData[rawDataIndex++] = green;
367                                rawData[rawDataIndex++] = blue;
368                                if (createAlpha) {
369                                    rawData[rawDataIndex++] = (byte) 255;
370                                }
371                            }
372                        }
373                    }
374                }
375
376            } else {
377                throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth);
378            }
379
380        } else if (imageType == TYPE_COLORMAPPED) {
381            if (cMapEntries != null) {
382                final int bytesPerIndex = pixelDepth / 8;
383
384                if (bytesPerIndex == 1) {
385                    for (int i = 0; i <= (height - 1); i++) {
386                        if (!flip) {
387                            rawDataIndex = (height - 1 - i) * width * dl;
388                        }
389                        for (int j = 0; j < width; j++) {
390                            final int index = dis.readUnsignedByte();
391                            if (index >= cMapEntries.length || index < 0) {
392                                throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index);
393                            }
394                            final ColorMapEntry entry = cMapEntries[index];
395                            rawData[rawDataIndex++] = entry.red;
396                            rawData[rawDataIndex++] = entry.green;
397                            rawData[rawDataIndex++] = entry.blue;
398                            if (dl == 4) {
399                                rawData[rawDataIndex++] = entry.alpha;
400                            }
401
402                        }
403                    }
404                } else if (bytesPerIndex == 2) {
405                    for (int i = 0; i <= (height - 1); i++) {
406                        if (!flip) {
407                            rawDataIndex = (height - 1 - i) * width * dl;
408                        }
409                        for (int j = 0; j < width; j++) {
410                            final int index = flipEndian(dis.readShort());
411                            if (index >= cMapEntries.length || index < 0) {
412                                throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index);
413                            }
414                            final ColorMapEntry entry = cMapEntries[index];
415                            rawData[rawDataIndex++] = entry.red;
416                            rawData[rawDataIndex++] = entry.green;
417                            rawData[rawDataIndex++] = entry.blue;
418                            if (dl == 4) {
419                                rawData[rawDataIndex++] = entry.alpha;
420                            }
421                        }
422                    }
423                } else {
424                    throw new Ardor3dException("TGA: unknown colormap indexing size used: " + bytesPerIndex);
425                }
426            }
427        }
428
429        // Get a pointer to the image memory
430        final ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length);
431        scratch.clear();
432        scratch.put(rawData);
433        scratch.rewind();
434        // Create the ardor3d.image.Image object
435        final com.ardor3d.image.Image textureImage = new com.ardor3d.image.Image();
436        if (dl == 4) {
437            textureImage.setDataFormat(ImageDataFormat.RGBA);
438        } else {
439            textureImage.setDataFormat(ImageDataFormat.RGB);
440        }
441        textureImage.setDataType(PixelDataType.UnsignedByte);
442        textureImage.setWidth(width);
443        textureImage.setHeight(height);
444        textureImage.setData(scratch);
445        return textureImage;
446    }
447
448    private static byte getBitsAsByte(final byte[] data, final int offset, final int length) {
449        int offsetBytes = offset / 8;
450        int indexBits = offset % 8;
451        int rVal = 0;
452
453        // start at data[offsetBytes]... spill into next byte as needed.
454        for (int i = length; --i >= 0;) {
455            final byte b = data[offsetBytes];
456            final int test = indexBits == 7 ? 1 : 2 << (6 - indexBits);
457            if ((b & test) != 0) {
458                if (i == 0) {
459                    rVal++;
460                } else {
461                    rVal += (2 << i - 1);
462                }
463            }
464            indexBits++;
465            if (indexBits == 8) {
466                indexBits = 0;
467                offsetBytes++;
468            }
469        }
470
471        return (byte) rVal;
472    }
473
474    /**
475     * <code>flipEndian</code> is used to flip the endian bit of the header file.
476     * 
477     * @param signedShort
478     *            the bit to flip.
479     * @return the flipped bit.
480     */
481    private static short flipEndian(final short signedShort) {
482        final int input = signedShort & 0xFFFF;
483        return (short) (input << 8 | (input & 0xFF00) >>> 8);
484    }
485
486    private static class ColorMapEntry {
487        byte red, green, blue, alpha;
488
489        @Override
490        public String toString() {
491            return "entry: " + red + "," + green + "," + blue + "," + alpha;
492        }
493    }
494}