diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java new file mode 100644 index 000000000..376774a0d --- /dev/null +++ b/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 imageData = new ArrayList(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 = 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; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java new file mode 100644 index 000000000..fbbdc8972 --- /dev/null +++ b/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 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 = 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); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java new file mode 100644 index 000000000..61eac655c --- /dev/null +++ b/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; +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java new file mode 100644 index 000000000..50397ff7d --- /dev/null +++ b/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; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java new file mode 100644 index 000000000..a1f7751f9 --- /dev/null +++ b/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; + } + +}