KTX file loading and writing support
This commit is contained in:
parent
cecf355c70
commit
8a96772ae3
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user