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