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; 34 35 import com.jme3.export.*; 36 import com.jme3.renderer.Renderer; 37 import com.jme3.util.NativeObject; 38 import java.io.IOException; 39 import java.nio.ByteBuffer; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 44 /** 45 * <code>Image</code> defines a data format for a graphical image. The image 46 * is defined by a format, a height and width, and the image data. The width and 47 * height must be greater than 0. The data is contained in a byte buffer, and 48 * should be packed before creation of the image object. 49 * 50 * @author Mark Powell 51 * @author Joshua Slack 52 * @version $Id: Image.java 4131 2009-03-19 20:15:28Z blaine.dev $ 53 */ 54 public class Image extends NativeObject implements Savable /*, Cloneable*/ { 55 56 public enum Format { 57 /** 58 * 8-bit alpha 59 */ 60 Alpha8(8), 61 62 /** 63 * 16-bit alpha 64 */ 65 Alpha16(16), 66 67 /** 68 * 8-bit grayscale/luminance. 69 */ 70 Luminance8(8), 71 72 /** 73 * 16-bit grayscale/luminance. 74 */ 75 Luminance16(16), 76 77 /** 78 * half-precision floating-point grayscale/luminance. 79 */ 80 Luminance16F(16,true), 81 82 /** 83 * single-precision floating-point grayscale/luminance. 84 */ 85 Luminance32F(32,true), 86 87 /** 88 * 8-bit luminance/grayscale and 8-bit alpha. 89 */ 90 Luminance8Alpha8(16), 91 92 /** 93 * 16-bit luminance/grayscale and 16-bit alpha. 94 */ 95 Luminance16Alpha16(32), 96 97 /** 98 * half-precision floating-point grayscale/luminance and alpha. 99 */ 100 Luminance16FAlpha16F(32,true), 101 102 Intensity8(8), 103 Intensity16(16), 104 105 /** 106 * 8-bit blue, green, and red. 107 */ 108 BGR8(24), // BGR and ABGR formats are often used on windows systems 109 110 /** 111 * 8-bit red, green, and blue. 112 */ 113 RGB8(24), 114 115 /** 116 * 10-bit red, green, and blue. 117 */ 118 RGB10(30), 119 120 /** 121 * 16-bit red, green, and blue. 122 */ 123 RGB16(48), 124 125 /** 126 * 5-bit red, 6-bit green, and 5-bit blue. 127 */ 128 RGB565(16), 129 130 /** 131 * 4-bit alpha, red, green, and blue. Used on Android only. 132 */ 133 ARGB4444(16), 134 135 /** 136 * 5-bit red, green, and blue with 1-bit alpha. 137 */ 138 RGB5A1(16), 139 140 /** 141 * 8-bit red, green, blue, and alpha. 142 */ 143 RGBA8(32), 144 145 /** 146 * 8-bit alpha, blue, green, and red. 147 */ 148 ABGR8(32), 149 150 /** 151 * 16-bit red, green, blue and alpha 152 */ 153 RGBA16(64), 154 155 /** 156 * S3TC compression DXT1. 157 * Called BC1 in DirectX10. 158 */ 159 DXT1(4,false,true, false), 160 161 /** 162 * S3TC compression DXT1 with 1-bit alpha. 163 */ 164 DXT1A(4,false,true, false), 165 166 /** 167 * S3TC compression DXT3 with 4-bit alpha. 168 * Called BC2 in DirectX10. 169 */ 170 DXT3(8,false,true, false), 171 172 /** 173 * S3TC compression DXT5 with interpolated 8-bit alpha. 174 * Called BC3 in DirectX10. 175 */ 176 DXT5(8,false,true, false), 177 178 /** 179 * Luminance-Alpha Texture Compression. 180 * Called BC5 in DirectX10. 181 */ 182 LATC(8, false, true, false), 183 184 /** 185 * Arbitrary depth format. The precision is chosen by the video 186 * hardware. 187 */ 188 Depth(0,true,false,false), 189 190 /** 191 * 16-bit depth. 192 */ 193 Depth16(16,true,false,false), 194 195 /** 196 * 24-bit depth. 197 */ 198 Depth24(24,true,false,false), 199 200 /** 201 * 32-bit depth. 202 */ 203 Depth32(32,true,false,false), 204 205 /** 206 * single-precision floating point depth. 207 */ 208 Depth32F(32,true,false,true), 209 210 /** 211 * Texture data is stored as {@link Format#RGB16F} in system memory, 212 * but will be converted to {@link Format#RGB111110F} when sent 213 * to the video hardware. 214 */ 215 RGB16F_to_RGB111110F(48,true), 216 217 /** 218 * unsigned floating-point red, green and blue that uses 32 bits. 219 */ 220 RGB111110F(32,true), 221 222 /** 223 * Texture data is stored as {@link Format#RGB16F} in system memory, 224 * but will be converted to {@link Format#RGB9E5} when sent 225 * to the video hardware. 226 */ 227 RGB16F_to_RGB9E5(48,true), 228 229 /** 230 * 9-bit red, green and blue with 5-bit exponent. 231 */ 232 RGB9E5(32,true), 233 234 /** 235 * half-precision floating point red, green, and blue. 236 */ 237 RGB16F(48,true), 238 239 /** 240 * half-precision floating point red, green, blue, and alpha. 241 */ 242 RGBA16F(64,true), 243 244 /** 245 * single-precision floating point red, green, and blue. 246 */ 247 RGB32F(96,true), 248 249 /** 250 * single-precision floating point red, green, blue and alpha. 251 */ 252 RGBA32F(128,true), 253 254 /** 255 * Luminance/grayscale texture compression. 256 * Called BC4 in DirectX10. 257 */ 258 LTC(4, false, true, false); 259 260 private int bpp; 261 private boolean isDepth; 262 private boolean isCompressed; 263 private boolean isFloatingPoint; 264 265 private Format(int bpp){ 266 this.bpp = bpp; 267 } 268 269 private Format(int bpp, boolean isFP){ 270 this(bpp); 271 this.isFloatingPoint = isFP; 272 } 273 274 private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){ 275 this(bpp, isFP); 276 this.isDepth = isDepth; 277 this.isCompressed = isCompressed; 278 } 279 280 /** 281 * @return bits per pixel. 282 */ 283 public int getBitsPerPixel(){ 284 return bpp; 285 } 286 287 /** 288 * @return True if this format is a depth format, false otherwise. 289 */ 290 public boolean isDepthFormat(){ 291 return isDepth; 292 } 293 294 /** 295 * @return True if this is a compressed image format, false if 296 * uncompressed. 297 */ 298 public boolean isCompressed() { 299 return isCompressed; 300 } 301 302 /** 303 * @return True if this image format is in floating point, 304 * false if it is an integer format. 305 */ 306 public boolean isFloatingPont(){ 307 return isFloatingPoint; 308 } 309 310 } 311 312 // image attributes 313 protected Format format; 314 protected int width, height, depth; 315 protected int[] mipMapSizes; 316 protected ArrayList<ByteBuffer> data; 317 protected transient Object efficientData; 318 protected int multiSamples = 1; 319 // protected int mipOffset = 0; 320 321 @Override 322 public void resetObject() { 323 this.id = -1; 324 setUpdateNeeded(); 325 } 326 327 @Override 328 public void deleteObject(Object rendererObject) { 329 ((Renderer)rendererObject).deleteImage(this); 330 } 331 332 @Override 333 public NativeObject createDestructableClone() { 334 return new Image(id); 335 } 336 337 /** 338 * @return A shallow clone of this image. The data is not cloned. 339 */ 340 @Override 341 public Image clone(){ 342 Image clone = (Image) super.clone(); 343 clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; 344 clone.data = data != null ? new ArrayList<ByteBuffer>(data) : null; 345 clone.setUpdateNeeded(); 346 return clone; 347 } 348 349 /** 350 * Constructor instantiates a new <code>Image</code> object. All values 351 * are undefined. 352 */ 353 public Image() { 354 super(Image.class); 355 data = new ArrayList<ByteBuffer>(1); 356 } 357 358 protected Image(int id){ 359 super(Image.class, id); 360 } 361 362 /** 363 * Constructor instantiates a new <code>Image</code> object. The 364 * attributes of the image are defined during construction. 365 * 366 * @param format 367 * the data format of the image. 368 * @param width 369 * the width of the image. 370 * @param height 371 * the height of the image. 372 * @param data 373 * the image data. 374 * @param mipMapSizes 375 * the array of mipmap sizes, or null for no mipmaps. 376 */ 377 public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data, 378 int[] mipMapSizes) { 379 380 this(); 381 382 if (mipMapSizes != null && mipMapSizes.length <= 1) { 383 mipMapSizes = null; 384 } 385 386 setFormat(format); 387 this.width = width; 388 this.height = height; 389 this.data = data; 390 this.depth = depth; 391 this.mipMapSizes = mipMapSizes; 392 } 393 394 /** 395 * Constructor instantiates a new <code>Image</code> object. The 396 * attributes of the image are defined during construction. 397 * 398 * @param format 399 * the data format of the image. 400 * @param width 401 * the width of the image. 402 * @param height 403 * the height of the image. 404 * @param data 405 * the image data. 406 * @param mipMapSizes 407 * the array of mipmap sizes, or null for no mipmaps. 408 */ 409 public Image(Format format, int width, int height, ByteBuffer data, 410 int[] mipMapSizes) { 411 412 this(); 413 414 if (mipMapSizes != null && mipMapSizes.length <= 1) { 415 mipMapSizes = null; 416 } 417 418 setFormat(format); 419 this.width = width; 420 this.height = height; 421 if (data != null){ 422 this.data = new ArrayList<ByteBuffer>(1); 423 this.data.add(data); 424 } 425 this.mipMapSizes = mipMapSizes; 426 } 427 428 /** 429 * Constructor instantiates a new <code>Image</code> object. The 430 * attributes of the image are defined during construction. 431 * 432 * @param format 433 * the data format of the image. 434 * @param width 435 * the width of the image. 436 * @param height 437 * the height of the image. 438 * @param data 439 * the image data. 440 */ 441 public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data) { 442 this(format, width, height, depth, data, null); 443 } 444 445 /** 446 * Constructor instantiates a new <code>Image</code> object. The 447 * attributes of the image are defined during construction. 448 * 449 * @param format 450 * the data format of the image. 451 * @param width 452 * the width of the image. 453 * @param height 454 * the height of the image. 455 * @param data 456 * the image data. 457 */ 458 public Image(Format format, int width, int height, ByteBuffer data) { 459 this(format, width, height, data, null); 460 } 461 462 /** 463 * @return The number of samples (for multisampled textures). 464 * @see Image#setMultiSamples(int) 465 */ 466 public int getMultiSamples() { 467 return multiSamples; 468 } 469 470 /** 471 * @param multiSamples Set the number of samples to use for this image, 472 * setting this to a value higher than 1 turns this image/texture 473 * into a multisample texture (on OpenGL3.1 and higher). 474 */ 475 public void setMultiSamples(int multiSamples) { 476 if (multiSamples <= 0) 477 throw new IllegalArgumentException("multiSamples must be > 0"); 478 479 if (getData(0) != null) 480 throw new IllegalArgumentException("Cannot upload data as multisample texture"); 481 482 if (hasMipmaps()) 483 throw new IllegalArgumentException("Multisample textures do not support mipmaps"); 484 485 this.multiSamples = multiSamples; 486 } 487 488 /** 489 * <code>setData</code> sets the data that makes up the image. This data 490 * is packed into an array of <code>ByteBuffer</code> objects. 491 * 492 * @param data 493 * the data that contains the image information. 494 */ 495 public void setData(ArrayList<ByteBuffer> data) { 496 this.data = data; 497 setUpdateNeeded(); 498 } 499 500 /** 501 * <code>setData</code> sets the data that makes up the image. This data 502 * is packed into a single <code>ByteBuffer</code>. 503 * 504 * @param data 505 * the data that contains the image information. 506 */ 507 public void setData(ByteBuffer data) { 508 this.data = new ArrayList<ByteBuffer>(1); 509 this.data.add(data); 510 setUpdateNeeded(); 511 } 512 513 public void addData(ByteBuffer data) { 514 if (this.data == null) 515 this.data = new ArrayList<ByteBuffer>(1); 516 this.data.add(data); 517 setUpdateNeeded(); 518 } 519 520 public void setData(int index, ByteBuffer data) { 521 if (index >= 0) { 522 while (this.data.size() <= index) { 523 this.data.add(null); 524 } 525 this.data.set(index, data); 526 setUpdateNeeded(); 527 } else { 528 throw new IllegalArgumentException("index must be greater than or equal to 0."); 529 } 530 } 531 532 /** 533 * Set the efficient data representation of this image. 534 * <p> 535 * Some system implementations are more efficient at operating 536 * on data other than ByteBuffers, in that case, this method can be used. 537 * 538 * @param efficientData 539 */ 540 public void setEfficentData(Object efficientData){ 541 this.efficientData = efficientData; 542 setUpdateNeeded(); 543 } 544 545 /** 546 * @return The efficient data representation of this image. 547 * @see Image#setEfficentData(java.lang.Object) 548 */ 549 public Object getEfficentData(){ 550 return efficientData; 551 } 552 553 /** 554 * Sets the mipmap sizes stored in this image's data buffer. Mipmaps are 555 * stored sequentially, and the first mipmap is the main image data. To 556 * specify no mipmaps, pass null and this will automatically be expanded 557 * into a single mipmap of the full 558 * 559 * @param mipMapSizes 560 * the mipmap sizes array, or null for a single image map. 561 */ 562 public void setMipMapSizes(int[] mipMapSizes) { 563 if (mipMapSizes != null && mipMapSizes.length <= 1) 564 mipMapSizes = null; 565 566 this.mipMapSizes = mipMapSizes; 567 setUpdateNeeded(); 568 } 569 570 /** 571 * <code>setHeight</code> sets the height value of the image. It is 572 * typically a good idea to try to keep this as a multiple of 2. 573 * 574 * @param height 575 * the height of the image. 576 */ 577 public void setHeight(int height) { 578 this.height = height; 579 setUpdateNeeded(); 580 } 581 582 /** 583 * <code>setDepth</code> sets the depth value of the image. It is 584 * typically a good idea to try to keep this as a multiple of 2. This is 585 * used for 3d images. 586 * 587 * @param depth 588 * the depth of the image. 589 */ 590 public void setDepth(int depth) { 591 this.depth = depth; 592 setUpdateNeeded(); 593 } 594 595 /** 596 * <code>setWidth</code> sets the width value of the image. It is 597 * typically a good idea to try to keep this as a multiple of 2. 598 * 599 * @param width 600 * the width of the image. 601 */ 602 public void setWidth(int width) { 603 this.width = width; 604 setUpdateNeeded(); 605 } 606 607 /** 608 * <code>setFormat</code> sets the image format for this image. 609 * 610 * @param format 611 * the image format. 612 * @throws NullPointerException 613 * if format is null 614 * @see Format 615 */ 616 public void setFormat(Format format) { 617 if (format == null) { 618 throw new NullPointerException("format may not be null."); 619 } 620 621 this.format = format; 622 setUpdateNeeded(); 623 } 624 625 /** 626 * <code>getFormat</code> returns the image format for this image. 627 * 628 * @return the image format. 629 * @see Format 630 */ 631 public Format getFormat() { 632 return format; 633 } 634 635 /** 636 * <code>getWidth</code> returns the width of this image. 637 * 638 * @return the width of this image. 639 */ 640 public int getWidth() { 641 return width; 642 } 643 644 /** 645 * <code>getHeight</code> returns the height of this image. 646 * 647 * @return the height of this image. 648 */ 649 public int getHeight() { 650 return height; 651 } 652 653 /** 654 * <code>getDepth</code> returns the depth of this image (for 3d images). 655 * 656 * @return the depth of this image. 657 */ 658 public int getDepth() { 659 return depth; 660 } 661 662 /** 663 * <code>getData</code> returns the data for this image. If the data is 664 * undefined, null will be returned. 665 * 666 * @return the data for this image. 667 */ 668 public List<ByteBuffer> getData() { 669 return data; 670 } 671 672 /** 673 * <code>getData</code> returns the data for this image. If the data is 674 * undefined, null will be returned. 675 * 676 * @return the data for this image. 677 */ 678 public ByteBuffer getData(int index) { 679 if (data.size() > index) 680 return data.get(index); 681 else 682 return null; 683 } 684 685 /** 686 * Returns whether the image data contains mipmaps. 687 * 688 * @return true if the image data contains mipmaps, false if not. 689 */ 690 public boolean hasMipmaps() { 691 return mipMapSizes != null; 692 } 693 694 /** 695 * Returns the mipmap sizes for this image. 696 * 697 * @return the mipmap sizes for this image. 698 */ 699 public int[] getMipMapSizes() { 700 return mipMapSizes; 701 } 702 703 @Override 704 public String toString(){ 705 StringBuilder sb = new StringBuilder(); 706 sb.append(getClass().getSimpleName()); 707 sb.append("[size=").append(width).append("x").append(height); 708 709 if (depth > 1) 710 sb.append("x").append(depth); 711 712 sb.append(", format=").append(format.name()); 713 714 if (hasMipmaps()) 715 sb.append(", mips"); 716 717 if (getId() >= 0) 718 sb.append(", id=").append(id); 719 720 sb.append("]"); 721 722 return sb.toString(); 723 } 724 725 @Override 726 public boolean equals(Object other) { 727 if (other == this) { 728 return true; 729 } 730 if (!(other instanceof Image)) { 731 return false; 732 } 733 Image that = (Image) other; 734 if (this.getFormat() != that.getFormat()) 735 return false; 736 if (this.getWidth() != that.getWidth()) 737 return false; 738 if (this.getHeight() != that.getHeight()) 739 return false; 740 if (this.getData() != null && !this.getData().equals(that.getData())) 741 return false; 742 if (this.getData() == null && that.getData() != null) 743 return false; 744 if (this.getMipMapSizes() != null 745 && !Arrays.equals(this.getMipMapSizes(), that.getMipMapSizes())) 746 return false; 747 if (this.getMipMapSizes() == null && that.getMipMapSizes() != null) 748 return false; 749 if (this.getMultiSamples() != that.getMultiSamples()) 750 return false; 751 752 return true; 753 } 754 755 @Override 756 public int hashCode() { 757 int hash = 7; 758 hash = 97 * hash + (this.format != null ? this.format.hashCode() : 0); 759 hash = 97 * hash + this.width; 760 hash = 97 * hash + this.height; 761 hash = 97 * hash + this.depth; 762 hash = 97 * hash + Arrays.hashCode(this.mipMapSizes); 763 hash = 97 * hash + (this.data != null ? this.data.hashCode() : 0); 764 hash = 97 * hash + this.multiSamples; 765 return hash; 766 } 767 768 public void write(JmeExporter e) throws IOException { 769 OutputCapsule capsule = e.getCapsule(this); 770 capsule.write(format, "format", Format.RGBA8); 771 capsule.write(width, "width", 0); 772 capsule.write(height, "height", 0); 773 capsule.write(depth, "depth", 0); 774 capsule.write(mipMapSizes, "mipMapSizes", null); 775 capsule.write(multiSamples, "multiSamples", 1); 776 capsule.writeByteBufferArrayList(data, "data", null); 777 } 778 779 public void read(JmeImporter e) throws IOException { 780 InputCapsule capsule = e.getCapsule(this); 781 format = capsule.readEnum("format", Format.class, Format.RGBA8); 782 width = capsule.readInt("width", 0); 783 height = capsule.readInt("height", 0); 784 depth = capsule.readInt("depth", 0); 785 mipMapSizes = capsule.readIntArray("mipMapSizes", null); 786 multiSamples = capsule.readInt("multiSamples", 1); 787 data = (ArrayList<ByteBuffer>) capsule.readByteBufferArrayList("data", null); 788 } 789 790 } 791