package org.newdawn.slick.opengl;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.util.ResourceLoader;
/**
* A texture loaded based on many old versions that will load image data from a file
* and produce OpenGL textures.
*
* @see ImageData
*
* @author kevin
*/
public class InternalTextureLoader {
/** The standard texture loaded used everywhere */
private static final InternalTextureLoader loader = new InternalTextureLoader();
/**
* Get the single instance of this texture loader
*
* @return The single instance of the texture loader
*/
public static InternalTextureLoader get() {
return loader;
}
/** The table of textures that have been loaded in this loader */
private HashMap texturesLinear = new HashMap();
/** The table of textures that have been loaded in this loader */
private HashMap texturesNearest = new HashMap();
/** The destination pixel format */
private int dstPixelFormat = GL11.GL_RGBA;
/** True if we're using deferred loading */
private boolean deferred;
/**
* Create a new texture loader based on the game panel
*/
private InternalTextureLoader() {
}
/**
* True if we should only record the request to load in the intention
* of loading the texture later
*
* @param deferred True if the we should load a token
*/
public void setDeferredLoading(boolean deferred) {
this.deferred = deferred;
}
/**
* Check if we're using deferred loading
*
* @return True if we're loading deferred textures
*/
public boolean isDeferredLoading() {
return deferred;
}
/**
* Clear out the cached textures
*/
public void clear() {
texturesLinear.clear();
texturesNearest.clear();
}
/**
* Tell the loader to produce 16 bit textures
*/
public void set16BitMode() {
dstPixelFormat = GL11.GL_RGBA16;
}
/**
* Create a new texture ID
*
* @return A new texture ID
*/
public static int createTextureID()
{
IntBuffer tmp = createIntBuffer(1);
GL11.glGenTextures(tmp);
return tmp.get(0);
}
/**
* Get a texture from a specific file
*
* @param source The file to load the texture from
* @param flipped True if we should flip the texture on the y axis while loading
* @param filter The filter to use
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public Texture getTexture(File source, boolean flipped,int filter) throws IOException {
String resourceName = source.getAbsolutePath();
InputStream in = new FileInputStream(source);
return getTexture(in, resourceName, flipped, filter, null);
}
/**
* Get a texture from a specific file
*
* @param source The file to load the texture from
* @param flipped True if we should flip the texture on the y axis while loading
* @param filter The filter to use
* @param transparent The colour to interpret as transparent or null if none
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public Texture getTexture(File source, boolean flipped,int filter, int[] transparent) throws IOException {
String resourceName = source.getAbsolutePath();
InputStream in = new FileInputStream(source);
return getTexture(in, resourceName, flipped, filter, transparent);
}
/**
* Get a texture from a resource location
*
* @param resourceName The location to load the texture from
* @param flipped True if we should flip the texture on the y axis while loading
* @param filter The filter to use when scaling the texture
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public Texture getTexture(String resourceName, boolean flipped, int filter) throws IOException {
InputStream in = ResourceLoader.getResourceAsStream(resourceName);
return getTexture(in, resourceName, flipped, filter, null);
}
/**
* Get a texture from a resource location
*
* @param resourceName The location to load the texture from
* @param flipped True if we should flip the texture on the y axis while loading
* @param filter The filter to use when scaling the texture
* @param transparent The colour to interpret as transparent or null if none
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public Texture getTexture(String resourceName, boolean flipped, int filter, int[] transparent) throws IOException {
HashMap hash = texturesLinear;
if (filter == GL11.GL_NEAREST) {
hash = texturesNearest;
}
String resName = resourceName;
if (transparent != null) {
resName += ":"+transparent[0]+":"+transparent[1]+":"+transparent[2];
}
SoftReference ref = (SoftReference) hash.get(resName);
if (ref != null) {
TextureImpl tex = (TextureImpl) ref.get();
if (tex != null) {
return tex;
} else {
hash.remove(resName);
}
}
InputStream in = ResourceLoader.getResourceAsStream(resourceName);
return getTexture(in, resourceName, flipped, filter, transparent);
}
/**
* Get a texture from a image file
*
* @param in The stream from which we can load the image
* @param resourceName The name to give this image in the internal cache
* @param flipped True if we should flip the image on the y-axis while loading
* @param filter The filter to use when scaling the texture
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public Texture getTexture(InputStream in, String resourceName, boolean flipped, int filter) throws IOException {
return getTexture(in, resourceName, flipped, filter, null);
}
/**
* Get a texture from a image file
*
* @param in The stream from which we can load the image
* @param resourceName The name to give this image in the internal cache
* @param flipped True if we should flip the image on the y-axis while loading
* @param filter The filter to use when scaling the texture
* @param transparent The colour to interpret as transparent or null if none
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
public TextureImpl getTexture(InputStream in, String resourceName, boolean flipped, int filter, int[] transparent) throws IOException {
if (deferred) {
return new DeferredTexture(in, resourceName, flipped, filter, transparent);
}
HashMap hash = texturesLinear;
if (filter == GL11.GL_NEAREST) {
hash = texturesNearest;
}
String resName = resourceName;
if (transparent != null) {
resName += ":"+transparent[0]+":"+transparent[1]+":"+transparent[2];
}
SoftReference ref = (SoftReference) hash.get(resName);
if (ref != null) {
TextureImpl tex = (TextureImpl) ref.get();
if (tex != null) {
return tex;
} else {
hash.remove(resName);
}
}
// horrible test until I can find something more suitable
try {
GL11.glGetError();
} catch (NullPointerException e) {
throw new RuntimeException("Image based resources must be loaded as part of init() or the game loop. They cannot be loaded before initialisation.");
}
TextureImpl tex = getTexture(in, resourceName,
GL11.GL_TEXTURE_2D,
filter,
filter, flipped, transparent);
hash.put(resName, new SoftReference(tex));
return tex;
}
/**
* Get a texture from a image file
*
* @param in The stream from which we can load the image
* @param resourceName The name to give this image in the internal cache
* @param flipped True if we should flip the image on the y-axis while loading
* @param target The texture target we're loading this texture into
* @param minFilter The scaling down filter
* @param magFilter The scaling up filter
* @param transparent The colour to interpret as transparent or null if none
* @return The texture loaded
* @throws IOException Indicates a failure to load the image
*/
private TextureImpl getTexture(InputStream in,
String resourceName,
int target,
int magFilter,
int minFilter, boolean flipped, int[] transparent) throws IOException
{
// create the texture ID for this texture
int textureID = createTextureID();
TextureImpl texture = new TextureImpl(resourceName, target, textureID);
// bind this texture
GL11.glBindTexture(target, textureID);
ByteBuffer textureBuffer;
int width;
int height;
int texWidth;
int texHeight;
boolean hasAlpha;
LoadableImageData imageData = ImageDataFactory.getImageDataFor(resourceName);
textureBuffer = imageData.loadImage(new BufferedInputStream(in), flipped, transparent);
width = imageData.getWidth();
height = imageData.getHeight();
hasAlpha = imageData.getDepth() == 32;
texture.setTextureWidth(imageData.getTexWidth());
texture.setTextureHeight(imageData.getTexHeight());
texWidth = texture.getTextureWidth();
texHeight = texture.getTextureHeight();
IntBuffer temp = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE, temp);
int max = temp.get(0);
if ((texWidth > max) || (texHeight > max)) {
throw new IOException("Attempt to allocate a texture to big for the current hardware");
}
int srcPixelFormat = hasAlpha ? GL11.GL_RGBA : GL11.GL_RGB;
int componentCount = hasAlpha ? 4 : 3;
texture.setWidth(width);
texture.setHeight(height);
texture.setAlpha(hasAlpha);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
// produce a texture from the byte buffer
GL11.glTexImage2D(target,
0,
dstPixelFormat,
get2Fold(width),
get2Fold(height),
0,
srcPixelFormat,
GL11.GL_UNSIGNED_BYTE,
textureBuffer);
return texture;
}
/**
* Create an empty texture
*
* @param width The width of the new texture
* @param height The height of the new texture
* @return The created empty texture
* @throws IOException Indicates a failure to create the texture on the graphics hardware
*/
public Texture createTexture(final int width, final int height) throws IOException {
ImageData ds = new EmptyImageData(width, height);
return getTexture(ds, GL11.GL_NEAREST);
}
/**
* Get a texture from a image file
*
* @param dataSource The image data to generate the texture from
* @param filter The filter to use when scaling the texture
* @return The texture created
* @throws IOException Indicates the texture is too big for the hardware
*/
public Texture getTexture(ImageData dataSource, int filter) throws IOException
{
int target = GL11.GL_TEXTURE_2D;
// create the texture ID for this texture
int textureID = createTextureID();
TextureImpl texture = new TextureImpl("generated:"+dataSource, target ,textureID);
int minFilter = filter;
int magFilter = filter;
boolean flipped = false;
// bind this texture
GL11.glBindTexture(target, textureID);
ByteBuffer textureBuffer;
int width;
int height;
int texWidth;
int texHeight;
boolean hasAlpha;
textureBuffer = dataSource.getImageBufferData();
width = dataSource.getWidth();
height = dataSource.getHeight();
hasAlpha = dataSource.getDepth() == 32;
texture.setTextureWidth(dataSource.getTexWidth());
texture.setTextureHeight(dataSource.getTexHeight());
texWidth = texture.getTextureWidth();
texHeight = texture.getTextureHeight();
int srcPixelFormat = hasAlpha ? GL11.GL_RGBA : GL11.GL_RGB;
int componentCount = hasAlpha ? 4 : 3;
texture.setWidth(width);
texture.setHeight(height);
texture.setAlpha(hasAlpha);
IntBuffer temp = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE, temp);
int max = temp.get(0);
if ((texWidth > max) || (texHeight > max)) {
throw new IOException("Attempt to allocate a texture to big for the current hardware");
}
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
// produce a texture from the byte buffer
GL11.glTexImage2D(target,
0,
dstPixelFormat,
get2Fold(width),
get2Fold(height),
0,
srcPixelFormat,
GL11.GL_UNSIGNED_BYTE,
textureBuffer);
return texture;
}
/**
* Get the closest greater power of 2 to the fold number
*
* @param fold The target number
* @return The power of 2
*/
public static int get2Fold(int fold) {
int ret = 2;
while (ret < fold) {
ret *= 2;
}
return ret;
}
/**
* Creates an integer buffer to hold specified ints
* - strictly a utility method
*
* @param size how many int to contain
* @return created IntBuffer
*/
public static IntBuffer createIntBuffer(int size) {
ByteBuffer temp = ByteBuffer.allocateDirect(4 * size);
temp.order(ByteOrder.nativeOrder());
return temp.asIntBuffer();
}
}