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}