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 jme3tools.converters;
34
35import com.jme3.texture.Image;
36import com.jme3.texture.Image.Format;
37import com.jme3.texture.plugins.AWTLoader;
38import com.jme3.util.BufferUtils;
39import java.awt.Transparency;
40import java.awt.color.ColorSpace;
41import java.awt.image.*;
42import java.nio.ByteBuffer;
43import java.nio.ByteOrder;
44import java.util.EnumMap;
45
46public class ImageToAwt {
47
48    private static final EnumMap<Format, DecodeParams> params
49            = new EnumMap<Format, DecodeParams>(Format.class);
50
51    private static class DecodeParams {
52
53        final int bpp, am, rm, gm, bm, as, rs, gs, bs, im, is;
54
55        public DecodeParams(int bpp, int am, int rm, int gm, int bm, int as, int rs, int gs, int bs, int im, int is) {
56            this.bpp = bpp;
57            this.am = am;
58            this.rm = rm;
59            this.gm = gm;
60            this.bm = bm;
61            this.as = as;
62            this.rs = rs;
63            this.gs = gs;
64            this.bs = bs;
65            this.im = im;
66            this.is = is;
67        }
68
69        public DecodeParams(int bpp, int rm, int rs, int im, int is, boolean alpha){
70            this.bpp = bpp;
71            if (alpha){
72                this.am = rm;
73                this.as = rs;
74                this.rm = 0;
75                this.rs = 0;
76            }else{
77                this.rm = rm;
78                this.rs = rs;
79                this.am = 0;
80                this.as = 0;
81            }
82
83            this.gm = 0;
84            this.bm = 0;
85            this.gs = 0;
86            this.bs = 0;
87            this.im = im;
88            this.is = is;
89        }
90
91        public DecodeParams(int bpp, int rm, int rs, int im, int is){
92            this(bpp, rm, rs, im, is, false);
93        }
94    }
95
96    static {
97        final int mx___ = 0xff000000;
98        final int m_x__ = 0x00ff0000;
99        final int m__x_ = 0x0000ff00;
100        final int m___x = 0x000000ff;
101        final int sx___ = 24;
102        final int s_x__ = 16;
103        final int s__x_ = 8;
104        final int s___x = 0;
105        final int mxxxx = 0xffffffff;
106        final int sxxxx = 0;
107
108        final int m4x___ = 0xf000;
109        final int m4_x__ = 0x0f00;
110        final int m4__x_ = 0x00f0;
111        final int m4___x = 0x000f;
112        final int s4x___ = 12;
113        final int s4_x__ = 8;
114        final int s4__x_ = 4;
115        final int s4___x = 0;
116
117        final int m5___  = 0xf800;
118        final int m_5__  = 0x07c0;
119        final int m__5_  = 0x003e;
120        final int m___1  = 0x0001;
121
122        final int s5___  = 11;
123        final int s_5__  = 6;
124        final int s__5_  = 1;
125        final int s___1  = 0;
126
127        final int m5__   = 0xf800;
128        final int m_6_   = 0x07e0;
129        final int m__5   = 0x001f;
130
131        final int s5__   = 11;
132        final int s_6_   = 5;
133        final int s__5   = 0;
134
135        final int mxx__  = 0xffff0000;
136        final int sxx__  = 32;
137        final int m__xx  = 0x0000ffff;
138        final int s__xx  = 0;
139
140        // note: compressed, depth, or floating point formats not included here..
141
142        params.put(Format.ABGR8,    new DecodeParams(4, mx___, m___x, m__x_, m_x__,
143                                                        sx___, s___x, s__x_, s_x__,
144                                                        mxxxx, sxxxx));
145        params.put(Format.ARGB4444, new DecodeParams(2, m4x___, m4_x__, m4__x_, m4___x,
146                                                        s4x___, s4_x__, s4__x_, s4___x,
147                                                        mxxxx, sxxxx));
148        params.put(Format.Alpha16,  new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, true));
149        params.put(Format.Alpha8,   new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, true));
150        params.put(Format.BGR8,     new DecodeParams(3, 0,     m___x, m__x_, m_x__,
151                                                        0,     s___x, s__x_, s_x__,
152                                                        mxxxx, sxxxx));
153        params.put(Format.Luminance16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
154        params.put(Format.Luminance8,  new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
155        params.put(Format.Luminance16Alpha16, new DecodeParams(4, m__xx, mxx__, 0, 0,
156                                                                  s__xx, sxx__, 0, 0,
157                                                                  mxxxx, sxxxx));
158        params.put(Format.Luminance16F, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
159        params.put(Format.Luminance16FAlpha16F, new DecodeParams(4, m__xx, mxx__, 0, 0,
160                                                                    s__xx, sxx__, 0, 0,
161                                                                    mxxxx, sxxxx));
162        params.put(Format.Luminance32F, new DecodeParams(4, mxxxx, sxxxx, mxxxx, sxxxx, false));
163        params.put(Format.Luminance8,   new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
164        params.put(Format.RGB5A1,       new DecodeParams(2, m___1, m5___, m_5__, m__5_,
165                                                            s___1, s5___, s_5__, s__5_,
166                                                            mxxxx, sxxxx));
167        params.put(Format.RGB565,       new DecodeParams(2, 0,     m5__ , m_6_ , m__5,
168                                                            0,     s5__ , s_6_ , s__5,
169                                                            mxxxx, sxxxx));
170        params.put(Format.RGB8,         new DecodeParams(3, 0,     m_x__, m__x_, m___x,
171                                                            0,     s_x__, s__x_, s___x,
172                                                            mxxxx, sxxxx));
173        params.put(Format.RGBA8,        new DecodeParams(4, m___x, mx___, m_x__, m__x_,
174                                                            s___x, sx___, s_x__, s__x_,
175                                                            mxxxx, sxxxx));
176    }
177
178    private static int Ix(int x, int y, int w){
179        return y * w + x;
180    }
181
182    private static int readPixel(ByteBuffer buf, int idx, int bpp){
183        buf.position(idx);
184        int original = buf.get() & 0xff;
185        while ((--bpp) > 0){
186            original = (original << 8) | (buf.get() & 0xff);
187        }
188        return original;
189    }
190
191    private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp){
192        buf.position(idx);
193        while ((--bpp) >= 0){
194//            pixel = pixel >> 8;
195            byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff);
196//            buf.put( (byte) (pixel & 0xff) );
197            buf.put(bt);
198        }
199    }
200
201
202    /**
203     * Convert an AWT image to jME image.
204     */
205    public static void convert(BufferedImage image, Format format, ByteBuffer buf){
206        DecodeParams p = params.get(format);
207        if (p == null)
208            throw new UnsupportedOperationException("Image format " + format + " is not supported");
209
210        int width = image.getWidth();
211        int height = image.getHeight();
212
213        boolean alpha = true;
214        boolean luminance = false;
215
216        int reductionA = 8 - Integer.bitCount(p.am);
217        int reductionR = 8 - Integer.bitCount(p.rm);
218        int reductionG = 8 - Integer.bitCount(p.gm);
219        int reductionB = 8 - Integer.bitCount(p.bm);
220
221        int initialPos = buf.position();
222        for (int y = 0; y < height; y++){
223            for (int x = 0; x < width; x++){
224                // Get ARGB
225                int argb = image.getRGB(x, y);
226
227                // Extract color components
228                int a = (argb & 0xff000000) >> 24;
229                int r = (argb & 0x00ff0000) >> 16;
230                int g = (argb & 0x0000ff00) >> 8;
231                int b = (argb & 0x000000ff);
232
233                // Remove anything after 8 bits
234                a = a & 0xff;
235                r = r & 0xff;
236                g = g & 0xff;
237                b = b & 0xff;
238
239                // Set full alpha if target image has no alpha
240                if (!alpha)
241                    a = 0xff;
242
243                // Convert color to luminance if target
244                // image is in luminance format
245                if (luminance){
246                    // convert RGB to luminance
247                }
248
249                // Do bit reduction, assumes proper rounding has already been
250                // done.
251                a = a >> reductionA;
252                r = r >> reductionR;
253                g = g >> reductionG;
254                b = b >> reductionB;
255
256                // Put components into appropriate positions
257                a = (a << p.as) & p.am;
258                r = (r << p.rs) & p.rm;
259                g = (g << p.gs) & p.gm;
260                b = (b << p.bs) & p.bm;
261
262                int outputPixel = ((a | r | g | b) << p.is) & p.im;
263                int i = initialPos + (Ix(x,y,width) * p.bpp);
264                writePixel(buf, i, outputPixel, p.bpp);
265            }
266        }
267    }
268
269    private static final double LOG2 = Math.log(2);
270
271    public static void createData(Image image, boolean mipmaps){
272        int bpp = image.getFormat().getBitsPerPixel();
273        int w = image.getWidth();
274        int h = image.getHeight();
275        if (!mipmaps){
276            image.setData(BufferUtils.createByteBuffer(w*h*bpp/8));
277            return;
278        }
279        int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(h, w)) / LOG2);
280        int[] mipMapSizes = new int[expectedMipmaps];
281        int total = 0;
282        for (int i = 0; i < mipMapSizes.length; i++){
283            int size = (w * h * bpp) / 8;
284            total += size;
285            mipMapSizes[i] = size;
286            w /= 2;
287            h /= 2;
288        }
289        image.setMipMapSizes(mipMapSizes);
290        image.setData(BufferUtils.createByteBuffer(total));
291    }
292
293    /**
294     * Convert the image from the given format to the output format.
295     * It is assumed that both images have buffers with the appropriate
296     * number of elements and that both have the same dimensions.
297     *
298     * @param input
299     * @param output
300     */
301    public static void convert(Image input, Image output){
302        DecodeParams inParams  = params.get(input.getFormat());
303        DecodeParams outParams = params.get(output.getFormat());
304
305        if (inParams == null || outParams == null)
306            throw new UnsupportedOperationException();
307
308        int width  = input.getWidth();
309        int height = input.getHeight();
310
311        if (width != output.getWidth() || height != output.getHeight())
312            throw new IllegalArgumentException();
313
314        ByteBuffer inData = input.getData(0);
315
316        boolean inAlpha = false;
317        boolean inLum = false;
318        boolean inRGB = false;
319        if (inParams.am != 0) {
320            inAlpha = true;
321        }
322
323        if (inParams.rm != 0 && inParams.gm == 0 && inParams.bm == 0) {
324            inLum = true;
325        } else if (inParams.rm != 0 && inParams.gm != 0 && inParams.bm != 0) {
326            inRGB = true;
327        }
328
329        int expansionA = 8 - Integer.bitCount(inParams.am);
330        int expansionR = 8 - Integer.bitCount(inParams.rm);
331        int expansionG = 8 - Integer.bitCount(inParams.gm);
332        int expansionB = 8 - Integer.bitCount(inParams.bm);
333
334        int inputPixel;
335        for (int y = 0; y < height; y++){
336            for (int x = 0; x < width; x++){
337                int i = Ix(x, y, width) * inParams.bpp;
338                inputPixel = (readPixel(inData, i, inParams.bpp) & inParams.im) >> inParams.is;
339
340                int a = (inputPixel & inParams.am) >> inParams.as;
341                int r = (inputPixel & inParams.rm) >> inParams.rs;
342                int g = (inputPixel & inParams.gm) >> inParams.gs;
343                int b = (inputPixel & inParams.bm) >> inParams.bs;
344
345                r = r & 0xff;
346                g = g & 0xff;
347                b = b & 0xff;
348                a = a & 0xff;
349
350                a = a << expansionA;
351                r = r << expansionR;
352                g = g << expansionG;
353                b = b << expansionB;
354
355                if (inLum)
356                    b = g = r;
357
358                if (!inAlpha)
359                    a = 0xff;
360
361//                int argb = (a << 24) | (r << 16) | (g << 8) | b;
362//                out.setRGB(x, y, argb);
363            }
364        }
365    }
366
367    public static BufferedImage convert(Image image, boolean do16bit, boolean fullalpha, int mipLevel){
368        Format format = image.getFormat();
369        DecodeParams p = params.get(image.getFormat());
370        if (p == null)
371            throw new UnsupportedOperationException();
372
373        int width = image.getWidth();
374        int height = image.getHeight();
375
376        int level = mipLevel;
377        while (--level >= 0){
378            width  /= 2;
379            height /= 2;
380        }
381
382        ByteBuffer buf = image.getData(0);
383        buf.order(ByteOrder.LITTLE_ENDIAN);
384
385        BufferedImage out;
386
387        boolean alpha = false;
388        boolean luminance = false;
389        boolean rgb = false;
390        if (p.am != 0)
391            alpha = true;
392
393        if (p.rm != 0 && p.gm == 0 && p.bm == 0)
394            luminance = true;
395        else if (p.rm != 0 && p.gm != 0 && p.bm != 0)
396            rgb = true;
397
398        // alpha OR luminance but not both
399        if ( (alpha && !rgb && !luminance) || (luminance && !alpha && !rgb) ){
400            out = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
401        }else if ( (rgb && alpha) || (luminance && alpha) ){
402            if (do16bit){
403                if (fullalpha){
404                    ColorModel model = AWTLoader.AWT_RGBA4444;
405                    WritableRaster raster = model.createCompatibleWritableRaster(width, width);
406                    out = new BufferedImage(model, raster, false, null);
407                }else{
408                    // RGB5_A1
409                    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
410                    int[] nBits = {5, 5, 5, 1};
411                    int[] bOffs = {0, 1, 2, 3};
412                    ColorModel colorModel = new ComponentColorModel(cs, nBits, true, false,
413                                                                    Transparency.BITMASK,
414                                                                    DataBuffer.TYPE_BYTE);
415                    WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
416                                                                           width, height,
417                                                                           width*2, 2,
418                                                                           bOffs, null);
419                    out = new BufferedImage(colorModel, raster, false, null);
420                }
421            }else{
422                out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
423            }
424        }else{
425            if (do16bit){
426                out = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);
427            }else{
428                out = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
429            }
430        }
431
432        int expansionA = 8 - Integer.bitCount(p.am);
433        int expansionR = 8 - Integer.bitCount(p.rm);
434        int expansionG = 8 - Integer.bitCount(p.gm);
435        int expansionB = 8 - Integer.bitCount(p.bm);
436
437        if (expansionR < 0){
438            expansionR = 0;
439        }
440
441        int mipPos = 0;
442        for (int i = 0; i < mipLevel; i++){
443            mipPos += image.getMipMapSizes()[i];
444        }
445        int inputPixel;
446        for (int y = 0; y < height; y++){
447            for (int x = 0; x < width; x++){
448                int i = mipPos + (Ix(x,y,width) * p.bpp);
449                inputPixel = (readPixel(buf,i,p.bpp) & p.im) >> p.is;
450                int a = (inputPixel & p.am) >> p.as;
451                int r = (inputPixel & p.rm) >> p.rs;
452                int g = (inputPixel & p.gm) >> p.gs;
453                int b = (inputPixel & p.bm) >> p.bs;
454
455                r = r & 0xff;
456                g = g & 0xff;
457                b = b & 0xff;
458                a = a & 0xff;
459
460                a = a << expansionA;
461                r = r << expansionR;
462                g = g << expansionG;
463                b = b << expansionB;
464
465                if (luminance)
466                    b = g = r;
467
468                if (!alpha)
469                    a = 0xff;
470
471                int argb = (a << 24) | (r << 16) | (g << 8) | b;
472                out.setRGB(x, y, argb);
473            }
474        }
475
476        return out;
477    }
478
479}
480