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 package com.jme3.texture.plugins; 33 34 import com.jme3.asset.AssetInfo; 35 import com.jme3.asset.AssetLoader; 36 import com.jme3.asset.TextureKey; 37 import com.jme3.texture.Image; 38 import com.jme3.texture.Image.Format; 39 import com.jme3.texture.Texture.Type; 40 import com.jme3.util.BufferUtils; 41 import com.jme3.util.LittleEndien; 42 import java.io.DataInput; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.nio.ByteBuffer; 46 import java.util.ArrayList; 47 import java.util.logging.Level; 48 import java.util.logging.Logger; 49 50 /** 51 * 52 * <code>DDSLoader</code> is an image loader that reads in a DirectX DDS file. 53 * Supports DXT1, DXT3, DXT5, RGB, RGBA, Grayscale, Alpha pixel formats. 54 * 2D images, mipmapped 2D images, and cubemaps. 55 * 56 * @author Gareth Jenkins-Jones 57 * @author Kirill Vainer 58 * @version $Id: DDSLoader.java,v 2.0 2008/8/15 59 */ 60 public class DDSLoader implements AssetLoader { 61 62 private static final Logger logger = Logger.getLogger(DDSLoader.class.getName()); 63 private static final boolean forceRGBA = false; 64 private static final int DDSD_MANDATORY = 0x1007; 65 private static final int DDSD_MANDATORY_DX10 = 0x6; 66 private static final int DDSD_MIPMAPCOUNT = 0x20000; 67 private static final int DDSD_LINEARSIZE = 0x80000; 68 private static final int DDSD_DEPTH = 0x800000; 69 private static final int DDPF_ALPHAPIXELS = 0x1; 70 private static final int DDPF_FOURCC = 0x4; 71 private static final int DDPF_RGB = 0x40; 72 // used by compressonator to mark grayscale images, red channel mask is used for data and bitcount is 8 73 private static final int DDPF_GRAYSCALE = 0x20000; 74 // used by compressonator to mark alpha images, alpha channel mask is used for data and bitcount is 8 75 private static final int DDPF_ALPHA = 0x2; 76 // used by NVTextureTools to mark normal images. 77 private static final int DDPF_NORMAL = 0x80000000; 78 private static final int SWIZZLE_xGxR = 0x78477852; 79 private static final int DDSCAPS_COMPLEX = 0x8; 80 private static final int DDSCAPS_TEXTURE = 0x1000; 81 private static final int DDSCAPS_MIPMAP = 0x400000; 82 private static final int DDSCAPS2_CUBEMAP = 0x200; 83 private static final int DDSCAPS2_VOLUME = 0x200000; 84 private static final int PF_DXT1 = 0x31545844; 85 private static final int PF_DXT3 = 0x33545844; 86 private static final int PF_DXT5 = 0x35545844; 87 private static final int PF_ATI1 = 0x31495441; 88 private static final int PF_ATI2 = 0x32495441; // 0x41544932; 89 private static final int PF_DX10 = 0x30315844; // a DX10 format 90 private static final int DX10DIM_BUFFER = 0x1, 91 DX10DIM_TEXTURE1D = 0x2, 92 DX10DIM_TEXTURE2D = 0x3, 93 DX10DIM_TEXTURE3D = 0x4; 94 private static final int DX10MISC_GENERATE_MIPS = 0x1, 95 DX10MISC_TEXTURECUBE = 0x4; 96 private static final double LOG2 = Math.log(2); 97 private int width; 98 private int height; 99 private int depth; 100 private int flags; 101 private int pitchOrSize; 102 private int mipMapCount; 103 private int caps1; 104 private int caps2; 105 private boolean directx10; 106 private boolean compressed; 107 private boolean texture3D; 108 private boolean grayscaleOrAlpha; 109 private boolean normal; 110 private Format pixelFormat; 111 private int bpp; 112 private int[] sizes; 113 private int redMask, greenMask, blueMask, alphaMask; 114 private DataInput in; 115 116 public DDSLoader() { 117 } 118 119 public Object load(AssetInfo info) throws IOException { 120 if (!(info.getKey() instanceof TextureKey)) { 121 throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); 122 } 123 124 InputStream stream = null; 125 try { 126 stream = info.openStream(); 127 in = new LittleEndien(stream); 128 loadHeader(); 129 if (texture3D) { 130 ((TextureKey) info.getKey()).setTextureTypeHint(Type.ThreeDimensional); 131 } else if (depth > 1) { 132 ((TextureKey) info.getKey()).setTextureTypeHint(Type.CubeMap); 133 } 134 ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY()); 135 return new Image(pixelFormat, width, height, depth, data, sizes); 136 } finally { 137 if (stream != null){ 138 stream.close(); 139 } 140 } 141 } 142 143 public Image load(InputStream stream) throws IOException { 144 in = new LittleEndien(stream); 145 loadHeader(); 146 ArrayList<ByteBuffer> data = readData(false); 147 return new Image(pixelFormat, width, height, depth, data, sizes); 148 } 149 150 private void loadDX10Header() throws IOException { 151 int dxgiFormat = in.readInt(); 152 if (dxgiFormat != 83) { 153 throw new IOException("Only DXGI_FORMAT_BC5_UNORM " 154 + "is supported for DirectX10 DDS! Got: " + dxgiFormat); 155 } 156 pixelFormat = Format.LATC; 157 bpp = 8; 158 compressed = true; 159 160 int resDim = in.readInt(); 161 if (resDim == DX10DIM_TEXTURE3D) { 162 texture3D = true; 163 } 164 int miscFlag = in.readInt(); 165 int arraySize = in.readInt(); 166 if (is(miscFlag, DX10MISC_TEXTURECUBE)) { 167 // mark texture as cube 168 if (arraySize != 6) { 169 throw new IOException("Cubemaps should consist of 6 images!"); 170 } 171 } 172 173 in.skipBytes(4); // skip reserved value 174 } 175 176 /** 177 * Reads the header (first 128 bytes) of a DDS File 178 */ 179 private void loadHeader() throws IOException { 180 if (in.readInt() != 0x20534444 || in.readInt() != 124) { 181 throw new IOException("Not a DDS file"); 182 } 183 184 flags = in.readInt(); 185 186 if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) { 187 throw new IOException("Mandatory flags missing"); 188 } 189 190 height = in.readInt(); 191 width = in.readInt(); 192 pitchOrSize = in.readInt(); 193 depth = in.readInt(); 194 mipMapCount = in.readInt(); 195 in.skipBytes(44); 196 pixelFormat = null; 197 directx10 = false; 198 readPixelFormat(); 199 caps1 = in.readInt(); 200 caps2 = in.readInt(); 201 in.skipBytes(12); 202 texture3D = false; 203 204 if (!directx10) { 205 if (!is(caps1, DDSCAPS_TEXTURE)) { 206 throw new IOException("File is not a texture"); 207 } 208 209 if (depth <= 0) { 210 depth = 1; 211 } 212 213 if (is(caps2, DDSCAPS2_CUBEMAP)) { 214 depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap 215 } 216 217 if (is(caps2, DDSCAPS2_VOLUME)) { 218 texture3D = true; 219 } 220 } 221 222 int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2); 223 224 if (is(caps1, DDSCAPS_MIPMAP)) { 225 if (!is(flags, DDSD_MIPMAPCOUNT)) { 226 mipMapCount = expectedMipmaps; 227 } else if (mipMapCount != expectedMipmaps) { 228 // changed to warning- images often do not have the required amount, 229 // or specify that they have mipmaps but include only the top level.. 230 logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}", 231 new Object[]{mipMapCount, expectedMipmaps}); 232 } 233 } else { 234 mipMapCount = 1; 235 } 236 237 if (directx10) { 238 loadDX10Header(); 239 } 240 241 loadSizes(); 242 } 243 244 /** 245 * Reads the PixelFormat structure in a DDS file 246 */ 247 private void readPixelFormat() throws IOException { 248 int pfSize = in.readInt(); 249 if (pfSize != 32) { 250 throw new IOException("Pixel format size is " + pfSize + ", not 32"); 251 } 252 253 int pfFlags = in.readInt(); 254 normal = is(pfFlags, DDPF_NORMAL); 255 256 if (is(pfFlags, DDPF_FOURCC)) { 257 compressed = true; 258 int fourcc = in.readInt(); 259 int swizzle = in.readInt(); 260 in.skipBytes(16); 261 262 switch (fourcc) { 263 case PF_DXT1: 264 bpp = 4; 265 if (is(pfFlags, DDPF_ALPHAPIXELS)) { 266 pixelFormat = Image.Format.DXT1A; 267 } else { 268 pixelFormat = Image.Format.DXT1; 269 } 270 break; 271 case PF_DXT3: 272 bpp = 8; 273 pixelFormat = Image.Format.DXT3; 274 break; 275 case PF_DXT5: 276 bpp = 8; 277 pixelFormat = Image.Format.DXT5; 278 if (swizzle == SWIZZLE_xGxR) { 279 normal = true; 280 } 281 break; 282 case PF_ATI1: 283 bpp = 4; 284 pixelFormat = Image.Format.LTC; 285 break; 286 case PF_ATI2: 287 bpp = 8; 288 pixelFormat = Image.Format.LATC; 289 break; 290 case PF_DX10: 291 compressed = false; 292 directx10 = true; 293 // exit here, the rest of the structure is not valid 294 // the real format will be available in the DX10 header 295 return; 296 default: 297 throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc)); 298 } 299 300 int size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2; 301 302 if (is(flags, DDSD_LINEARSIZE)) { 303 if (pitchOrSize == 0) { 304 logger.warning("Must use linear size with fourcc"); 305 pitchOrSize = size; 306 } else if (pitchOrSize != size) { 307 logger.log(Level.WARNING, "Expected size = {0}, real = {1}", 308 new Object[]{size, pitchOrSize}); 309 } 310 } else { 311 pitchOrSize = size; 312 } 313 } else { 314 compressed = false; 315 316 // skip fourCC 317 in.readInt(); 318 319 bpp = in.readInt(); 320 redMask = in.readInt(); 321 greenMask = in.readInt(); 322 blueMask = in.readInt(); 323 alphaMask = in.readInt(); 324 325 if (is(pfFlags, DDPF_RGB)) { 326 if (is(pfFlags, DDPF_ALPHAPIXELS)) { 327 pixelFormat = Format.RGBA8; 328 } else { 329 pixelFormat = Format.RGB8; 330 } 331 } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) { 332 switch (bpp) { 333 case 16: 334 pixelFormat = Format.Luminance8Alpha8; 335 break; 336 case 32: 337 pixelFormat = Format.Luminance16Alpha16; 338 break; 339 default: 340 throw new IOException("Unsupported GrayscaleAlpha BPP: " + bpp); 341 } 342 grayscaleOrAlpha = true; 343 } else if (is(pfFlags, DDPF_GRAYSCALE)) { 344 switch (bpp) { 345 case 8: 346 pixelFormat = Format.Luminance8; 347 break; 348 case 16: 349 pixelFormat = Format.Luminance16; 350 break; 351 default: 352 throw new IOException("Unsupported Grayscale BPP: " + bpp); 353 } 354 grayscaleOrAlpha = true; 355 } else if (is(pfFlags, DDPF_ALPHA)) { 356 switch (bpp) { 357 case 8: 358 pixelFormat = Format.Alpha8; 359 break; 360 case 16: 361 pixelFormat = Format.Alpha16; 362 break; 363 default: 364 throw new IOException("Unsupported Alpha BPP: " + bpp); 365 } 366 grayscaleOrAlpha = true; 367 } else { 368 throw new IOException("Unknown PixelFormat in DDS file"); 369 } 370 371 int size = (bpp / 8 * width); 372 373 if (is(flags, DDSD_LINEARSIZE)) { 374 if (pitchOrSize == 0) { 375 logger.warning("Linear size said to contain valid value but does not"); 376 pitchOrSize = size; 377 } else if (pitchOrSize != size) { 378 logger.log(Level.WARNING, "Expected size = {0}, real = {1}", 379 new Object[]{size, pitchOrSize}); 380 } 381 } else { 382 pitchOrSize = size; 383 } 384 } 385 } 386 387 /** 388 * Computes the sizes of each mipmap level in bytes, and stores it in sizes_[]. 389 */ 390 private void loadSizes() { 391 int mipWidth = width; 392 int mipHeight = height; 393 394 sizes = new int[mipMapCount]; 395 int outBpp = pixelFormat.getBitsPerPixel(); 396 for (int i = 0; i < mipMapCount; i++) { 397 int size; 398 if (compressed) { 399 size = ((mipWidth + 3) / 4) * ((mipHeight + 3) / 4) * outBpp * 2; 400 } else { 401 size = mipWidth * mipHeight * outBpp / 8; 402 } 403 404 sizes[i] = ((size + 3) / 4) * 4; 405 406 mipWidth = Math.max(mipWidth / 2, 1); 407 mipHeight = Math.max(mipHeight / 2, 1); 408 } 409 } 410 411 /** 412 * Flips the given image data on the Y axis. 413 * @param data Data array containing image data (without mipmaps) 414 * @param scanlineSize Size of a single scanline = width * bytesPerPixel 415 * @param height Height of the image in pixels 416 * @return The new data flipped by the Y axis 417 */ 418 public byte[] flipData(byte[] data, int scanlineSize, int height) { 419 byte[] newData = new byte[data.length]; 420 421 for (int y = 0; y < height; y++) { 422 System.arraycopy(data, y * scanlineSize, 423 newData, (height - y - 1) * scanlineSize, 424 scanlineSize); 425 } 426 427 return newData; 428 } 429 430 /** 431 * Reads a grayscale image with mipmaps from the InputStream 432 * @param flip Flip the loaded image by Y axis 433 * @param totalSize Total size of the image in bytes including the mipmaps 434 * @return A ByteBuffer containing the grayscale image data with mips. 435 * @throws java.io.IOException If an error occured while reading from InputStream 436 */ 437 public ByteBuffer readGrayscale2D(boolean flip, int totalSize) throws IOException { 438 ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); 439 440 if (bpp == 8) { 441 logger.finest("Source image format: R8"); 442 } 443 444 assert bpp == pixelFormat.getBitsPerPixel(); 445 446 int mipWidth = width; 447 int mipHeight = height; 448 449 for (int mip = 0; mip < mipMapCount; mip++) { 450 byte[] data = new byte[sizes[mip]]; 451 in.readFully(data); 452 if (flip) { 453 data = flipData(data, mipWidth * bpp / 8, mipHeight); 454 } 455 buffer.put(data); 456 457 mipWidth = Math.max(mipWidth / 2, 1); 458 mipHeight = Math.max(mipHeight / 2, 1); 459 } 460 461 return buffer; 462 } 463 464 /** 465 * Reads an uncompressed RGB or RGBA image. 466 * 467 * @param flip Flip the image on the Y axis 468 * @param totalSize Size of the image in bytes including mipmaps 469 * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_ 470 * @throws java.io.IOException If an error occured while reading from InputStream 471 */ 472 public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException { 473 int redCount = count(redMask), 474 blueCount = count(blueMask), 475 greenCount = count(greenMask), 476 alphaCount = count(alphaMask); 477 478 if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) { 479 if (alphaMask == 0xFF000000 && bpp == 32) { 480 logger.finest("Data source format: BGRA8"); 481 } else if (bpp == 24) { 482 logger.finest("Data source format: BGR8"); 483 } 484 } 485 486 int sourcebytesPP = bpp / 8; 487 int targetBytesPP = pixelFormat.getBitsPerPixel() / 8; 488 489 ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); 490 491 int mipWidth = width; 492 int mipHeight = height; 493 494 int offset = 0; 495 byte[] b = new byte[sourcebytesPP]; 496 for (int mip = 0; mip < mipMapCount; mip++) { 497 for (int y = 0; y < mipHeight; y++) { 498 for (int x = 0; x < mipWidth; x++) { 499 in.readFully(b); 500 501 int i = byte2int(b); 502 503 byte red = (byte) (((i & redMask) >> redCount)); 504 byte green = (byte) (((i & greenMask) >> greenCount)); 505 byte blue = (byte) (((i & blueMask) >> blueCount)); 506 byte alpha = (byte) (((i & alphaMask) >> alphaCount)); 507 508 if (flip) { 509 dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); 510 } 511 //else 512 // dataBuffer.position(offset + (y * width + x) * targetBytesPP); 513 514 if (alphaMask == 0) { 515 dataBuffer.put(red).put(green).put(blue); 516 } else { 517 dataBuffer.put(red).put(green).put(blue).put(alpha); 518 } 519 } 520 } 521 522 offset += mipWidth * mipHeight * targetBytesPP; 523 524 mipWidth = Math.max(mipWidth / 2, 1); 525 mipHeight = Math.max(mipHeight / 2, 1); 526 } 527 528 return dataBuffer; 529 } 530 531 /** 532 * Reads a DXT compressed image from the InputStream 533 * 534 * @param totalSize Total size of the image in bytes, including mipmaps 535 * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ 536 * @throws java.io.IOException If an error occured while reading from InputStream 537 */ 538 public ByteBuffer readDXT2D(boolean flip, int totalSize) throws IOException { 539 logger.finest("Source image format: DXT"); 540 541 ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); 542 543 int mipWidth = width; 544 int mipHeight = height; 545 546 for (int mip = 0; mip < mipMapCount; mip++) { 547 if (flip) { 548 byte[] data = new byte[sizes[mip]]; 549 in.readFully(data); 550 ByteBuffer wrapped = ByteBuffer.wrap(data); 551 wrapped.rewind(); 552 ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat); 553 buffer.put(flipped); 554 } else { 555 byte[] data = new byte[sizes[mip]]; 556 in.readFully(data); 557 buffer.put(data); 558 } 559 560 mipWidth = Math.max(mipWidth / 2, 1); 561 mipHeight = Math.max(mipHeight / 2, 1); 562 } 563 buffer.rewind(); 564 565 return buffer; 566 } 567 568 /** 569 * Reads a grayscale image with mipmaps from the InputStream 570 * @param flip Flip the loaded image by Y axis 571 * @param totalSize Total size of the image in bytes including the mipmaps 572 * @return A ByteBuffer containing the grayscale image data with mips. 573 * @throws java.io.IOException If an error occured while reading from InputStream 574 */ 575 public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException { 576 ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth); 577 578 if (bpp == 8) { 579 logger.finest("Source image format: R8"); 580 } 581 582 assert bpp == pixelFormat.getBitsPerPixel(); 583 584 585 for (int i = 0; i < depth; i++) { 586 int mipWidth = width; 587 int mipHeight = height; 588 589 for (int mip = 0; mip < mipMapCount; mip++) { 590 byte[] data = new byte[sizes[mip]]; 591 in.readFully(data); 592 if (flip) { 593 data = flipData(data, mipWidth * bpp / 8, mipHeight); 594 } 595 buffer.put(data); 596 597 mipWidth = Math.max(mipWidth / 2, 1); 598 mipHeight = Math.max(mipHeight / 2, 1); 599 } 600 } 601 buffer.rewind(); 602 return buffer; 603 } 604 605 /** 606 * Reads an uncompressed RGB or RGBA image. 607 * 608 * @param flip Flip the image on the Y axis 609 * @param totalSize Size of the image in bytes including mipmaps 610 * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_ 611 * @throws java.io.IOException If an error occured while reading from InputStream 612 */ 613 public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException { 614 int redCount = count(redMask), 615 blueCount = count(blueMask), 616 greenCount = count(greenMask), 617 alphaCount = count(alphaMask); 618 619 if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) { 620 if (alphaMask == 0xFF000000 && bpp == 32) { 621 logger.finest("Data source format: BGRA8"); 622 } else if (bpp == 24) { 623 logger.finest("Data source format: BGR8"); 624 } 625 } 626 627 int sourcebytesPP = bpp / 8; 628 int targetBytesPP = pixelFormat.getBitsPerPixel() / 8; 629 630 ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth); 631 632 for (int k = 0; k < depth; k++) { 633 // ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); 634 int mipWidth = width; 635 int mipHeight = height; 636 int offset = k * totalSize; 637 byte[] b = new byte[sourcebytesPP]; 638 for (int mip = 0; mip < mipMapCount; mip++) { 639 for (int y = 0; y < mipHeight; y++) { 640 for (int x = 0; x < mipWidth; x++) { 641 in.readFully(b); 642 643 int i = byte2int(b); 644 645 byte red = (byte) (((i & redMask) >> redCount)); 646 byte green = (byte) (((i & greenMask) >> greenCount)); 647 byte blue = (byte) (((i & blueMask) >> blueCount)); 648 byte alpha = (byte) (((i & alphaMask) >> alphaCount)); 649 650 if (flip) { 651 dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); 652 } 653 //else 654 // dataBuffer.position(offset + (y * width + x) * targetBytesPP); 655 656 if (alphaMask == 0) { 657 dataBuffer.put(red).put(green).put(blue); 658 } else { 659 dataBuffer.put(red).put(green).put(blue).put(alpha); 660 } 661 } 662 } 663 664 offset += (mipWidth * mipHeight * targetBytesPP); 665 666 mipWidth = Math.max(mipWidth / 2, 1); 667 mipHeight = Math.max(mipHeight / 2, 1); 668 } 669 } 670 dataBuffer.rewind(); 671 return dataBuffer; 672 } 673 674 /** 675 * Reads a DXT compressed image from the InputStream 676 * 677 * @param totalSize Total size of the image in bytes, including mipmaps 678 * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ 679 * @throws java.io.IOException If an error occured while reading from InputStream 680 */ 681 public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException { 682 logger.finest("Source image format: DXT"); 683 684 ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth); 685 686 for (int i = 0; i < depth; i++) { 687 ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); 688 int mipWidth = width; 689 int mipHeight = height; 690 for (int mip = 0; mip < mipMapCount; mip++) { 691 if (flip) { 692 byte[] data = new byte[sizes[mip]]; 693 in.readFully(data); 694 ByteBuffer wrapped = ByteBuffer.wrap(data); 695 wrapped.rewind(); 696 ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat); 697 flipped.rewind(); 698 buffer.put(flipped); 699 } else { 700 byte[] data = new byte[sizes[mip]]; 701 in.readFully(data); 702 buffer.put(data); 703 } 704 705 mipWidth = Math.max(mipWidth / 2, 1); 706 mipHeight = Math.max(mipHeight / 2, 1); 707 } 708 buffer.rewind(); 709 bufferAll.put(buffer); 710 } 711 712 return bufferAll; 713 } 714 715 /** 716 * Reads the image data from the InputStream in the required format. 717 * If the file contains a cubemap image, it is loaded as 6 ByteBuffers 718 * (potentially containing mipmaps if they were specified), otherwise 719 * a single ByteBuffer is returned for a 2D image. 720 * 721 * @param flip Flip the image data or not. 722 * For cubemaps, each of the cubemap faces is flipped individually. 723 * If the image is DXT compressed, no flipping is done. 724 * @return An ArrayList containing a single ByteBuffer for a 2D image, or 6 ByteBuffers for a cubemap. 725 * The cubemap ByteBuffer order is PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ. 726 * 727 * @throws java.io.IOException If an error occured while reading from the stream. 728 */ 729 public ArrayList<ByteBuffer> readData(boolean flip) throws IOException { 730 int totalSize = 0; 731 732 for (int i = 0; i < sizes.length; i++) { 733 totalSize += sizes[i]; 734 } 735 736 ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>(); 737 if (depth > 1 && !texture3D) { 738 for (int i = 0; i < depth; i++) { 739 if (compressed) { 740 allMaps.add(readDXT2D(flip, totalSize)); 741 } else if (grayscaleOrAlpha) { 742 allMaps.add(readGrayscale2D(flip, totalSize)); 743 } else { 744 allMaps.add(readRGB2D(flip, totalSize)); 745 } 746 } 747 } else if (texture3D) { 748 if (compressed) { 749 allMaps.add(readDXT3D(flip, totalSize)); 750 } else if (grayscaleOrAlpha) { 751 allMaps.add(readGrayscale3D(flip, totalSize)); 752 } else { 753 allMaps.add(readRGB3D(flip, totalSize)); 754 } 755 756 } else { 757 if (compressed) { 758 allMaps.add(readDXT2D(flip, totalSize)); 759 } else if (grayscaleOrAlpha) { 760 allMaps.add(readGrayscale2D(flip, totalSize)); 761 } else { 762 allMaps.add(readRGB2D(flip, totalSize)); 763 } 764 } 765 766 return allMaps; 767 } 768 769 /** 770 * Checks if flags contains the specified mask 771 */ 772 private static boolean is(int flags, int mask) { 773 return (flags & mask) == mask; 774 } 775 776 /** 777 * Counts the amount of bits needed to shift till bitmask n is at zero 778 * @param n Bitmask to test 779 */ 780 private static int count(int n) { 781 if (n == 0) { 782 return 0; 783 } 784 785 int i = 0; 786 while ((n & 0x1) == 0) { 787 n = n >> 1; 788 i++; 789 if (i > 32) { 790 throw new RuntimeException(Integer.toHexString(n)); 791 } 792 } 793 794 return i; 795 } 796 797 /** 798 * Converts a 1 to 4 sized byte array to an integer 799 */ 800 private static int byte2int(byte[] b) { 801 if (b.length == 1) { 802 return b[0] & 0xFF; 803 } else if (b.length == 2) { 804 return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8); 805 } else if (b.length == 3) { 806 return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16); 807 } else if (b.length == 4) { 808 return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16) | ((b[3] & 0xFF) << 24); 809 } else { 810 return 0; 811 } 812 } 813 814 /** 815 * Converts a int representing a FourCC into a String 816 */ 817 private static String string(int value) { 818 StringBuilder buf = new StringBuilder(); 819 820 buf.append((char) (value & 0xFF)); 821 buf.append((char) ((value & 0xFF00) >> 8)); 822 buf.append((char) ((value & 0xFF0000) >> 16)); 823 buf.append((char) ((value & 0xFF00000) >> 24)); 824 825 return buf.toString(); 826 } 827 } 828