001/**
002 * Copyright (c) 2008-2014 Ardor Labs, Inc.
003 *
004 * This file is part of Ardor3D.
005 *
006 * Ardor3D is free software: you can redistribute it and/or modify it
007 * under the terms of its license which may be found in the accompanying
008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
009 */
010
011package com.ardor3d.image.util.jogl;
012
013import java.io.IOException;
014import java.io.InputStream;
015import java.nio.Buffer;
016import java.nio.ByteBuffer;
017import java.nio.CharBuffer;
018import java.nio.DoubleBuffer;
019import java.nio.FloatBuffer;
020import java.nio.IntBuffer;
021import java.nio.LongBuffer;
022import java.nio.ShortBuffer;
023import java.util.ArrayList;
024import java.util.List;
025
026import com.ardor3d.framework.jogl.CapsUtil;
027import com.ardor3d.image.Image;
028import com.ardor3d.image.PixelDataType;
029import com.ardor3d.image.util.ImageLoader;
030import com.ardor3d.image.util.ImageLoaderUtil;
031import com.ardor3d.scene.state.jogl.util.JoglTextureUtil;
032import com.ardor3d.util.geom.BufferUtils;
033import com.jogamp.common.nio.Buffers;
034import com.jogamp.common.os.Platform;
035import com.jogamp.opengl.util.texture.TextureData;
036import com.jogamp.opengl.util.texture.TextureIO;
037
038public class JoglImageLoader implements ImageLoader {
039
040    public static boolean createOnHeap = false;
041
042    protected final CapsUtil _capsUtil;
043
044    /**
045     * Flag indicating whether the mipmaps are produced by JOGL (retrieved from the file or generated)
046     */
047    private boolean _mipmapsProductionEnabled;
048
049    private enum TYPE {
050        BYTE(ByteBuffer.class), SHORT(ShortBuffer.class), CHAR(CharBuffer.class), INT(IntBuffer.class), FLOAT(
051                FloatBuffer.class), LONG(LongBuffer.class), DOUBLE(DoubleBuffer.class);
052
053        private final Class<? extends Buffer> bufferClass;
054
055        private TYPE(final Class<? extends Buffer> bufferClass) {
056            this.bufferClass = bufferClass;
057        }
058    };
059
060    private static final String[] _supportedFormats = computeSupportedFormats();
061
062    private static final String[] computeSupportedFormats() {
063        final List<String> supportedFormatsList = new ArrayList<>();
064        if (Platform.AWT_AVAILABLE) {
065            supportedFormatsList.add("." + TextureIO.GIF.toUpperCase());
066        }
067        supportedFormatsList.add("." + TextureIO.DDS.toUpperCase());
068        supportedFormatsList.add("." + TextureIO.JPG.toUpperCase());
069        supportedFormatsList.add("." + TextureIO.PNG.toUpperCase());
070        supportedFormatsList.add("." + TextureIO.SGI.toUpperCase());
071        supportedFormatsList.add("." + TextureIO.SGI_RGB.toUpperCase());
072        return supportedFormatsList.toArray(new String[supportedFormatsList.size()]);
073    }
074
075    public static String[] getSupportedFormats() {
076        return _supportedFormats;
077    }
078
079    public static void registerLoader() {
080        registerLoader(new JoglImageLoader(), _supportedFormats);
081        registerLoader(new JoglTgaImageLoader(), JoglTgaImageLoader.getSupportedFormats());
082    }
083
084    public static void registerLoader(final JoglImageLoader joglImageLoader, final String[] supportedFormats) {
085        ImageLoaderUtil.registerHandler(joglImageLoader, supportedFormats);
086    }
087
088    public JoglImageLoader() {
089        this(new CapsUtil());
090    }
091
092    public JoglImageLoader(final CapsUtil capsUtil) {
093        _capsUtil = capsUtil;
094    }
095
096    public Image makeArdor3dImage(final TextureData textureData, final boolean verticalFlipNeeded) {
097        final Buffer textureDataBuffer = textureData.getBuffer();
098        final Image ardorImage = new Image();
099        TYPE bufferDataType = getBufferDataType(textureDataBuffer);
100        if (bufferDataType == null) {
101            throw new UnsupportedOperationException("Unknown buffer type " + textureDataBuffer.getClass().getName());
102        } else {
103            int dataSizeInBytes = textureDataBuffer.capacity() * Buffers.sizeOfBufferElem(textureDataBuffer);
104            ByteBuffer scratch = createOnHeap ? BufferUtils.createByteBufferOnHeap(dataSizeInBytes) : Buffers
105                    .newDirectByteBuffer(dataSizeInBytes);
106            if (verticalFlipNeeded) {
107                flipImageData(textureDataBuffer, scratch, dataSizeInBytes, bufferDataType, textureData.getWidth(),
108                        textureData.getHeight());
109            } else {
110                copyImageData(textureDataBuffer, scratch, bufferDataType);
111            }
112            ardorImage.setWidth(textureData.getWidth());
113            ardorImage.setHeight(textureData.getHeight());
114            ardorImage.setData(scratch);
115            ardorImage.setDataFormat(JoglTextureUtil.getImageDataFormat(textureData.getPixelFormat()));
116            /**
117             * A ByteBuffer is always used to store the image data, otherwise we should call
118             * JoglTextureUtil.getPixelDataType(textureData.getPixelType())
119             */
120            ardorImage.setDataType(PixelDataType.UnsignedByte);
121            if (textureData.getMipmapData() != null) {
122                for (final Buffer mipmapData : textureData.getMipmapData()) {
123                    dataSizeInBytes = mipmapData.capacity() * Buffers.sizeOfBufferElem(mipmapData);
124                    scratch = createOnHeap ? BufferUtils.createByteBufferOnHeap(dataSizeInBytes) : Buffers
125                            .newDirectByteBuffer(dataSizeInBytes);
126                    bufferDataType = getBufferDataType(mipmapData);
127                    if (verticalFlipNeeded) {
128                        flipImageData(mipmapData, scratch, dataSizeInBytes, bufferDataType, textureData.getWidth(),
129                                textureData.getHeight());
130                    } else {
131                        copyImageData(mipmapData, scratch, bufferDataType);
132                    }
133                    ardorImage.addData(scratch);
134                }
135                final int[] mipMapSizes = new int[ardorImage.getDataSize()];
136                int imageDataIndex = 0;
137                for (final Buffer imageData : ardorImage.getData()) {
138                    mipMapSizes[imageDataIndex] = imageData.capacity();
139                    imageDataIndex++;
140                }
141                ardorImage.setMipMapByteSizes(mipMapSizes);
142            }
143            return ardorImage;
144        }
145    }
146
147    @Override
148    public Image load(final InputStream is, final boolean verticalFlipNeeded) throws IOException {
149        final TextureData textureData = TextureIO.newTextureData(_capsUtil.getProfile(), is, _mipmapsProductionEnabled,
150                null /* JOGL >= 2.3.2 is able to guess the file suffix */);
151        if (textureData == null) {
152            return null;
153        }
154        return makeArdor3dImage(textureData, textureData.getMustFlipVertically() == verticalFlipNeeded);
155    }
156
157    private TYPE getBufferDataType(final Buffer buffer) {
158        TYPE bufferDataType = null;
159        for (final TYPE type : TYPE.values()) {
160            if (type.bufferClass.isAssignableFrom(buffer.getClass())) {
161                bufferDataType = type;
162                break;
163            }
164        }
165        return bufferDataType;
166    }
167
168    protected void copyImageData(final Buffer src, final ByteBuffer dest, final TYPE bufferDataType) {
169        final int srcPos = src.position();
170        final int destPos = dest.position();
171        switch (bufferDataType) {
172            case BYTE:
173                dest.put((ByteBuffer) src);
174                break;
175            case SHORT:
176                dest.asShortBuffer().put((ShortBuffer) src);
177                break;
178            case CHAR:
179                dest.asCharBuffer().put((CharBuffer) src);
180                break;
181            case INT:
182                dest.asIntBuffer().put((IntBuffer) src);
183                break;
184            case FLOAT:
185                dest.asFloatBuffer().put((FloatBuffer) src);
186            case LONG:
187                dest.asLongBuffer().put((LongBuffer) src);
188                break;
189            case DOUBLE:
190                dest.asDoubleBuffer().put((DoubleBuffer) src);
191                break;
192            default:
193                // it should never happen
194        }
195        src.position(srcPos);
196        dest.position(destPos);
197    }
198
199    protected void flipImageData(final Buffer src, final ByteBuffer dest, final int dataSizeInBytes,
200            final TYPE bufferDataType, final int width, final int height) {
201        final int srcPos = src.position();
202        final int destPos = dest.position();
203        final int bytesPerPixel = dataSizeInBytes / (width * height);
204        final int bytesPerElement = Buffers.sizeOfBufferElem(src);
205        final int elementsPerPixel = bytesPerPixel / bytesPerElement;
206        final int elementsPerLine = width * elementsPerPixel;
207        final int bytesPerLine = bytesPerPixel * width;// width = pixels per line
208        byte[] byteBuf = null;
209        short[] shortBuf = null;
210        char[] charBuf = null;
211        int[] intBuf = null;
212        float[] floatBuf = null;
213        long[] longBuf = null;
214        double[] doubleBuf = null;
215        switch (bufferDataType) {
216            case BYTE:
217                byteBuf = new byte[elementsPerLine];
218                break;
219            case SHORT:
220                shortBuf = new short[elementsPerLine];
221                break;
222            case CHAR:
223                charBuf = new char[elementsPerLine];
224                break;
225            case INT:
226                intBuf = new int[elementsPerLine];
227                break;
228            case FLOAT:
229                floatBuf = new float[elementsPerLine];
230                break;
231            case LONG:
232                longBuf = new long[elementsPerLine];
233                break;
234            case DOUBLE:
235                doubleBuf = new double[elementsPerLine];
236                break;
237            default:
238                // it should never happen
239        }
240        while (dest.hasRemaining()) {
241            final int srcFirstPixelIndex = dest.position() / bytesPerPixel;
242            final int srcFirstPixelComponentOffset = dest.position() - (srcFirstPixelIndex * bytesPerPixel);
243            final int srcFirstColumnIndex = srcFirstPixelIndex % width;
244            final int scrFirstRowIndex = (srcFirstPixelIndex - srcFirstColumnIndex) / width;
245            final int dstFirstColumnIndex = srcFirstColumnIndex;
246            final int dstFirstRowIndex = (height - 1) - scrFirstRowIndex;
247            final int dstFirstPixelIndex = dstFirstRowIndex * width + dstFirstColumnIndex;
248            final int dstFirstPixelComponentOffset = srcFirstPixelComponentOffset;
249            final int dstFirstElementIndex = dstFirstPixelIndex * bytesPerPixel + dstFirstPixelComponentOffset;
250            switch (bufferDataType) {
251                case BYTE:
252                    ((ByteBuffer) src).position(dstFirstElementIndex);
253                    ((ByteBuffer) src).get(byteBuf);
254                    dest.put(byteBuf);
255                    break;
256                case SHORT:
257                    ((ShortBuffer) src).position(dstFirstElementIndex);
258                    ((ShortBuffer) src).get(shortBuf);
259                    dest.asShortBuffer().put(shortBuf);
260                    dest.position(dest.position() + bytesPerLine);
261                    break;
262                case CHAR:
263                    ((CharBuffer) src).position(dstFirstElementIndex);
264                    ((CharBuffer) src).get(charBuf);
265                    dest.asCharBuffer().put(charBuf);
266                    dest.position(dest.position() + bytesPerLine);
267                    break;
268                case INT:
269                    ((IntBuffer) src).position(dstFirstElementIndex);
270                    ((IntBuffer) src).get(intBuf);
271                    dest.asIntBuffer().put(intBuf);
272                    dest.position(dest.position() + bytesPerLine);
273                    break;
274                case FLOAT:
275                    ((FloatBuffer) src).position(dstFirstElementIndex);
276                    ((FloatBuffer) src).get(floatBuf);
277                    dest.asFloatBuffer().put(floatBuf);
278                    dest.position(dest.position() + bytesPerLine);
279                    break;
280                case LONG:
281                    ((LongBuffer) src).position(dstFirstElementIndex);
282                    ((LongBuffer) src).get(longBuf);
283                    dest.asLongBuffer().put(longBuf);
284                    dest.position(dest.position() + bytesPerLine);
285                    break;
286                case DOUBLE:
287                    ((DoubleBuffer) src).position(dstFirstElementIndex);
288                    ((DoubleBuffer) src).get(doubleBuf);
289                    dest.asDoubleBuffer().put(doubleBuf);
290                    dest.position(dest.position() + bytesPerLine);
291                    break;
292                default:
293                    // it should never happen
294            }
295        }
296        src.position(srcPos);
297        dest.position(destPos);
298    }
299
300    public boolean isMipmapsProductionEnabled() {
301        return _mipmapsProductionEnabled;
302    }
303
304    public void setMipmapsProductionEnabled(final boolean mipmapsProductionEnabled) {
305        _mipmapsProductionEnabled = mipmapsProductionEnabled;
306    }
307}