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 
Format(int bpp)265         private Format(int bpp){
266             this.bpp = bpp;
267         }
268 
Format(int bpp, boolean isFP)269         private Format(int bpp, boolean isFP){
270             this(bpp);
271             this.isFloatingPoint = isFP;
272         }
273 
Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP)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          */
getBitsPerPixel()283         public int getBitsPerPixel(){
284             return bpp;
285         }
286 
287         /**
288          * @return True if this format is a depth format, false otherwise.
289          */
isDepthFormat()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          */
isCompressed()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          */
isFloatingPont()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
resetObject()322     public void resetObject() {
323         this.id = -1;
324         setUpdateNeeded();
325     }
326 
327     @Override
deleteObject(Object rendererObject)328     public void deleteObject(Object rendererObject) {
329         ((Renderer)rendererObject).deleteImage(this);
330     }
331 
332     @Override
createDestructableClone()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
clone()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      */
Image()353     public Image() {
354         super(Image.class);
355         data = new ArrayList<ByteBuffer>(1);
356     }
357 
Image(int id)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      */
Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data, int[] mipMapSizes)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      */
Image(Format format, int width, int height, ByteBuffer data, int[] mipMapSizes)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      */
Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data)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      */
Image(Format format, int width, int height, ByteBuffer data)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      */
getMultiSamples()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      */
setMultiSamples(int multiSamples)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      */
setData(ArrayList<ByteBuffer> data)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      */
setData(ByteBuffer data)507     public void setData(ByteBuffer data) {
508         this.data = new ArrayList<ByteBuffer>(1);
509         this.data.add(data);
510         setUpdateNeeded();
511     }
512 
addData(ByteBuffer data)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 
setData(int index, ByteBuffer data)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      */
setEfficentData(Object efficientData)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      */
getEfficentData()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      */
setMipMapSizes(int[] mipMapSizes)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      */
setHeight(int height)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      */
setDepth(int depth)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      */
setWidth(int width)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      */
setFormat(Format format)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      */
getFormat()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      */
getWidth()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      */
getHeight()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      */
getDepth()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      */
getData()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      */
getData(int index)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      */
hasMipmaps()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      */
getMipMapSizes()699     public int[] getMipMapSizes() {
700         return mipMapSizes;
701     }
702 
703     @Override
toString()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
equals(Object other)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
hashCode()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 
write(JmeExporter e)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 
read(JmeImporter e)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