Ver Fonte

KTX file loading and writing support

Nehon há 10 anos atrás
pai
commit
8a96772ae3

+ 354 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java

@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture.plugins.ktx;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.opengl.GLImageFormat;
+import com.jme3.renderer.opengl.GLImageFormats;
+import com.jme3.texture.Image;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.LittleEndien;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * A KTX file loader
+ * KTX file format is an image container defined by the Kronos group
+ * See specs here https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
+ * 
+ * This loader doesn't support compressed files yet.
+ * 
+ * @author Nehon
+ */
+public class KTXLoader implements AssetLoader {
+    
+    private final static Logger log = Logger.getLogger(KTXLoader.class.getName());
+ 
+    private final static byte[] fileIdentifier = {
+        (byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
+    };
+    private boolean slicesInside = false;
+
+    @Override
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey)) {
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+        }
+
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            Image img = load(in);
+            return img;
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    private Image load(InputStream stream) {
+
+        byte[] fileId = new byte[12];
+
+        DataInput in = new DataInputStream(stream);
+        try {
+            stream.read(fileId, 0, 12);
+            if (!checkFileIdentifier(fileId)) {
+                throw new IllegalArgumentException("Unrecognized ktx file identifier : " + new String(fileId) + " should be " + new String(fileIdentifier));
+            }
+
+            int endianness = in.readInt();
+            //opposite endianness
+            if (endianness == 0x01020304) {
+                in = new LittleEndien(stream);
+            }
+            int glType = in.readInt();
+            int glTypeSize = in.readInt();
+            int glFormat = in.readInt();
+            int glInternalFormat = in.readInt();
+            int glBaseInternalFormat = in.readInt();
+            int pixelWidth = in.readInt();
+            int pixelHeight = in.readInt();
+            int pixelDepth = in.readInt();
+            int numberOfArrayElements = in.readInt();
+            int numberOfFaces = in.readInt();
+            int numberOfMipmapLevels = in.readInt();
+            int bytesOfKeyValueData = in.readInt();
+
+            log.log(Level.FINE, "glType = {0}", glType);
+            log.log(Level.FINE, "glTypeSize = {0}", glTypeSize);
+            log.log(Level.FINE, "glFormat = {0}", glFormat);
+            log.log(Level.FINE, "glInternalFormat = {0}", glInternalFormat);
+            log.log(Level.FINE, "glBaseInternalFormat = {0}", glBaseInternalFormat);
+            log.log(Level.FINE, "pixelWidth = {0}", pixelWidth);
+            log.log(Level.FINE, "pixelHeight = {0}", pixelHeight);
+            log.log(Level.FINE, "pixelDepth = {0}", pixelDepth);
+            log.log(Level.FINE, "numberOfArrayElements = {0}", numberOfArrayElements);
+            log.log(Level.FINE, "numberOfFaces = {0}", numberOfFaces);
+            log.log(Level.FINE, "numberOfMipmapLevels = {0}", numberOfMipmapLevels);
+            log.log(Level.FINE, "bytesOfKeyValueData = {0}", bytesOfKeyValueData);
+            
+            if((numberOfFaces >1 && pixelDepth >1) || (numberOfFaces >1 && numberOfArrayElements >1) ||  (pixelDepth >1 && numberOfArrayElements >1)){
+                throw new UnsupportedOperationException("jME doesn't support cube maps of 3D textures or arrays of 3D texture or arrays of cube map of 3d textures");
+            }
+            
+
+            PixelReader pixelReader = parseMetaData(bytesOfKeyValueData, in);
+            if (pixelReader == null){
+                pixelReader = new SrTuRoPixelReader(); 
+            }
+            
+            //some of the values may be 0 we need them at least to be 1
+            pixelDepth = Math.max(1, pixelDepth);
+            numberOfArrayElements = Math.max(1, numberOfArrayElements);
+            numberOfFaces = Math.max(1, numberOfFaces);
+            numberOfMipmapLevels = Math.max(1, numberOfMipmapLevels);
+            
+            int nbSlices = Math.max(numberOfFaces,numberOfArrayElements);
+
+            Image.Format imgFormat = getImageFormat(glFormat, glInternalFormat, glType);
+            log.log(Level.FINE, "img format {0}", imgFormat.toString());
+            
+           
+            int bytePerPixel = imgFormat.getBitsPerPixel() / 8;            
+            int byteBuffersSize = computeBuffersSize(numberOfMipmapLevels, pixelWidth, pixelHeight, bytePerPixel, pixelDepth);
+            log.log(Level.FINE, "data size {0}", byteBuffersSize);
+            
+            int[] mipMapSizes = new int[numberOfMipmapLevels];
+            
+            Image image = createImage(nbSlices, byteBuffersSize, imgFormat, pixelWidth, pixelHeight, pixelDepth);
+            
+            byte[] pixelData = new byte[bytePerPixel];            
+            
+            int offset = 0;
+            //iterate over data
+            for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+                //size of the image in byte.
+                //this value is bogus in many example, when using mipmaps.
+                //instead we compute the theorical size and display a warning when it does not match.
+                int fileImageSize = in.readInt();
+                
+                int width = Math.max(1, pixelWidth >> mipLevel);
+                int height = Math.max(1, pixelHeight >> mipLevel);
+               
+                int imageSize = width * height * bytePerPixel;
+                mipMapSizes[mipLevel] = imageSize;
+                log.log(Level.FINE, "current mip size {0}", imageSize);
+                if(fileImageSize != imageSize){
+                    log.log(Level.WARNING, "Mip map size is wrong in the file for mip level {0} size is {1} should be {2}", new Object[]{mipLevel, fileImageSize, imageSize});
+                }
+                
+                for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
+                    for (int face = 0; face < numberOfFaces; face++) {
+                        int nbPixelRead = 0;
+                        for (int depth = 0; depth < pixelDepth; depth++) {
+                            ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));    
+
+                            log.log(Level.FINE, "position {0}", byteBuffer.position());
+                            byteBuffer.position(offset);                                                        
+                            nbPixelRead = pixelReader.readPixels(width, height, pixelData, byteBuffer, in);
+                        }
+                        //cube padding
+                        if (numberOfFaces == 6 && numberOfArrayElements == 0) {
+                            in.skipBytes(3 - ((nbPixelRead + 3) % 4));
+                        }
+                    }
+                }
+                //mip padding
+                log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
+                in.skipBytes(3 - ((imageSize + 3) % 4));
+                offset+=imageSize;
+            }
+            //there are loaded mip maps we set the sizes
+            if(numberOfMipmapLevels >1){
+                image.setMipMapSizes(mipMapSizes);
+            }
+            //if 3D texture and slices' orientation is inside, we reverse the data array.
+            if(pixelDepth > 1 && slicesInside){
+                Collections.reverse(image.getData());
+            }
+            return image;
+
+        } catch (IOException ex) {
+            Logger.getLogger(KTXLoader.class.getName()).log(Level.SEVERE, null, ex);
+        }
+        return null;
+    }
+
+    /**
+     * returns the slice from the face and the array index
+     * @param face the face
+     * @param arrayElem the array index
+     * @return 
+     */
+    private static int getSlice(int face, int arrayElem) {
+        return Math.max(face, arrayElem);
+    }
+
+    /**
+     * Computes a buffer size from given parameters
+     * @param numberOfMipmapLevels 
+     * @param pixelWidth
+     * @param pixelHeight
+     * @param bytePerPixel
+     * @param pixelDepth
+     * @return 
+     */
+    private int computeBuffersSize(int numberOfMipmapLevels, int pixelWidth, int pixelHeight, int bytePerPixel, int pixelDepth) {
+        int byteBuffersSize = 0;
+        for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+            int width = Math.max(1, pixelWidth >> mipLevel);
+            int height = Math.max(1, pixelHeight >> mipLevel);
+            byteBuffersSize += width * height * bytePerPixel;
+            log.log(Level.FINE, "mip level size : {0} : {1}", new Object[]{mipLevel, width * height * bytePerPixel});
+        }
+        return byteBuffersSize * pixelDepth;
+    }
+
+    /**
+     * Create an image with given parameters
+     * @param nbSlices
+     * @param byteBuffersSize
+     * @param imgFormat
+     * @param pixelWidth
+     * @param pixelHeight
+     * @param depth
+     * @return 
+     */
+    private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFormat, int pixelWidth, int pixelHeight, int depth) {
+        ArrayList<ByteBuffer> imageData = new ArrayList<ByteBuffer>(nbSlices);
+        for (int i = 0; i < nbSlices; i++) {
+            imageData.add(BufferUtils.createByteBuffer(byteBuffersSize));
+        }
+        Image image = new Image(imgFormat, pixelWidth, pixelHeight, depth, imageData, ColorSpace.sRGB);
+        return image;
+    }
+
+    /**
+     * Parse the file metaData to select the PixelReader that suits the file 
+     * coordinates orientation
+     * @param bytesOfKeyValueData
+     * @param in
+     * @return
+     * @throws IOException 
+     */
+    private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws IOException {
+        PixelReader pixelReader = null;
+        for (int i = 0; i < bytesOfKeyValueData;) {
+            //reading key values
+            int keyAndValueByteSize = in.readInt();            
+            byte[] keyValue = new byte[keyAndValueByteSize];
+            in.readFully(keyValue);
+            
+            
+            //parsing key values
+            String[] kv = new String(keyValue).split("\0");            
+            for (int j = 0; j < kv.length; j += 2) {
+                System.err.println("key : " + kv[j]);
+                System.err.println("value : " + kv[j + 1]);
+                if(kv[j].equalsIgnoreCase("KTXorientation")){
+                    if(kv[j + 1].startsWith("S=r,T=d") ){
+                        pixelReader = new SrTdRiPixelReader();
+                    }else{
+                        pixelReader = new SrTuRoPixelReader();
+                    }
+                    if(kv[j + 1].contains("R=i")){
+                        slicesInside = true;
+                    }
+                }
+            }
+            
+            //padding
+            int padding = 3 - ((keyAndValueByteSize + 3) % 4);            
+            if (padding > 0) {
+                in.skipBytes(padding);
+            }
+            i += 4 + keyAndValueByteSize + padding;
+        }
+        return pixelReader;
+    }
+
+    /**
+     * Chacks the file id
+     * @param b
+     * @return 
+     */
+    private boolean checkFileIdentifier(byte[] b) {
+        boolean check = true;
+        for (int i = 0; i < 12; i++) {
+            if (b[i] != fileIdentifier[i]) {
+                check = false;
+            }
+        }
+        return check;
+    }
+
+    /**
+     * returns the JME image format from gl formats and types.
+     * @param glFormat
+     * @param glInternalFormat
+     * @param glType
+     * @return 
+     */
+    private Image.Format getImageFormat(int glFormat, int glInternalFormat, int glType) {
+        EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
+        GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
+        for (GLImageFormat[] format : formats) {
+            for (int j = 0; j < format.length; j++) {
+                GLImageFormat glImgFormat = format[j];
+                if (glImgFormat != null) {
+                    if (glImgFormat.format == glFormat && glImgFormat.dataType == glType) {
+                        if (glFormat == glInternalFormat || glImgFormat.internalFormat == glInternalFormat) {
+                            return Image.Format.values()[j];
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+}

+ 279 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java

@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture.plugins.ktx;
+
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.opengl.GLImageFormat;
+import com.jme3.renderer.opengl.GLImageFormats;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.Texture3D;
+import com.jme3.texture.TextureArray;
+import com.jme3.texture.TextureCubeMap;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * This class allows one to write a KTX file.
+ * It doesn't support compressed data yet.
+ * 
+ * @author Nehon
+ */
+public class KTXWriter {
+
+    private final static Logger log = Logger.getLogger(KTXWriter.class.getName());
+    
+    private final String filePath;
+
+    private final static byte[] fileIdentifier = {
+        (byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
+    };
+
+    /**
+     * Creates a KTXWriter that will write files in the given path
+     * @param path 
+     */
+    public KTXWriter(String path) {
+        filePath = path;
+    }
+
+    /**
+     * Writes a 2D image from the given image in a KTX file named from the fileName param
+     * Note that the fileName should contain the extension (.ktx sounds like a wise choice)
+     * @param image the image to write
+     * @param fileName the name of the file to write
+     */
+    public void write(Image image, String fileName) {
+        write(image, Texture2D.class, fileName);
+    }
+
+    /**
+     * Writes an image with the given params
+     * 
+     * textureType, allows one to write textureArrays, Texture3D, and TextureCubeMaps.
+     * Texture2D will write a 2D image.
+     * Note that the fileName should contain the extension (.ktx sounds like a wise choice)
+     * @param image the image to write
+     * @param textureType the texture type
+     * @param fileName the name of the file to write
+     */
+    public void write(Image image, Class<? extends Texture> textureType, String fileName) {
+
+        FileOutputStream outs = null;
+        try {
+            File file = new File(filePath + "/" + fileName);            
+            outs = new FileOutputStream(file);
+
+            DataOutput out = new DataOutputStream(outs);
+
+            //fileID
+            out.write(fileIdentifier);
+            //endianness
+            out.writeInt(0x04030201);
+            GLImageFormat format = getGlFormat(image.getFormat());
+            //glType
+            out.writeInt(format.dataType);
+            //glTypeSize
+            out.writeInt(1);
+            //glFormat
+            out.writeInt(format.format);
+            //glInernalFormat
+            out.writeInt(format.internalFormat);
+            //glBaseInternalFormat
+            out.writeInt(format.format);
+            //pixelWidth
+            out.writeInt(image.getWidth());
+            //pixelHeight
+            out.writeInt(image.getHeight());
+
+            int pixelDepth = 1;
+            int numberOfArrayElements = 1;
+            int numberOfFaces = 1;
+            if (image.getDepth() > 1) {
+                //pixelDepth
+                if (textureType == Texture3D.class) {
+                    pixelDepth = image.getDepth();
+                }
+            }
+            if(image.getData().size()>1){
+                //numberOfArrayElements
+                if (textureType == TextureArray.class) {
+                    numberOfArrayElements = image.getData().size();
+                }
+                //numberOfFaces                
+                if (textureType == TextureCubeMap.class) {
+                    numberOfFaces = image.getData().size();
+                }
+            }
+            out.writeInt(pixelDepth);
+            out.writeInt(numberOfArrayElements);
+            out.writeInt(numberOfFaces);
+
+            int numberOfMipmapLevels = 1;
+            //numberOfMipmapLevels
+            if (image.hasMipmaps()) {
+                numberOfMipmapLevels = image.getMipMapSizes().length;
+            }
+            out.writeInt(numberOfMipmapLevels);
+
+            //bytesOfKeyValueData
+            String keyValues = "KTXorientation\0S=r,T=u\0";
+            int bytesOfKeyValueData = keyValues.length() + 4;
+            int padding = 3 - ((bytesOfKeyValueData + 3) % 4);
+            bytesOfKeyValueData += padding;
+            out.writeInt(bytesOfKeyValueData);
+
+            //keyAndValueByteSize
+            out.writeInt(bytesOfKeyValueData - 4 - padding);
+            //values
+            out.writeBytes(keyValues);
+            pad(padding, out);
+
+            int offset = 0;
+            //iterate over data
+            for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+
+                int width = Math.max(1, image.getWidth() >> mipLevel);
+                int height = Math.max(1, image.getHeight() >> mipLevel);
+                
+                int imageSize;
+
+                if (image.hasMipmaps()) {
+                    imageSize = image.getMipMapSizes()[mipLevel];
+                } else {
+                    imageSize = width * height * image.getFormat().getBitsPerPixel() / 8;
+                }
+                out.writeInt(imageSize);
+
+                for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
+                    for (int face = 0; face < numberOfFaces; face++) {
+                        int nbPixelWritten = 0;
+                        for (int depth = 0; depth < pixelDepth; depth++) {
+                            ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));
+                            // BufferUtils.ensureLargeEnough(byteBuffer, imageSize);
+                            log.log(Level.FINE, "position {0}", byteBuffer.position());
+                            byteBuffer.position(offset);
+                            byte[] b = getByteBufferArray(byteBuffer, imageSize);
+                            out.write(b);
+
+                            nbPixelWritten = b.length;
+                        }
+                        //cube padding
+                        if (numberOfFaces == 6 && numberOfArrayElements == 0) {
+                            padding = 3 - ((nbPixelWritten + 3) % 4);
+                            pad(padding, out);
+                        }
+                    }
+                }
+                //mip padding
+                log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
+                padding = 3 - ((imageSize + 3) % 4);
+                pad(padding, out);
+                offset += imageSize;
+            }
+
+        } catch (FileNotFoundException ex) {
+            Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+        } catch (IOException ex) {
+            Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+        } finally {
+            try {
+                if(outs != null){
+                    outs.close();
+                }
+            } catch (IOException ex) {
+                Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+
+    /**
+     * writes padding data to the output padding times.
+     * @param padding
+     * @param out
+     * @throws IOException 
+     */
+    private void pad(int padding, DataOutput out) throws IOException {
+        //padding
+        for (int i = 0; i < padding; i++) {
+            out.write('\0');
+        }
+    }
+
+    /**
+     * Get a byte array from a byte buffer.
+     * @param byteBuffer the  byte buffer
+     * @param size the size of the resulting array
+     * @return 
+     */
+    private byte[] getByteBufferArray(ByteBuffer byteBuffer, int size) {
+        byte[] b;
+        if (byteBuffer.hasArray()) {
+            b = byteBuffer.array();
+        } else {
+            b = new byte[size];
+            byteBuffer.get(b, 0, size);
+        }
+        return b;
+    }
+
+    /**
+     * get the glformat from JME image Format
+     * @param format
+     * @return 
+     */
+    private GLImageFormat getGlFormat(Image.Format format) {
+        EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
+        GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
+        return formats[0][format.ordinal()];
+    }
+
+    /**
+     * get a slice from the face and the array index
+     * @param face
+     * @param arrayElem
+     * @return 
+     */
+    private static int getSlice(int face,int arrayElem) {
+        return Math.max(face,  arrayElem);
+    }
+}

+ 46 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * Interface used to read a set of pixels in a KTX file
+ * @author Nehon
+ */
+public interface PixelReader {
+
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException;
+}

+ 60 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * reads the pixels of an image whose origin is the top left corner
+ *
+ * @author Nehon
+ */
+public class SrTdRiPixelReader implements PixelReader {
+
+    @Override
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
+        int pixelRead = 0;
+        for (int row = pixelHeight - 1; row >= 0; row--) {
+            for (int pixel = 0; pixel < pixelWidth; pixel++) {
+                in.readFully(pixelData);
+                for (int i = 0; i < pixelData.length; i++) {
+                    buffer.put((row * pixelWidth + pixel) * pixelData.length + i, pixelData[i]);
+                }
+                pixelRead += pixelData.length;
+            }
+        }
+        return pixelRead;
+    }
+
+}

+ 58 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * reads the pixels of an image whose origin is the bottom left corner
+ *
+ * @author Nehon
+ */
+public class SrTuRoPixelReader implements PixelReader {
+
+    @Override
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
+        int pixelRead = 0;
+        for (int row = 0; row < pixelHeight; row++) {
+            for (int pixel = 0; pixel < pixelWidth; pixel++) {
+                in.readFully(pixelData);
+                buffer.put(pixelData);
+                pixelRead += pixelData.length;
+            }
+        }
+        return pixelRead;
+    }
+
+}