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
33package com.jme3.texture;
34
35import com.jme3.export.*;
36import com.jme3.renderer.Renderer;
37import com.jme3.util.NativeObject;
38import java.io.IOException;
39import java.nio.ByteBuffer;
40import java.util.ArrayList;
41import java.util.Arrays;
42import 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 */
54public 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