Home | History | Annotate | Download | only in texture
      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