Create a 2D Texture pixel data

Started by Cornix, February 20, 2013, 22:28:39

Previous topic - Next topic

Cornix

Hi, i am trying to create a 2D texture from a 3-dimensional byte array.

Right now my code looks somewhat like this:
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.nativeOrder());
for (int x = 0; x < width; x++){
	for (int y = 0; y < height; y++){
		buf.put(pixel_data[x][y][0]);
		buf.put(pixel_data[x][y][1]);
		buf.put(pixel_data[x][y][2]);
		buf.put(pixel_data[x][y][3]);
	}
}
buf.flip();
texture_id = GL11.glGenTextures();
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_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);


But when i try to draw something with this texture the result is always pure white.
What am I doing wrong?


Edit:
I know that the drawing works, i manage to load textures via the slick-util texture loader and draw them perfectly.

Elviz

Add the following after the call to glGenTextures:

GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture_id);


Also, make sure to move the y loop before the x loop:

for (int y = 0; y < height; y++) {
	for (int x = 0; x < width; x++) {


This assumes that the source image is already in bottom-up format. If not, simply reverse the line order:

for (int y = height - 1; y >= 0; y--) {
	for (int x = 0; x < width; x++) {

Cornix

Thanks for the answer but that doesnt do anything. The picture is still pure white across the board.

abcdef

You haven't posted your rendering code but have you enabled GL_TEXTURE_2D?

Cornix

I have updated my first post to hopefully clarify.

The drawing works, i am able to draw textures which i have loaded via the slick-util texture loader.
My rendering code looks like this:
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture_id);
		GL11.glMatrixMode(GL11.GL_TEXTURE);
		GL11.glLoadIdentity();
		GL11.glScalef(1f / width, 1f / height, 1);

		GL11.glBegin(GL11.GL_QUADS);
			
				GL11.glColor4f(1f, 1f, 1f, 1f);
				GL14.glSecondaryColor3f(0f, 0f, 0f);
			GL11.glTexCoord2f( 0,  0); GL11.glVertex3f(256, 256, 1);
			GL11.glTexCoord2f(32,  0); GL11.glVertex3f(320, 256, 1);
			GL11.glTexCoord2f(32, 32); GL11.glVertex3f(320, 320, 1);
			GL11.glTexCoord2f( 0, 32); GL11.glVertex3f(256, 320, 1);
			
		GL11.glEnd();

(I use front-face culling, dont worry about the order of vertices)

quew8

Maybe a stupid answer, but have you actually checked the content of your buffer before loading into opengl?

Cornix

Quote from: quew8 on February 21, 2013, 15:40:22
Maybe a stupid answer, but have you actually checked the content of your buffer before loading into opengl?

I did.
It seems to be correct.

Also tested the texture id, its a positive number.

abcdef

Aren't texture coordinates meant to be in the range 0-1? You have 0-32 (never drawn thigns in TEXTURE mode though, so this might be normal)

I still don't see you enabling GL_TEXTURE_2D (might be else where though)

Lastly what type of image format are you trying to load? Its hard to tell. You use types GL11.GL_UNSIGNED_BYTE, does your pixel_data contain byte values. They might be signed (so you might need to do a &FF to convert to unsigned byte

Cornix

Quote from: abcdef on February 22, 2013, 10:47:07
Aren't texture coordinates meant to be in the range 0-1? You have 0-32 (never drawn thigns in TEXTURE mode though, so this might be normal)
Normally they are in the range of 0-1 but after this line of code:
GL11.glScalef(1f / width, 1f / height, 1);

I can use the exact pixel coordinates.
Although even without the line, since i set the wrap mode to be "Repeat" it would still work.

Quote from: abcdef on February 22, 2013, 10:47:07
I still don't see you enabling GL_TEXTURE_2D (might be else where though)
I do it at another point in the program. As i said, i can draw textures loaded via the slick-util.

Quote from: abcdef on February 22, 2013, 10:47:07Lastly what type of image format are you trying to load? Its hard to tell. You use types GL11.GL_UNSIGNED_BYTE, does your pixel_data contain byte values. They might be signed (so you might need to do a &FF to convert to unsigned byte
I try to use a 3 dimensional byte table to define the picture myself. No format used at all. Just raw byte data.

Fool Running

Try using RGBA instead of RGBA8 (it's what I've used in my code and it works fine for me :P):
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

Cornix

Quote from: Fool Running on February 22, 2013, 13:37:17
Try using RGBA instead of RGBA8 (it's what I've used in my code and it works fine for me :P):
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);

Thank you for the reply, but unfortunately it didnt work.
What exactly is the difference between RGBA and RGBA8?

On a sidenote: Doing "& 0xFF" did not work either.


Here is the entire code for that class:
package graphics2D;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;

public class Tex2D {
	
	private final float scale_width;
	private final float scale_height;
	private final int width;
	private final int height;
	private final int texture_id;
	
	public Tex2D(final byte[][][] pixel_data, final Filter_Mode min_filter, final Filter_Mode mag_filter, final Wrap_Mode vertical_wrap, final Wrap_Mode horizontal_wrap){
		int w;
		int h;
		w = pixel_data.length;
		if (w == 0){
			throw new TextureSizeException("Can not create texture with width of 0");
		}else{
			h = pixel_data[0].length;
			if (h == 0){
				throw new TextureSizeException("Can not create texture with height of 0");
			}
		}
		if (!is_power_of_two(w)){
			w = next_power_of_two(w);
		}
		if (!is_power_of_two(h)){
			h = next_power_of_two(h);
		}
		this.width = w;
		this.height = h;
		this.scale_width = 1f / this.width;
		this.scale_height = 1f / this.height;
		
		final ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
		buf.order(ByteOrder.nativeOrder());
		for (int y = 0; y < this.height; y++){
			for (int x = 0; x < this.width; x++){
				buf.put((byte) (pixel_data[x][y][0] & 0xFF));
				buf.put((byte) (pixel_data[x][y][1] & 0xFF));
				buf.put((byte) (pixel_data[x][y][2] & 0xFF));
				buf.put((byte) (pixel_data[x][y][3] & 0xFF));
			}
		}
		buf.flip();
		
		this.texture_id = GL11.glGenTextures();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.texture_id);
		set_filter(true, min_filter);
		set_filter(false, mag_filter);
		set_wrap(true, horizontal_wrap);
		set_wrap(false, vertical_wrap);
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, this.width, this.height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
	}
	
	public void print_buffer(){
		ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
		buf.order(ByteOrder.nativeOrder());
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.texture_id);
		GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
		byte[] data = new byte[buf.limit()];
		buf.get(data);
		buf.clear();
		System.out.println(Arrays.toString(data));
	}
	
	public void bind(){
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.texture_id);
		GL11.glMatrixMode(GL11.GL_TEXTURE);
		GL11.glLoadIdentity();
		GL11.glScalef(this.scale_width, this.scale_height, 1);
	}
	
	public void remove(){
		IntBuffer texBuf = ByteBuffer.allocateDirect(4).asIntBuffer();
		texBuf.put(this.texture_id);
		texBuf.flip();
		GL11.glDeleteTextures(texBuf);
	}
	
	private static final int next_power_of_two(int value){
		value--;
		value |= value >> 1;
		value |= value >> 2;
		value |= value >> 4;
		value |= value >> 8;
		value |= value >> 16;
		value++;
		return value;
	}
	
	private static final boolean is_power_of_two(final int value){
		return (value & (~value + 1)) == value;
	}
	
	private static final void set_wrap(final boolean horizontal, final Wrap_Mode wrap){
		final int type = horizontal ? GL11.GL_TEXTURE_WRAP_S : GL11.GL_TEXTURE_WRAP_T;
		switch (wrap){
		case REPEAT :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL11.GL_REPEAT);
			break;
		case MIRRORED_REPEAT :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL14.GL_MIRRORED_REPEAT);
			break;
		case CLAMP :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL11.GL_CLAMP);
			break;
		case CLAMP_TO_BORDER :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL13.GL_CLAMP_TO_BORDER);
			break;
		case CLAMP_TO_EDGE :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL12.GL_CLAMP_TO_EDGE);
			break;
		}
	}
	
	private static final void set_filter(final boolean min, final Filter_Mode filter){
		final int type = min ? GL11.GL_TEXTURE_MIN_FILTER : GL11.GL_TEXTURE_MAG_FILTER;
		switch (filter){
		case LINEAR :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL11.GL_LINEAR);
			break;
		case NEAREST :
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, type, GL11.GL_NEAREST);
			break;
		}
	}
	
	public static enum Filter_Mode {
		LINEAR, NEAREST;
	}
	
	public static enum Wrap_Mode {
		REPEAT, MIRRORED_REPEAT, CLAMP, CLAMP_TO_BORDER, CLAMP_TO_EDGE;
	}
	
	private static final class TextureSizeException extends RuntimeException{
		private static final long serialVersionUID = -8911712330468875206L;
		public TextureSizeException(String string) {
			super(string);
		}
	}
	
}



And this is how i use it:
private Tex2D tex;
	
	public void initialize() {
		byte[][][] pixel_data = new byte[6][6][4];
		byte i = 0;
		for (int x = 0; x < pixel_data.length; x++){
			for (int y = 0; y < pixel_data[0].length; y++){
				pixel_data[x][y][0] = i;
				pixel_data[x][y][1] = i;
				pixel_data[x][y][2] = i;
				pixel_data[x][y][3] = i;
				i++;
			}
		}
		tex = new Tex2D(pixel_data, Tex2D.Filter_Mode.NEAREST, Tex2D.Filter_Mode.NEAREST, Tex2D.Wrap_Mode.REPEAT, Tex2D.Wrap_Mode.REPEAT);
	}

	public void update() {
		tex.bind();
		GL11.glBegin(GL11.GL_QUADS);
			
				GL11.glColor4f(1f, 1f, 1f, 1f);
				GL14.glSecondaryColor3f(0f, 0f, 0f);
			GL11.glTexCoord2f( 0,  0); GL11.glVertex3f(256, 256, 1);
			GL11.glTexCoord2f(32,  0); GL11.glVertex3f(320, 256, 1);
			GL11.glTexCoord2f(32, 32); GL11.glVertex3f(320, 320, 1);
			GL11.glTexCoord2f( 0, 32); GL11.glVertex3f(256, 320, 1);
			
		GL11.glEnd();
	}

quew8

RGBA8 uses 8 bits per component. RGBA uses a default size dependent on hardware and implementation.
Something just occurred to me. There is a glGetTexture function you could use to ensure the data openGL has is the data you're giving it.

Cornix

Quote from: quew8 on February 24, 2013, 11:52:31
RGBA8 uses 8 bits per component. RGBA uses a default size dependent on hardware and implementation.
Something just occurred to me. There is a glGetTexture function you could use to ensure the data openGL has is the data you're giving it.

Thank you for the information.
I was using:
public String test(){
		ByteBuffer buf = ByteBuffer.allocateDirect(4);
		buf.order(ByteOrder.nativeOrder());
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture_id);
    	GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 
    					   buf);
    	byte[] data = new byte[buf.limit()];
    	buf.get(data);
    	buf.clear();
    	
    	return Arrays.toString(data);
	}

To test the pixel data and the results i got with this function were identical to the input i made.

Now, this piece of code is not very long, would it be possible if somebody could maybe try to implement a data structure like mine and see if he/she could get it done?
I would really appreciate any further help.

quew8

So this has been bugging me and I decided to write my own implementation that just loads a single colour for simplicity:
public static int createColourTexture(Colour c, int width, int height) {
        int id = createTextureID();
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
        
        int texWidth = get2Fold(width);
        int texHeight = get2Fold(height);
        
        float[] fdata = new float[texWidth * texHeight * 4];
        for(int h = 0, pos = 0; h < texHeight; h++) {
            for(int w = 0; w < texWidth; w++, pos++) {
                fdata[pos++] = c.getRed();
                fdata[pos++] = c.getGreen();
                fdata[pos++] = c.getBlue();
                fdata[pos] = c.getAlpha();
            }
        }
        FloatBuffer fb = BufferUtils.createFloatBuffer(fdata);

        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
        
        GL11.glTexImage2D(
                GL11.GL_TEXTURE_2D, 
                0, 
                GL11.GL_RGBA, 
                texWidth, 
                texHeight, 
                0,
                GL11.GL_RGBA,
                GL11.GL_FLOAT,
                fb
                );
        
        GLException.checkGLError();
        table.put(ref, id);
        return id;
    }

Using floats, this worked like a charm. I could not however get a byte or int version to work in the slightest (although they always appeared black for me rather than white so probably we're having different problems). Maybe you could try a float based version (as a test)

Cornix

Thank you for the reply.
But it doesnt seem to work for me.

First of all, as it seems i am using an old version of lwjgl since there is neither a function to create a FloatBuffer from a float array nor can i resolve the type GLException in my build.

But even if i try to use a float buffer instead of a byte-buffer it is still just a pure white square.