diff options
Diffstat (limited to 'engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java')
-rw-r--r-- | engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java new file mode 100644 index 0000000..bbd51d6 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * <code>TextureManager</code> provides static methods for building a + * <code>Texture</code> object. Typically, the information supplied is the + * filename and the texture properties. + * + * @author Mark Powell + * @author Joshua Slack - cleaned, commented, added ability to read 16bit true color and color-mapped TGAs. + * @author Kirill Vainer - ported to jME3 + * @version $Id: TGALoader.java 4131 2009-03-19 20:15:28Z blaine.dev $ + */ +public final class TGALoader implements AssetLoader { + + // 0 - no image data in file + public static final int TYPE_NO_IMAGE = 0; + + // 1 - uncompressed, color-mapped image + public static final int TYPE_COLORMAPPED = 1; + + // 2 - uncompressed, true-color image + public static final int TYPE_TRUECOLOR = 2; + + // 3 - uncompressed, black and white image + public static final int TYPE_BLACKANDWHITE = 3; + + // 9 - run-length encoded, color-mapped image + public static final int TYPE_COLORMAPPED_RLE = 9; + + // 10 - run-length encoded, true-color image + public static final int TYPE_TRUECOLOR_RLE = 10; + + // 11 - run-length encoded, black and white image + public static final int TYPE_BLACKANDWHITE_RLE = 11; + + public Object load(AssetInfo info) throws IOException{ + if (!(info.getKey() instanceof TextureKey)) + throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); + + boolean flip = ((TextureKey)info.getKey()).isFlipY(); + InputStream in = null; + try { + in = info.openStream(); + Image img = load(in, flip); + return img; + } finally { + if (in != null){ + in.close(); + } + } + } + + /** + * <code>loadImage</code> is a manual image loader which is entirely + * independent of AWT. OUT: RGB888 or RGBA8888 Image object + * + * @return <code>Image</code> object that contains the + * image, either as a RGB888 or RGBA8888 + * @param flip + * Flip the image vertically + * @param exp32 + * Add a dummy Alpha channel to 24b RGB image. + * @param fis + * InputStream of an uncompressed 24b RGB or 32b RGBA TGA + * @throws java.io.IOException + */ + public static Image load(InputStream in, boolean flip) throws IOException { + boolean flipH = false; + + // open a stream to the file + DataInputStream dis = new DataInputStream(new BufferedInputStream(in)); + + // ---------- Start Reading the TGA header ---------- // + // length of the image id (1 byte) + int idLength = dis.readUnsignedByte(); + + // Type of color map (if any) included with the image + // 0 - no color map data is included + // 1 - a color map is included + int colorMapType = dis.readUnsignedByte(); + + // Type of image being read: + int imageType = dis.readUnsignedByte(); + + // Read Color Map Specification (5 bytes) + // Index of first color map entry (if we want to use it, uncomment and remove extra read.) +// short cMapStart = flipEndian(dis.readShort()); + dis.readShort(); + // number of entries in the color map + short cMapLength = flipEndian(dis.readShort()); + // number of bits per color map entry + int cMapDepth = dis.readUnsignedByte(); + + // Read Image Specification (10 bytes) + // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.) +// int xOffset = flipEndian(dis.readShort()); + dis.readShort(); + // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.) +// int yOffset = flipEndian(dis.readShort()); + dis.readShort(); + // width of image - in pixels + int width = flipEndian(dis.readShort()); + // height of image - in pixels + int height = flipEndian(dis.readShort()); + // bits per pixel in image. + int pixelDepth = dis.readUnsignedByte(); + int imageDescriptor = dis.readUnsignedByte(); + if ((imageDescriptor & 32) != 0) // bit 5 : if 1, flip top/bottom ordering + flip = !flip; + if ((imageDescriptor & 16) != 0) // bit 4 : if 1, flip left/right ordering + flipH = !flipH; + + // ---------- Done Reading the TGA header ---------- // + + // Skip image ID + if (idLength > 0) + in.skip(idLength); + + ColorMapEntry[] cMapEntries = null; + if (colorMapType != 0) { + // read the color map. + int bytesInColorMap = (cMapDepth * cMapLength) >> 3; + int bitsPerColor = Math.min(cMapDepth/3 , 8); + + byte[] cMapData = new byte[bytesInColorMap]; + in.read(cMapData); + + // Only go to the trouble of constructing the color map + // table if this is declared a color mapped image. + if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) { + cMapEntries = new ColorMapEntry[cMapLength]; + int alphaSize = cMapDepth - (3*bitsPerColor); + float scalar = 255f / (FastMath.pow(2, bitsPerColor) - 1); + float alphaScalar = 255f / (FastMath.pow(2, alphaSize) - 1); + for (int i = 0; i < cMapLength; i++) { + ColorMapEntry entry = new ColorMapEntry(); + int offset = cMapDepth * i; + entry.red = (byte)(int)(getBitsAsByte(cMapData, offset, bitsPerColor) * scalar); + entry.green = (byte)(int)(getBitsAsByte(cMapData, offset+bitsPerColor, bitsPerColor) * scalar); + entry.blue = (byte)(int)(getBitsAsByte(cMapData, offset+(2*bitsPerColor), bitsPerColor) * scalar); + if (alphaSize <= 0) + entry.alpha = (byte)255; + else + entry.alpha = (byte)(int)(getBitsAsByte(cMapData, offset+(3*bitsPerColor), alphaSize) * alphaScalar); + + cMapEntries[i] = entry; + } + } + } + + + // Allocate image data array + Format format; + byte[] rawData = null; + int dl; + if (pixelDepth == 32) { + rawData = new byte[width * height * 4]; + dl = 4; + } else { + rawData = new byte[width * height * 3]; + dl = 3; + } + int rawDataIndex = 0; + + if (imageType == TYPE_TRUECOLOR) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a seperate loop for each. + if (pixelDepth == 16) { + byte[] data = new byte[2]; + float scalar = 255f/31f; + for (int i = 0; i <= (height - 1); i++) { + if (!flip) + rawDataIndex = (height - 1 - i) * width * dl; + for (int j = 0; j < width; j++) { + data[1] = dis.readByte(); + data[0] = dis.readByte(); + rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 1, 5) * scalar); + rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 6, 5) * scalar); + rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 11, 5) * scalar); + if (dl == 4) { + // create an alpha channel + alpha = getBitsAsByte(data, 0, 1); + if (alpha == 1) alpha = (byte)255; + rawData[rawDataIndex++] = alpha; + } + } + } + + format = dl == 4 ? Format.RGBA8 : Format.RGB8; + } else if (pixelDepth == 24){ + for (int y = 0; y < height; y++) { + if (!flip) + rawDataIndex = (height - 1 - y) * width * dl; + else + rawDataIndex = y * width * dl; + + dis.readFully(rawData, rawDataIndex, width * dl); +// for (int x = 0; x < width; x++) { + //read scanline +// blue = dis.readByte(); +// green = dis.readByte(); +// red = dis.readByte(); +// rawData[rawDataIndex++] = red; +// rawData[rawDataIndex++] = green; +// rawData[rawDataIndex++] = blue; +// } + } + format = Format.BGR8; + } else if (pixelDepth == 32){ + for (int i = 0; i <= (height - 1); i++) { + if (!flip) + rawDataIndex = (height - 1 - i) * width * dl; + + for (int j = 0; j < width; j++) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } + format = Format.RGBA8; + }else{ + throw new IOException("Unsupported TGA true color depth: "+pixelDepth); + } + } else if( imageType == TYPE_TRUECOLOR_RLE ) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a seperate loop for each. + if( pixelDepth == 32 ){ + for( int i = 0; i <= ( height - 1 ); ++i ){ + if( !flip ){ + rawDataIndex = ( height - 1 - i ) * width * dl; + } + + for( int j = 0; j < width; ++j ){ + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if( ( count & 0x80 ) != 0 ){ + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + while( count-- >= 0 ){ + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } else{ + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while( count-- >= 0 ){ + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } + } + } + format = Format.RGBA8; + } else if( pixelDepth == 24 ){ + for( int i = 0; i <= ( height - 1 ); i++ ){ + if( !flip ){ + rawDataIndex = ( height - 1 - i ) * width * dl; + } + for( int j = 0; j < width; ++j ){ + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if( ( count & 0x80 ) != 0 ){ + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + while( count-- >= 0 ){ + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + } + } else{ + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while( count-- >= 0 ){ + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + } + } + } + } + format = Format.RGB8; + } else if( pixelDepth == 16 ){ + byte[] data = new byte[ 2 ]; + float scalar = 255f / 31f; + for( int i = 0; i <= ( height - 1 ); i++ ){ + if( !flip ){ + rawDataIndex = ( height - 1 - i ) * width * dl; + } + for( int j = 0; j < width; j++ ){ + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if( ( count & 0x80 ) != 0 ){ + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + data[1] = dis.readByte(); + data[0] = dis.readByte(); + blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar ); + green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar ); + red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar ); + while( count-- >= 0 ){ + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + } + } else{ + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while( count-- >= 0 ){ + data[1] = dis.readByte(); + data[0] = dis.readByte(); + blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar ); + green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar ); + red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar ); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + } + } + } + } + format = Format.RGB8; + } else{ + throw new IOException( "Unsupported TGA true color depth: " + pixelDepth ); + } + + } else if( imageType == TYPE_COLORMAPPED ){ + int bytesPerIndex = pixelDepth / 8; + + if (bytesPerIndex == 1) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) + rawDataIndex = (height - 1 - i) * width * dl; + for (int j = 0; j < width; j++) { + int index = dis.readUnsignedByte(); + if (index >= cMapEntries.length || index < 0) + throw new IOException("TGA: Invalid color map entry referenced: "+index); + + ColorMapEntry entry = cMapEntries[index]; + rawData[rawDataIndex++] = entry.red; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.blue; + if (dl == 4) { + rawData[rawDataIndex++] = entry.alpha; + } + + } + } + } else if (bytesPerIndex == 2) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) + rawDataIndex = (height - 1 - i) * width * dl; + for (int j = 0; j < width; j++) { + int index = flipEndian(dis.readShort()); + if (index >= cMapEntries.length || index < 0) + throw new IOException("TGA: Invalid color map entry referenced: "+index); + + ColorMapEntry entry = cMapEntries[index]; + rawData[rawDataIndex++] = entry.red; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.blue; + if (dl == 4) { + rawData[rawDataIndex++] = entry.alpha; + } + } + } + } else { + throw new IOException("TGA: unknown colormap indexing size used: "+bytesPerIndex); + } + + format = dl == 4 ? Format.RGBA8 : Format.RGB8; + } else { + throw new IOException("Grayscale TGA not supported"); + } + + + in.close(); + // Get a pointer to the image memory + ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length); + scratch.clear(); + scratch.put(rawData); + scratch.rewind(); + // Create the Image object + Image textureImage = new Image(); + textureImage.setFormat(format); + textureImage.setWidth(width); + textureImage.setHeight(height); + textureImage.setData(scratch); + return textureImage; + } + + private static byte getBitsAsByte(byte[] data, int offset, int length) { + int offsetBytes = offset / 8; + int indexBits = offset % 8; + int rVal = 0; + + // start at data[offsetBytes]... spill into next byte as needed. + for (int i = length; --i >=0;) { + byte b = data[offsetBytes]; + int test = indexBits == 7 ? 1 : 2 << (6-indexBits); + if ((b & test) != 0) { + if (i == 0) + rVal++; + else + rVal += (2 << i-1); + } + indexBits++; + if (indexBits == 8) { + indexBits = 0; + offsetBytes++; + } + } + + return (byte)rVal; + } + + /** + * <code>flipEndian</code> is used to flip the endian bit of the header + * file. + * + * @param signedShort + * the bit to flip. + * @return the flipped bit. + */ + private static short flipEndian(short signedShort) { + int input = signedShort & 0xFFFF; + return (short) (input << 8 | (input & 0xFF00) >>> 8); + } + + static class ColorMapEntry { + byte red, green, blue, alpha; + + @Override + public String toString() { + return "entry: "+red+","+green+","+blue+","+alpha; + } + } +} |