/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.texture; import com.jme3.export.*; import com.jme3.renderer.Renderer; import com.jme3.util.NativeObject; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Image defines a data format for a graphical image. The image * is defined by a format, a height and width, and the image data. The width and * height must be greater than 0. The data is contained in a byte buffer, and * should be packed before creation of the image object. * * @author Mark Powell * @author Joshua Slack * @version $Id: Image.java 4131 2009-03-19 20:15:28Z blaine.dev $ */ public class Image extends NativeObject implements Savable /*, Cloneable*/ { public enum Format { /** * 8-bit alpha */ Alpha8(8), /** * 16-bit alpha */ Alpha16(16), /** * 8-bit grayscale/luminance. */ Luminance8(8), /** * 16-bit grayscale/luminance. */ Luminance16(16), /** * half-precision floating-point grayscale/luminance. */ Luminance16F(16,true), /** * single-precision floating-point grayscale/luminance. */ Luminance32F(32,true), /** * 8-bit luminance/grayscale and 8-bit alpha. */ Luminance8Alpha8(16), /** * 16-bit luminance/grayscale and 16-bit alpha. */ Luminance16Alpha16(32), /** * half-precision floating-point grayscale/luminance and alpha. */ Luminance16FAlpha16F(32,true), Intensity8(8), Intensity16(16), /** * 8-bit blue, green, and red. */ BGR8(24), // BGR and ABGR formats are often used on windows systems /** * 8-bit red, green, and blue. */ RGB8(24), /** * 10-bit red, green, and blue. */ RGB10(30), /** * 16-bit red, green, and blue. */ RGB16(48), /** * 5-bit red, 6-bit green, and 5-bit blue. */ RGB565(16), /** * 4-bit alpha, red, green, and blue. Used on Android only. */ ARGB4444(16), /** * 5-bit red, green, and blue with 1-bit alpha. */ RGB5A1(16), /** * 8-bit red, green, blue, and alpha. */ RGBA8(32), /** * 8-bit alpha, blue, green, and red. */ ABGR8(32), /** * 16-bit red, green, blue and alpha */ RGBA16(64), /** * S3TC compression DXT1. * Called BC1 in DirectX10. */ DXT1(4,false,true, false), /** * S3TC compression DXT1 with 1-bit alpha. */ DXT1A(4,false,true, false), /** * S3TC compression DXT3 with 4-bit alpha. * Called BC2 in DirectX10. */ DXT3(8,false,true, false), /** * S3TC compression DXT5 with interpolated 8-bit alpha. * Called BC3 in DirectX10. */ DXT5(8,false,true, false), /** * Luminance-Alpha Texture Compression. * Called BC5 in DirectX10. */ LATC(8, false, true, false), /** * Arbitrary depth format. The precision is chosen by the video * hardware. */ Depth(0,true,false,false), /** * 16-bit depth. */ Depth16(16,true,false,false), /** * 24-bit depth. */ Depth24(24,true,false,false), /** * 32-bit depth. */ Depth32(32,true,false,false), /** * single-precision floating point depth. */ Depth32F(32,true,false,true), /** * Texture data is stored as {@link Format#RGB16F} in system memory, * but will be converted to {@link Format#RGB111110F} when sent * to the video hardware. */ RGB16F_to_RGB111110F(48,true), /** * unsigned floating-point red, green and blue that uses 32 bits. */ RGB111110F(32,true), /** * Texture data is stored as {@link Format#RGB16F} in system memory, * but will be converted to {@link Format#RGB9E5} when sent * to the video hardware. */ RGB16F_to_RGB9E5(48,true), /** * 9-bit red, green and blue with 5-bit exponent. */ RGB9E5(32,true), /** * half-precision floating point red, green, and blue. */ RGB16F(48,true), /** * half-precision floating point red, green, blue, and alpha. */ RGBA16F(64,true), /** * single-precision floating point red, green, and blue. */ RGB32F(96,true), /** * single-precision floating point red, green, blue and alpha. */ RGBA32F(128,true), /** * Luminance/grayscale texture compression. * Called BC4 in DirectX10. */ LTC(4, false, true, false); private int bpp; private boolean isDepth; private boolean isCompressed; private boolean isFloatingPoint; private Format(int bpp){ this.bpp = bpp; } private Format(int bpp, boolean isFP){ this(bpp); this.isFloatingPoint = isFP; } private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){ this(bpp, isFP); this.isDepth = isDepth; this.isCompressed = isCompressed; } /** * @return bits per pixel. */ public int getBitsPerPixel(){ return bpp; } /** * @return True if this format is a depth format, false otherwise. */ public boolean isDepthFormat(){ return isDepth; } /** * @return True if this is a compressed image format, false if * uncompressed. */ public boolean isCompressed() { return isCompressed; } /** * @return True if this image format is in floating point, * false if it is an integer format. */ public boolean isFloatingPont(){ return isFloatingPoint; } } // image attributes protected Format format; protected int width, height, depth; protected int[] mipMapSizes; protected ArrayList data; protected transient Object efficientData; protected int multiSamples = 1; // protected int mipOffset = 0; @Override public void resetObject() { this.id = -1; setUpdateNeeded(); } @Override public void deleteObject(Object rendererObject) { ((Renderer)rendererObject).deleteImage(this); } @Override public NativeObject createDestructableClone() { return new Image(id); } /** * @return A shallow clone of this image. The data is not cloned. */ @Override public Image clone(){ Image clone = (Image) super.clone(); clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; clone.data = data != null ? new ArrayList(data) : null; clone.setUpdateNeeded(); return clone; } /** * Constructor instantiates a new Image object. All values * are undefined. */ public Image() { super(Image.class); data = new ArrayList(1); } protected Image(int id){ super(Image.class, id); } /** * Constructor instantiates a new Image object. The * attributes of the image are defined during construction. * * @param format * the data format of the image. * @param width * the width of the image. * @param height * the height of the image. * @param data * the image data. * @param mipMapSizes * the array of mipmap sizes, or null for no mipmaps. */ public Image(Format format, int width, int height, int depth, ArrayList data, int[] mipMapSizes) { this(); if (mipMapSizes != null && mipMapSizes.length <= 1) { mipMapSizes = null; } setFormat(format); this.width = width; this.height = height; this.data = data; this.depth = depth; this.mipMapSizes = mipMapSizes; } /** * Constructor instantiates a new Image object. The * attributes of the image are defined during construction. * * @param format * the data format of the image. * @param width * the width of the image. * @param height * the height of the image. * @param data * the image data. * @param mipMapSizes * the array of mipmap sizes, or null for no mipmaps. */ public Image(Format format, int width, int height, ByteBuffer data, int[] mipMapSizes) { this(); if (mipMapSizes != null && mipMapSizes.length <= 1) { mipMapSizes = null; } setFormat(format); this.width = width; this.height = height; if (data != null){ this.data = new ArrayList(1); this.data.add(data); } this.mipMapSizes = mipMapSizes; } /** * Constructor instantiates a new Image object. The * attributes of the image are defined during construction. * * @param format * the data format of the image. * @param width * the width of the image. * @param height * the height of the image. * @param data * the image data. */ public Image(Format format, int width, int height, int depth, ArrayList data) { this(format, width, height, depth, data, null); } /** * Constructor instantiates a new Image object. The * attributes of the image are defined during construction. * * @param format * the data format of the image. * @param width * the width of the image. * @param height * the height of the image. * @param data * the image data. */ public Image(Format format, int width, int height, ByteBuffer data) { this(format, width, height, data, null); } /** * @return The number of samples (for multisampled textures). * @see Image#setMultiSamples(int) */ public int getMultiSamples() { return multiSamples; } /** * @param multiSamples Set the number of samples to use for this image, * setting this to a value higher than 1 turns this image/texture * into a multisample texture (on OpenGL3.1 and higher). */ public void setMultiSamples(int multiSamples) { if (multiSamples <= 0) throw new IllegalArgumentException("multiSamples must be > 0"); if (getData(0) != null) throw new IllegalArgumentException("Cannot upload data as multisample texture"); if (hasMipmaps()) throw new IllegalArgumentException("Multisample textures do not support mipmaps"); this.multiSamples = multiSamples; } /** * setData sets the data that makes up the image. This data * is packed into an array of ByteBuffer objects. * * @param data * the data that contains the image information. */ public void setData(ArrayList data) { this.data = data; setUpdateNeeded(); } /** * setData sets the data that makes up the image. This data * is packed into a single ByteBuffer. * * @param data * the data that contains the image information. */ public void setData(ByteBuffer data) { this.data = new ArrayList(1); this.data.add(data); setUpdateNeeded(); } public void addData(ByteBuffer data) { if (this.data == null) this.data = new ArrayList(1); this.data.add(data); setUpdateNeeded(); } public void setData(int index, ByteBuffer data) { if (index >= 0) { while (this.data.size() <= index) { this.data.add(null); } this.data.set(index, data); setUpdateNeeded(); } else { throw new IllegalArgumentException("index must be greater than or equal to 0."); } } /** * Set the efficient data representation of this image. *

* Some system implementations are more efficient at operating * on data other than ByteBuffers, in that case, this method can be used. * * @param efficientData */ public void setEfficentData(Object efficientData){ this.efficientData = efficientData; setUpdateNeeded(); } /** * @return The efficient data representation of this image. * @see Image#setEfficentData(java.lang.Object) */ public Object getEfficentData(){ return efficientData; } /** * Sets the mipmap sizes stored in this image's data buffer. Mipmaps are * stored sequentially, and the first mipmap is the main image data. To * specify no mipmaps, pass null and this will automatically be expanded * into a single mipmap of the full * * @param mipMapSizes * the mipmap sizes array, or null for a single image map. */ public void setMipMapSizes(int[] mipMapSizes) { if (mipMapSizes != null && mipMapSizes.length <= 1) mipMapSizes = null; this.mipMapSizes = mipMapSizes; setUpdateNeeded(); } /** * setHeight sets the height value of the image. It is * typically a good idea to try to keep this as a multiple of 2. * * @param height * the height of the image. */ public void setHeight(int height) { this.height = height; setUpdateNeeded(); } /** * setDepth sets the depth value of the image. It is * typically a good idea to try to keep this as a multiple of 2. This is * used for 3d images. * * @param depth * the depth of the image. */ public void setDepth(int depth) { this.depth = depth; setUpdateNeeded(); } /** * setWidth sets the width value of the image. It is * typically a good idea to try to keep this as a multiple of 2. * * @param width * the width of the image. */ public void setWidth(int width) { this.width = width; setUpdateNeeded(); } /** * setFormat sets the image format for this image. * * @param format * the image format. * @throws NullPointerException * if format is null * @see Format */ public void setFormat(Format format) { if (format == null) { throw new NullPointerException("format may not be null."); } this.format = format; setUpdateNeeded(); } /** * getFormat returns the image format for this image. * * @return the image format. * @see Format */ public Format getFormat() { return format; } /** * getWidth returns the width of this image. * * @return the width of this image. */ public int getWidth() { return width; } /** * getHeight returns the height of this image. * * @return the height of this image. */ public int getHeight() { return height; } /** * getDepth returns the depth of this image (for 3d images). * * @return the depth of this image. */ public int getDepth() { return depth; } /** * getData returns the data for this image. If the data is * undefined, null will be returned. * * @return the data for this image. */ public List getData() { return data; } /** * getData returns the data for this image. If the data is * undefined, null will be returned. * * @return the data for this image. */ public ByteBuffer getData(int index) { if (data.size() > index) return data.get(index); else return null; } /** * Returns whether the image data contains mipmaps. * * @return true if the image data contains mipmaps, false if not. */ public boolean hasMipmaps() { return mipMapSizes != null; } /** * Returns the mipmap sizes for this image. * * @return the mipmap sizes for this image. */ public int[] getMipMapSizes() { return mipMapSizes; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append("[size=").append(width).append("x").append(height); if (depth > 1) sb.append("x").append(depth); sb.append(", format=").append(format.name()); if (hasMipmaps()) sb.append(", mips"); if (getId() >= 0) sb.append(", id=").append(id); sb.append("]"); return sb.toString(); } @Override public boolean equals(Object other) { if (other == this) { return true; } if (!(other instanceof Image)) { return false; } Image that = (Image) other; if (this.getFormat() != that.getFormat()) return false; if (this.getWidth() != that.getWidth()) return false; if (this.getHeight() != that.getHeight()) return false; if (this.getData() != null && !this.getData().equals(that.getData())) return false; if (this.getData() == null && that.getData() != null) return false; if (this.getMipMapSizes() != null && !Arrays.equals(this.getMipMapSizes(), that.getMipMapSizes())) return false; if (this.getMipMapSizes() == null && that.getMipMapSizes() != null) return false; if (this.getMultiSamples() != that.getMultiSamples()) return false; return true; } @Override public int hashCode() { int hash = 7; hash = 97 * hash + (this.format != null ? this.format.hashCode() : 0); hash = 97 * hash + this.width; hash = 97 * hash + this.height; hash = 97 * hash + this.depth; hash = 97 * hash + Arrays.hashCode(this.mipMapSizes); hash = 97 * hash + (this.data != null ? this.data.hashCode() : 0); hash = 97 * hash + this.multiSamples; return hash; } public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(format, "format", Format.RGBA8); capsule.write(width, "width", 0); capsule.write(height, "height", 0); capsule.write(depth, "depth", 0); capsule.write(mipMapSizes, "mipMapSizes", null); capsule.write(multiSamples, "multiSamples", 1); capsule.writeByteBufferArrayList(data, "data", null); } public void read(JmeImporter e) throws IOException { InputCapsule capsule = e.getCapsule(this); format = capsule.readEnum("format", Format.class, Format.RGBA8); width = capsule.readInt("width", 0); height = capsule.readInt("height", 0); depth = capsule.readInt("depth", 0); mipMapSizes = capsule.readIntArray("mipMapSizes", null); multiSamples = capsule.readInt("multiSamples", 1); data = (ArrayList) capsule.readByteBufferArrayList("data", null); } }