package k8;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import k8.util.LinkedList;
import k8.util.PixelStoreState;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public class Texture
{
private static LinkedList<Texture> textures = new LinkedList<Texture>();
private int texture_id;
private LinkedList.Node<Texture> listnode;
/** Destroys all Texture instances. */
public static void destroyAll()
{
// Call destroy() on all Texture instances
LinkedList.Node<Texture> curr = null;
while ((curr = textures.next(curr)) != null)
curr.item.destroy();
}
/** Creates a new instance of Texture */
public Texture()
{
texture_id = 0;
listnode = null;
}
/** Creates a new instance of Texture */
public Texture(String name)
{
URL url = this.getClass().getResource(name);
BufferedImage tex = null;
try
{
tex = ImageIO.read(url);
} catch (IOException e)
{
k8.logger.warning("Unable to find texture " + name);
return;
}
generate(tex, GL11.GL_RGB4);
}
/**
* Generates the texture.
*
* @param image
* A BufferedImage
* @param interanlFormat
* Internal format used by OpenGL
*/
public void generate(BufferedImage image, int internalFormat)
{
// Don't proceed if we were given crap data
if (image == null || internalFormat == 0)
{
return;
}
// Acquire dimensions and pixel data of image
int width = image.getWidth();
int height = image.getHeight();
byte data[] = (byte[]) image.getRaster().getDataElements(0, 0, width,
height, null);
// Put the pixel data of the BufferedImage into a ByteBuffer
ByteBuffer pixel_buffer = ByteBuffer.allocateDirect(3 * width * height);
pixel_buffer.put(data);
pixel_buffer.rewind();
// Generate the texture
generate(pixel_buffer, width, height, internalFormat);
}
/**
* Generates the texture.
*
* @param pixel_buffer
* Pixel data
* @param width
* Width
* @param height
* Height
* @param interanlFormat
* Internal format used by OpenGL
*/
public void generate(ByteBuffer pixel_buffer, int width, int height,
int internalFormat)
{
// Don't proceed if we were given crap data
if (pixel_buffer == null || width <= 0 || height <= 0
|| internalFormat == 0)
{
return;
}
// Destroy any previous generation
if (texture_id != 0)
{
destroy();
}
// Make sure the ByteBuffer is rewound
pixel_buffer.rewind();
// Create a texture id
IntBuffer tex_buffer = ByteBuffer.allocateDirect(4).order(
ByteOrder.nativeOrder()).asIntBuffer();
GL11.glGenTextures(tex_buffer);
texture_id = tex_buffer.get(0);
// Create a MipMapped texture
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture_id);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR_MIPMAP_LINEAR);
Texture.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, internalFormat, width,
height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, pixel_buffer);
// Add this texture to the list of generated textures
listnode = textures.append(this);
}
/**
* Builds Mipmaps.
*
* @param target
* @param components
* @param width
* @param height
* @param format
* @param type
* @param data
*
* @return int
*/
private static void gluBuild2DMipmaps(final int target,
final int components, final int width, final int height,
final int format, final int type, final ByteBuffer data)
{
if (width < 1 || height < 1)
return;
final int bpp = Texture.bytesPerPixel(format, type);
if (bpp == 0)
return;
final int maxSize = PixelStoreState
.glGetIntegerv(GL11.GL_MAX_TEXTURE_SIZE);
int w = Texture.nearestPower(width);
if (w > maxSize)
w = maxSize;
int h = Texture.nearestPower(height);
if (h > maxSize)
h = maxSize;
// Get current glPixelStore state
PixelStoreState pss = new PixelStoreState();
// set pixel packing
GL11.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
GL11.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 0);
ByteBuffer image;
boolean done = false;
if (w != width || h != height)
{
// Must rescale image to get "top" mipmap texture image
image = BufferUtils.createByteBuffer((w + 4) * h * bpp);
if (!Texture.gluScaleImage(format, width, height, type, data, w, h,
type, image))
done = true;
/* set pixel unpacking */
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
} else
{
image = data;
}
ByteBuffer bufferA = null;
ByteBuffer bufferB = null;
int level = 0;
while (!done)
{
if (image != data)
{
/* set pixel unpacking */
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
}
GL11.glTexImage2D(target, level, components, w, h, 0, format, type,
image);
if (w == 1 && h == 1)
break;
final int newW = (w < 2) ? 1 : w >> 1;
final int newH = (h < 2) ? 1 : h >> 1;
final ByteBuffer newImage;
if (bufferA == null)
newImage = (bufferA = BufferUtils.createByteBuffer((newW + 4)
* newH * bpp));
else if (bufferB == null)
newImage = (bufferB = BufferUtils.createByteBuffer((newW + 4)
* newH * bpp));
else
newImage = bufferB;
if (!Texture.gluScaleImage(format, w, h, type, image, newW, newH,
type, newImage))
done = true;
image = newImage;
if (bufferB != null)
bufferB = bufferA;
w = newW;
h = newH;
level++;
}
// Restore original glPixelStore state
pss.save();
}
/**
* Method bytesPerPixel.
*
* @param format
* @param type
*
* @return int
*/
private static int bytesPerPixel(int format, int type)
{
int n, m;
switch (format)
{
case GL11.GL_COLOR_INDEX:
case GL11.GL_STENCIL_INDEX:
case GL11.GL_DEPTH_COMPONENT:
case GL11.GL_RED:
case GL11.GL_GREEN:
case GL11.GL_BLUE:
case GL11.GL_ALPHA:
case GL11.GL_LUMINANCE:
n = 1;
break;
case GL11.GL_LUMINANCE_ALPHA:
n = 2;
break;
case GL11.GL_RGB:
case GL12.GL_BGR:
n = 3;
break;
case GL11.GL_RGBA:
case GL12.GL_BGRA:
n = 4;
break;
default:
n = 0;
}
switch (type)
{
case GL11.GL_UNSIGNED_BYTE:
m = 1;
break;
case GL11.GL_BYTE:
m = 1;
break;
case GL11.GL_BITMAP:
m = 1;
break;
case GL11.GL_UNSIGNED_SHORT:
m = 2;
break;
case GL11.GL_SHORT:
m = 2;
break;
case GL11.GL_UNSIGNED_INT:
m = 4;
break;
case GL11.GL_INT:
m = 4;
break;
case GL11.GL_FLOAT:
m = 4;
break;
default:
m = 0;
}
return n * m;
}
/**
* Method nearestPower.
*
* Compute the nearest power of 2 number. This algorithm is a little
* strange, but it works quite well.
*
* @param value
*
* @return int
*/
private static int nearestPower(int value)
{
int i = 1;
/* Error! */
if (value == 0)
return -1;
for (;;)
{
if (value == 1)
{
return i;
} else if (value == 3)
{
return i << 2;
}
value >>= 1;
i <<= 1;
}
}
/**
* Method gluScaleImage.
*
* @param format
* @param widthIn
* @param heightIn
* @param typein
* @param dataIn
* @param widthOut
* @param heightOut
* @param typeOut
* @param dataOut
*
* @return int
*/
private static boolean gluScaleImage(int format, int widthIn, int heightIn,
int typein, ByteBuffer dataIn, int widthOut, int heightOut,
int typeOut, ByteBuffer dataOut)
{
final int components = Texture.compPerPix(format);
if (components == -1)
return false;
int i, j, k;
float[] tempIn, tempOut;
float sx, sy;
int sizein, sizeout;
int rowstride, rowlen;
// temp image data
tempIn = new float[widthIn * heightIn * components];
tempOut = new float[widthOut * heightOut * components];
// Determine bytes per input type
switch (typein)
{
case GL11.GL_UNSIGNED_BYTE:
sizein = 1;
break;
case GL11.GL_FLOAT:
sizein = 4;
break;
default:
return false;
}
// Determine bytes per output type
switch (typeOut)
{
case GL11.GL_UNSIGNED_BYTE:
sizeout = 1;
break;
case GL11.GL_FLOAT:
sizeout = 4;
break;
default:
return false;
}
// Get glPixelStore state
PixelStoreState pss = new PixelStoreState();
// Unpack the pixel data and convert to floating point
if (pss.unpackRowLength > 0)
rowlen = pss.unpackRowLength;
else
rowlen = widthIn;
if (sizein >= pss.unpackAlignment)
rowstride = components * rowlen;
else
rowstride = pss.unpackAlignment
/ sizein
* Texture.ceil(components * rowlen * sizein,
pss.unpackAlignment);
switch (typein)
{
case GL11.GL_UNSIGNED_BYTE:
k = 0;
dataIn.rewind();
for (i = 0; i < heightIn; i++)
{
int ubptr = i * rowstride + pss.unpackSkipRows * rowstride
+ pss.unpackSkipPixels * components;
for (j = 0; j < widthIn * components; j++)
{
tempIn[k++] = dataIn.get(ubptr++) & 0xff;
}
}
break;
case GL11.GL_FLOAT:
k = 0;
dataIn.rewind();
for (i = 0; i < heightIn; i++)
{
int fptr = 4 * (i * rowstride + pss.unpackSkipRows * rowstride + pss.unpackSkipPixels
* components);
for (j = 0; j < widthIn * components; j++)
{
tempIn[k++] = dataIn.getFloat(fptr);
fptr += 4;
}
}
break;
default:
return false;
}
// Do scaling
sx = (float) widthIn / (float) widthOut;
sy = (float) heightIn / (float) heightOut;
float[] c = new float[components];
int src, dst;
for (int iy = 0; iy < heightOut; iy++)
{
for (int ix = 0; ix < widthOut; ix++)
{
int x0 = (int) (ix * sx);
int x1 = (int) ((ix + 1) * sx);
int y0 = (int) (iy * sy);
int y1 = (int) ((iy + 1) * sy);
int readPix = 0;
// reset weighted pixel
for (int ic = 0; ic < components; ic++)
{
c[ic] = 0;
}
// create weighted pixel
for (int ix0 = x0; ix0 < x1; ix0++)
{
for (int iy0 = y0; iy0 < y1; iy0++)
{
src = (iy0 * widthIn + ix0) * components;
for (int ic = 0; ic < components; ic++)
{
c[ic] += tempIn[src + ic];
}
readPix++;
}
}
// store weighted pixel
dst = (iy * widthOut + ix) * components;
if (readPix == 0)
{
// Image is sized up, caused by non power of two texture as
// input
src = (y0 * widthIn + x0) * components;
for (int ic = 0; ic < components; ic++)
{
tempOut[dst++] = tempIn[src + ic];
}
} else
{
// sized down
for (k = 0; k < components; k++)
{
tempOut[dst++] = c[k] / readPix;
}
}
}
}
// Convert temp output
if (pss.packRowLength > 0)
rowlen = pss.packRowLength;
else
rowlen = widthOut;
if (sizeout >= pss.packAlignment)
rowstride = components * rowlen;
else
rowstride = pss.packAlignment
/ sizeout
* Texture.ceil(components * rowlen * sizeout,
pss.packAlignment);
switch (typeOut)
{
case GL11.GL_UNSIGNED_BYTE:
k = 0;
for (i = 0; i < heightOut; i++)
{
int ubptr = i * rowstride + pss.packSkipRows * rowstride
+ pss.packSkipPixels * components;
for (j = 0; j < widthOut * components; j++)
{
dataOut.put(ubptr++, (byte) tempOut[k++]);
}
}
break;
case GL11.GL_FLOAT:
k = 0;
for (i = 0; i < heightOut; i++)
{
int fptr = 4 * (i * rowstride + pss.unpackSkipRows * rowstride + pss.unpackSkipPixels
* components);
for (j = 0; j < widthOut * components; j++)
{
dataOut.putFloat(fptr, tempOut[k++]);
fptr += 4;
}
}
break;
default:
return false;
}
return true;
}
/**
* Return ceiling of integer division
*
* @param a
* @param b
*
* @return int
*/
private static int ceil(int a, int b)
{
return (a % b == 0 ? a / b : a / b + 1);
}
/**
* Determine number of components per pixel.
*
* @param format
*
* @return int
*/
private static int compPerPix(int format)
{
switch (format)
{
case GL11.GL_COLOR_INDEX:
case GL11.GL_STENCIL_INDEX:
case GL11.GL_DEPTH_COMPONENT:
case GL11.GL_RED:
case GL11.GL_GREEN:
case GL11.GL_BLUE:
case GL11.GL_ALPHA:
case GL11.GL_LUMINANCE:
return 1;
case GL11.GL_LUMINANCE_ALPHA:
return 2;
case GL11.GL_RGB:
case GL12.GL_BGR:
return 3;
case GL11.GL_RGBA:
case GL12.GL_BGRA:
return 4;
default:
return -1;
}
}
/** Deletes the texture from OpenGL */
public void destroy()
{
if (texture_id != 0)
{
IntBuffer tex_buf = ByteBuffer.allocateDirect(4).order(
ByteOrder.nativeOrder()).asIntBuffer();
tex_buf.put(texture_id);
tex_buf.rewind();
GL11.glDeleteTextures(tex_buf);
texture_id = 0;
textures.remove(listnode);
}
}
/** Makes current texture for rendering */
public void glBindTexture()
{
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture_id);
}
}