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}