BufferedImage to lwjgl texture

Hi guys!
I have a simple question about converting a bufferedimage from java.awt library to lwjgl Texture. Basically i want to bind this texture to opengl (“texture.bind()”) after i do some cropping from the main image (using “image.getSubImage(x,y,width,height)” or cropImageFilter()"). It’s supposed to make a few textures from a bigger image just like minecraft does for it’s fonts and so on. So can anyone give me a snippet how could i do this?

  1. Load image with ImageIO or any other method.
  2. Crop out the different parts you want to smaller BufferedImages using getSubImage(…)
    For each sub image:

        int[] pixels = new int[image.getWidth() * image.getHeight()];
        image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

        ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB
        
        for(int y = 0; y < image.getHeight(); y++){
            for(int x = 0; x < image.getWidth(); x++){
                int pixel = pixels[y * image.getWidth() + x];
                buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component
                buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component
                buffer.put((byte) (pixel & 0xFF));	            // Blue component
                buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA
            }
        }

        buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS

        // You now have a ByteBuffer filled with the color data of each pixel.
        // Now just create a texture ID and bind it. Then you can load it using 
        // whatever OpenGL method you want, for example:
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8A, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

I can’t get it to work. I get alot of errors like invalid enum or null pointer location using various methods in opengl. At the end i tryed displaying this buffer with drawPixels() method which kinda worked but image was completly messed up. Can you please show me how to bind this buffer, to gl_quads for example, correctly? I don’t know exactly what im doing couse i started learning java about a month ago. Thanks for your help!

If you’re new to Java, then I recommend you to stay away from OpenGL and stick to Java2D which is easier to use.

Anyway, if you are using LWJGL I can give you a working example program, but if you’re using JOGL you’ll have to adapt it slightly yourself. Which one are you using?

Im using LWJGL. I mean i made some pretty cool stuff already in opengl using textures mapping i just didn’t get the chance to learn using this buffers yet.

package YOUR.PACKAGE.HERE;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL12;

import static org.lwjgl.opengl.GL11.*;

public class BasicTextureTest {
	
	private static final int WIDTH = 800, HEIGHT = 600;
	
	public static void main(String[] args){
		
		try{
			Display.setDisplayMode(new DisplayMode(800, 600));
			Display.create();
		}catch(LWJGLException e){
			e.printStackTrace();
		}
		
		glMatrixMode(GL_PROJECTION);
		glOrtho(0, WIDTH, HEIGHT, 0, -1, 1); //2D projection matrix
		glMatrixMode(GL_MODELVIEW);
		
		glClearColor(0, 1, 0, 0); //Green clear color
		
		
		//Generate a small test image by drawing to a BufferedImage
		//It's of course also possible to just load an image using ImageIO.load()
		BufferedImage test = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = test.createGraphics();

		g2d.setColor(new Color(1.0f, 1.0f, 1.0f, 0.5f));
		g2d.fillRect(0, 0, 128, 128); //A transparent white background
		
		g2d.setColor(Color.red);
		g2d.drawRect(0, 0, 127, 127); //A red frame around the image
		g2d.fillRect(10, 10, 10, 10); //A red box 
		
		g2d.setColor(Color.blue);
		g2d.drawString("Test image", 10, 64); //Some blue text
		
		int textureID = loadTexture(test);
		
		glEnable(GL_TEXTURE_2D); //Enable texturing
		
		
		while(!Display.isCloseRequested()){
			glClear(GL_COLOR_BUFFER_BIT);
			
			//Enable blending so the green background can be seen through the texture
			glEnable(GL_BLEND);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			
			glPushMatrix();
			glTranslatef(100, 100, 0);
			glBindTexture(GL_TEXTURE_2D, textureID);
			glBegin(GL_QUADS);
			{
				glTexCoord2f(0, 0);
				glVertex2f(0, 0);
				
				glTexCoord2f(1, 0);
				glVertex2f(128, 0);
				
				glTexCoord2f(1, 1);
				glVertex2f(128, 128);
				
				glTexCoord2f(0, 1);
				glVertex2f(0, 128);
			}
			glEnd();
			glPopMatrix();
			
			Display.update();
		}
	}
	
	private static final int BYTES_PER_PIXEL = 4;
	public static int loadTexture(BufferedImage image){
		
		int[] pixels = new int[image.getWidth() * image.getHeight()];
        image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

        ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB
        
        for(int y = 0; y < image.getHeight(); y++){
            for(int x = 0; x < image.getWidth(); x++){
                int pixel = pixels[y * image.getWidth() + x];
                buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component
                buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component
                buffer.put((byte) (pixel & 0xFF));               // Blue component
                buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA
            }
        }

        buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS

        // You now have a ByteBuffer filled with the color data of each pixel.
        // Now just create a texture ID and bind it. Then you can load it using 
        // whatever OpenGL method you want, for example:

		int textureID = glGenTextures(); //Generate texture ID
        glBindTexture(GL_TEXTURE_2D, textureID); //Bind texture ID
        
        //Setup wrap mode
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);

        //Setup texture scaling filtering
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        
        //Send texel data to OpenGL
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
		
        //Return the texture ID so we can bind it later again
		return textureID;
	}
}

You should be able to extract what you want from this program. Note that I generate an image by drawing to a BufferedImage.

It works great! I load it to gluPerspective and it finaly works. Thank you so much! :slight_smile:

No problem! =D Increasing the OpenGL population is something I enjoy doing!

:o I was just looking for this. Thanks theagentd!!! ;D

one more thanks :wink:

Well, as this thread has been woken up … :wink:

I’m using a slightly different technique to write the pixel data to the texture, which may be useful and potentially faster if you have to update the texture from the pixel data (or BufferedImage) regularly. It saves on doing all the bit massaging in Java by using the fact that GL_BGRA is the same as ARGB in reverse. Alpha bias is used to correct the data if the pixel data has zero alpha values. The data is obviously still going to need to be massaged, but hopefully doing it on the OpenGL side will be faster.

NB. My textures are already allocated (as RGBA8) by this point.


int size = width * height;
if (buffer == null || buffer.capacity() < size) {
    buffer = BufferUtils.createIntBuffer(size);
}
texture.bind();
buffer.rewind();
buffer.put(pixels, 0, size);
buffer.rewind();
if (!alpha) {
    GL11.glPixelTransferf(GL11.GL_ALPHA_BIAS, 1);
}
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D,
     0,
     0,
     0,
     width,
     height,
     GL12.GL_BGRA,
     GL12.GL_UNSIGNED_INT_8_8_8_8_REV,
     buffer);
if (!alpha) {
   GL11.glPixelTransferf(GL11.GL_ALPHA_BIAS, 0);
}

Yes, this should work perfectly fine and have much better performance assuming the swizzling is done on the GPU (which I see no reason for not happening) assuming it works. I’ve always thought that color is stored in ARGB in BufferedImages though. Is that not the case? You’re using BGRA? Ah, GL_UNSIGNED_INT_8_8_8_8_REV. REV = reverse? Clever.

Then the only problem with this is that it transfers the alpha data too which consumes some extra bandwidth if it’s not needed, but what the hell, the trade-off is still worth it. It also reduces the complexity of the loading, so I guess this is the best way of loading textures. Be sure to explain to newbies exactly what’s happening in when doing this if you give them that code though, since it’s far from clear how and why it works. =S Or just link them here, I guess. =D

The way the pixels are stored is determined by the 3rd parameter in the constructor:

BufferedImage(int width, int height, int imageType)

TYPE_USHORT_565_RGB, TYPE_4BYTE_ABGR, TYPE_INT_ARGB, etc.

Well, I think we all just plug the BufferedImage we get from ImageIO.load() into our renderers though… :wink: It seems to always use TYPE_INT_ARGB though, right?

No, not always. On MacOS I’ve had it in the other endianness on PPC arch and that comes out as “custom” format in Java2D.

Cas :slight_smile:

Nope, it depends on the bytes it receives. It tries to pick a datatype that matches the native format. Reading multiple JPEGs can result into multiple data-types - same for PNG, obviously whether or not there is transparancy, but PNG’s can also be indexed. The datatypes of BMP and GIF are constant, I think.

I used to, but as mentioned already it won’t always work. I pinched this method from someone in the lwjgl irc a while ago to convert to a custom ComponentColorModel which can be directly sent to OpenGL. It leaves the per-pixel manipulation up to j2d (which it’s really good at):

package com.triangularpixels.rebirth.renderer.image;

import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Hashtable;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

public class TextureUtils
{
	static ComponentColorModel glRGBAColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
             new int[] {8,8,8,8},
             true,
             false,
             ComponentColorModel.TRANSLUCENT,
             DataBuffer.TYPE_BYTE);
             
	static ComponentColorModel glRGBColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
             new int[] {8,8,8,0},
             false,
             false,
             ComponentColorModel.OPAQUE,
             DataBuffer.TYPE_BYTE);
	
	public static BufferedImage createBufferedImage(final int width, final int height, TextureFormat format)
	{
		if (format == TextureFormat.RGB)
		{
			WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 3, null);
			return new BufferedImage(TextureUtils.glRGBColorModel, raster, false, new Hashtable<String, Object>());
		}
		else if (format == TextureFormat.RGBA)
		{
			WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 4, null);
			return new BufferedImage(TextureUtils.glRGBAColorModel, raster, false, new Hashtable<String, Object>());
		}
		else
		{
			assert false;
			return null;
		}
	}
	
	public static TextureFormat findFormat(BufferedImage img)
	{
		final int numBands = img.getRaster().getNumBands();
		return (numBands == 3) ? TextureFormat.RGB : TextureFormat.RGBA;
	}
	
	public static BufferedImage convertToGlFormat(BufferedImage inImage, final boolean flipY)
	{
		// If already in a suitable colour model then just return the input unchanged
		if ((inImage.getColorModel() == glRGBColorModel || inImage.getColorModel() == glRGBAColorModel)
				&& !flipY)
		{
			return inImage;
		}
		
		TextureFormat format = TextureUtils.findFormat(inImage);
		BufferedImage outImage = TextureUtils.createBufferedImage(inImage.getWidth(), inImage.getHeight(), format);
		
		if (flipY)
		{
			outImage.getGraphics().drawImage(inImage,
					0, 0, inImage.getWidth(), inImage.getHeight(), // dest rect
					0, inImage.getHeight(), inImage.getWidth(), 0, // src rect
					null);
		}
		else
		{
			outImage.getGraphics().drawImage(inImage, 0, 0, null);
		}
		
		return outImage;
	}
	
	public static int createTexture(BufferedImage imageData, TextureFilter filterMode, TextureWrap wrapMode, final boolean flipY)
	{
		imageData = convertToGlFormat(imageData, flipY);
		
		TextureFormat format = TextureUtils.findFormat(imageData);
		
		IntBuffer buff = BufferUtils.createIntBuffer(16);
		buff.limit(1);
		GL11.glGenTextures(buff);
		
		final int textureId = buff.get();
		
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
		
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filterMode.glValue());
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filterMode.glValue());
		
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapMode.glValue());
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapMode.glValue());
		
		ByteBuffer scratch = ByteBuffer.allocateDirect(format.bytesPerPixel()*imageData.getWidth()*imageData.getHeight());

		Raster raster = imageData.getRaster();
		byte data[] = (byte[])raster.getDataElements(0, 0, imageData.getWidth(), imageData.getHeight(), null);
		scratch.clear();
		scratch.put(data);
		scratch.rewind();
		
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format.glValue(),				// Mip level & internal format
							imageData.getWidth(), imageData.getHeight(), 0,		// width, height, border
							format.glValue(), GL11.GL_UNSIGNED_BYTE,			// pixel data format
							scratch);											// pixel data
		
		return textureId;
	}
}

Feel free to pinch any bits of this you want.

Yes, REV is reverse. You could also swap the endian-ness of the IntBuffer to get the same effect - for some reason lost in the mists of time (which generally means anything past 5 min ago ::slight_smile: ) I decided on this way. It feels like a hack but seems to work fine. I needed it for the OpenGL pipeline in Praxis, which has its own optimized software pipeline. In order to be able to develop the OpenGL pipeline incrementally I needed a fast way of dropping to the software pipeline for anything not yet implemented, which might involve updating the texture at 60fps. Seems to work OK, if a little CPU intensive! ;D

As others have said, the data in BufferedImages can be stored in a wide range of formats. The getRGB() method in your code above will always return ARGB though. However, it involves needlessly copying the data.

But better than the OpenGL driver / GPU???

LOL ;D

Faster than manually looping over and converting the pixel data by hand I mean. And will be able to deal with whatever internal format you can throw at it.

I pinched the method, not the actual code above. ::slight_smile:

Ah, OK - wasn’t sure what that was in comparison to. Having said that, I wouldn’t assume that J2D will always perform better than manual looping and bit-shifting.

Oops, different interpretations of ‘method’ - still amused me though.