Jan Weber
Jan Weber

Reputation: 137

LWJGL skewed texture

I was trying to use a texture with LWJGL, turns out that RBG-pngs are somewhat skewed. Example: Original picture/Texture

The loading code is 98% of the lwjgl wiki space invaders example.. Texture.java:

public int target, textureID, height, width, texWidth, texHeight;
private float widthRatio, heightRatio;

public Texture(int target, int textureID) {

    this.target = target;
    this.textureID = textureID;
}

public void bind() {

    GL11.glBindTexture(target, textureID);
}

public void setWidth(int width) {

    this.width = width;
    setWidth();
}

public void setHeight(int height) {

    this.height = height;
    setHeight();
}

public int getImageWidth() {

    return width;
}

public int getImageHeight() {

    return height;
}

public float getWidth() {

    return widthRatio;
}

public float getHeight() {

    return heightRatio;
}

public void setTextureWidth(int texWidth) {

    this.texWidth = texWidth;
    setWidth();
}

public void setTextureHeight(int texHeight) {

    this.texHeight = texHeight;
    setHeight();
}

private void setWidth() {

    if (texWidth != 0)
        widthRatio = ((float) width) / texWidth;
}

private void setHeight() {

    if (texHeight != 0)
        heightRatio = ((float) height) / texHeight;
}

TextureLoader.java:

private static HashMap<String, Texture> table = new HashMap<String, Texture>();

    private static ColorModel glAlphaColorModel, glColorModel;
    private static IntBuffer textureIDBuffer = BufferUtils.createIntBuffer(1);

    static {
        glAlphaColorModel = new ComponentColorModel(
                ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8,
                        8, 8 }, true, false, ComponentColorModel.TRANSLUCENT,
                DataBuffer.TYPE_BYTE);

        glColorModel = new ComponentColorModel(
                ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8,
                        8, 0 }, false, false, ComponentColorModel.OPAQUE,
                DataBuffer.TYPE_BYTE);
    }

    private static int createTextureID() {

        GL11.glGenTextures(textureIDBuffer);
        return textureIDBuffer.get(0);
    }

    public static Texture getTexture(String name, BufferedImage image)
            throws IOException {

        Texture tex = table.get(name);

        if (tex != null)
            return tex;

        tex = getTexture(image, GL11.GL_TEXTURE_2D, GL11.GL_RGBA,
                GL11.GL_LINEAR, GL11.GL_LINEAR);

        table.put(name, tex);

        return tex;
    }

    public static Texture getTexture(BufferedImage image, int target,
            int dstPixelFormat, int minFilter, int magFilter)
            throws IOException {

        int srcPixelFormat;

        int textureID = createTextureID();
        Texture texture = new Texture(target, textureID);

        GL11.glBindTexture(target, textureID);

        texture.setWidth(image.getWidth());
        texture.setHeight(image.getHeight());

        if (image.getColorModel().hasAlpha())
            srcPixelFormat = GL11.GL_RGBA;
        else
            srcPixelFormat = GL11.GL_RGB;

        ByteBuffer textureBuffer = convertImageData(image, texture);

        if (target == GL11.GL_TEXTURE_2D) {

            GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
            GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
        }

        GL11.glTexImage2D(target, 0, dstPixelFormat, image.getWidth(),
                image.getHeight(), 0, srcPixelFormat,
                GL11.GL_UNSIGNED_BYTE, textureBuffer);

        return texture;
    }

    private static ByteBuffer convertImageData(BufferedImage bufferedImage,
            Texture texture) {

        ByteBuffer imageBuffer;
        WritableRaster raster;
        BufferedImage texImage;

        int texWidth = bufferedImage.getWidth();
        int texHeight = bufferedImage.getHeight();

        texture.setTextureHeight(texHeight);
        texture.setTextureWidth(texWidth);

        if (bufferedImage.getColorModel().hasAlpha()) {
            raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                    texWidth, texHeight, 4, null);
            texImage = new BufferedImage(glAlphaColorModel, raster, false,
                    new Hashtable());
        } else {
            raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                    texWidth, texHeight, 3, null);
            texImage = new BufferedImage(glColorModel, raster, false,
                    new Hashtable());
        }

        Graphics g = texImage.getGraphics();
        g.setColor(new Color(0f, 0f, 0f, 0f));
        g.fillRect(0, 0, texWidth, texHeight);
        g.drawImage(bufferedImage, 0, 0, null);

        // texImage is NOT skewed at this point

        byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer())
                .getData();

        imageBuffer = ByteBuffer.allocateDirect(data.length);
        imageBuffer.order(ByteOrder.nativeOrder());
        imageBuffer.put(data, 0, data.length);
        imageBuffer.flip();

        return imageBuffer;
    }

Upvotes: 1

Views: 576

Answers (2)

Brendan Jones
Brendan Jones

Reputation: 46

I know this is an old question, but I ran into this just a moment ago myself. Hopefully someone else will benefit, assuming you've already sorted this out on your own.

The reason the textures are skewed and discolored is because it doesn't line up properly with your UNPACK_ALIGNMENT. From what I understand, the number of bytes in each row must be a multiple of whatever UNPACK_ALIGNMENT is set to (4 by default).

With a 4-component format, this isn't an issue because each pixel consists of 4 bytes, so any dimension image is aligned properly. But with other formats, the data is padded to keep it properly aligned, which causes problems like this.

You can either change the size of your image so that it aligns properly ((width * formatComponents) % 4 == 0) , change the format to a 4-component format, or change the UNPACK_ALIGNMENT to something that doesn't pad your images using:

glPixelStore(GL_UNPACK_ALIGNMENT, alignment); //Alignment must be 1, 2, 4, or 8.

http://www.opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads

Upvotes: 2

Freezerburn
Freezerburn

Reputation: 1013

You might be having an issue with how the PNG is stored in the file and how it is loaded by Java. I'm not sure if I experienced exactly this issue before, but I know I had trouble loading images in LWJGL at one point. Here is a class I put together that ensures the PNG is converted to a format understood by OpenGL without skewing or color issues. As a warning, this worked for my PNGs, but there are a ton of possible formats a BufferedImage can be in, and I don't believe this code covers all of the cases. Hopefully this helps you out. It should be completely standalone except for another class (SimpleTexture) which I will also include here for you. Note that the BasicTextureLoader class is actually a Runnable, as this was originally designed for loading textures in the background.

import org.lwjgl.opengl.OpenGLException;

import javax.imageio.ImageIO;

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

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.net.URL;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * User: FreezerburnVinny
 * Date: 1/1/12
 * Time: 5:59 PM
 */
public class BasicTextureLoader implements Runnable {
    public SimpleTexture texture;
    public BufferedImage image;
    public int width, height;
    protected String mFile;
    protected boolean mShouldRemoveBackground, mKeepBufferedImage;

    public BasicTextureLoader( String file, boolean shouldRemoveBackground ) {
        mFile = file;
        this.texture = null;
        this.width = 0;
        this.height = 0;
        this.mShouldRemoveBackground = shouldRemoveBackground;
        this.mKeepBufferedImage = false;
    }

    public BasicTextureLoader( String file, boolean shouldRemoveBackground, boolean keepBufferedImage ) {
        mFile = file;
        this.texture = null;
        this.width = 0;
        this.height = 0;
        this.mShouldRemoveBackground = shouldRemoveBackground;
        this.mKeepBufferedImage = keepBufferedImage;
    }

     protected ByteBuffer convertBufferedImageToByteBuffer( BufferedImage image ) {
        ByteBuffer buffer = ByteBuffer.allocateDirect( width * height * 4 );
        buffer.order( ByteOrder.nativeOrder() );
        byte[] bytes = ( (DataBufferByte) image.getRaster().getDataBuffer() ).getData();
        switch( image.getType() ) {
            case BufferedImage.TYPE_3BYTE_BGR:
                convertBGRBufferedImageToByteBuffer( buffer, bytes );
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                convertABGRBufferedImageToByteBuffer( buffer, bytes );
                break;
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                convertBGRBufferedImageToByteBuffer( buffer, bytes );
                break;
            case BufferedImage.TYPE_INT_ARGB:
                convertARGBBufferedImageToByteBuffer( buffer, bytes );
                break;
            case BufferedImage.TYPE_INT_BGR:
                convertBGRBufferedImageToByteBuffer( buffer, bytes );
                break;
            case BufferedImage.TYPE_INT_RGB:
                convertRGBBufferedImageToByteBuffer( buffer, bytes );
                break;
            case 12:
                convertRGBBufferedImageToByteBuffer( buffer, bytes );
                break;
            default:
                throw new OpenGLException( "Unsupported image type: " + image.getType() );
        }
        return buffer;
    }

    protected void convertARGBBufferedImageToByteBuffer( ByteBuffer buffer, byte[] bytes ) {
        byte backgrounda = (byte) 255;
        byte backgroundr = (byte) 255;
        byte backgroundg = (byte) 255;
        byte backgroundb = (byte) 255;
        for( int i = 0; i < bytes.length; i+=4 ) {
            byte alpha = bytes[ i ];
            byte red = bytes[ i + 1 ];
            byte green = bytes[ i + 2 ];
            byte blue = bytes[ i + 3 ];
            if( mShouldRemoveBackground ) {
                if( i == 0 ) {
                    backgrounda = alpha;
                    backgroundr = red;
                    backgroundg = green;
                    backgroundb = blue;
                }
                else if( alpha == backgrounda && red == backgroundr &&
                        green == backgroundg && blue == backgroundb ) {
                    alpha = 0;
                }
            }
            buffer.put( red );
            buffer.put( green );
            buffer.put( blue );
            buffer.put( alpha );
        }
        buffer.rewind();
    }

    protected void convertABGRBufferedImageToByteBuffer( ByteBuffer buffer, byte[] bytes ) {
        byte backgrounda = (byte) 255;
        byte backgroundr = (byte) 255;
        byte backgroundg = (byte) 255;
        byte backgroundb = (byte) 255;
//        System.err.println( buffer.limit() );
        for( int i = 0; i < bytes.length; i+=4 ) {
            byte alpha = bytes[ i ];
            byte blue = bytes[ i + 1 ];
            byte green = bytes[ i + 2 ];
            byte red = bytes[ i + 3 ];
            if( mShouldRemoveBackground ) {
                if( i == 0 ) {
                    backgrounda = alpha;
                    backgroundr = red;
                    backgroundg = green;
                    backgroundb = blue;
                }
                else if( alpha == backgrounda && red == backgroundr &&
                        green == backgroundg && blue == backgroundb ) {
                    alpha = 0;
                }
            }
            buffer.put( red );
            buffer.put( green );
            buffer.put( blue );
            buffer.put( alpha );
        }
        buffer.rewind();
    }

    protected void convertBGRBufferedImageToByteBuffer( ByteBuffer buffer, byte[] bytes ) {
        byte backgrounda = (byte) 255;
        byte backgroundr = (byte) 255;
        byte backgroundg = (byte) 255;
        byte backgroundb = (byte) 255;
        for( int i = 0; i < bytes.length; i+=3 ) {
            byte blue = bytes[ i ];
            byte green = bytes[ i + 1 ];
            byte red = bytes[ i + 2 ];
            byte alpha = (byte) 0xFF;
            buffer.put( red );
            buffer.put( green );
            buffer.put( blue );
            if( mShouldRemoveBackground ) {
                if( i == 0 ) {
                    backgrounda = alpha;
                    backgroundr = red;
                    backgroundg = green;
                    backgroundb = blue;
                }
                else if( alpha == backgrounda && red == backgroundr &&
                        green == backgroundg && blue == backgroundb ) {
                    alpha = 0;
                }
            }
            buffer.put( alpha );
        }
        buffer.rewind();
    }

    protected void convertRGBBufferedImageToByteBuffer( ByteBuffer buffer, byte[] bytes ) {
        byte backgrounda = (byte) 255;
        byte backgroundr = (byte) 255;
        byte backgroundg = (byte) 255;
        byte backgroundb = (byte) 255;
        for( int i = 0; i < bytes.length; i+=3 ) {
            byte red = bytes[ i ];
            byte green = bytes[ i + 1 ];
            byte blue = bytes[ i + 2 ];
            byte alpha = (byte) 0xFF;
            buffer.put( red );
            buffer.put( green );
            buffer.put( blue );
            if( mShouldRemoveBackground ) {
                if( i == 0 ) {
                    backgrounda = alpha;
                    backgroundr = red;
                    backgroundg = green;
                    backgroundb = blue;
                }
                else if( alpha == backgrounda && red == backgroundr &&
                        green == backgroundg && blue == backgroundb ) {
                    alpha = 0;
                }
            }
            buffer.put( alpha );
        }
        buffer.rewind();
    }

    protected int nextPowerOf2( int num ) {
        int ret = 2;
        while( ret < num ) ret *= 2;
        return ret;
    }

    protected int genTextureFromBufferedImage( BufferedImage image ) {
        int tex = -1;
        try {
//            width = nextPowerOf2( image.getWidth() );
//            height = nextPowerOf2( image.getHeight() );
            width = image.getWidth();
            height = image.getHeight();
            ByteBuffer imageBuffer = convertBufferedImageToByteBuffer( image );
            tex = glGenTextures();
            glBindTexture( GL_TEXTURE_2D, tex );
            glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height,
                    0, GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        }
        catch( IndexOutOfBoundsException e ) {
            e.printStackTrace();
            glDeleteTextures( tex );
            tex = -1;
        }
        catch( OpenGLException e ) {
            e.printStackTrace();
            glDeleteTextures( tex );
            tex = -1;
        }
        return tex;
    }

    protected int genTexture( String file ) {
        try {
            if( System.getProperty( "os.name" ).toLowerCase().contains( "win"  ) ) {
                String fileName = System.getProperty( "user.dir" );
                fileName = fileName.substring( 2 );
                fileName = "file:" + fileName + "\\" + file;
                image = ImageIO.read( new URL( fileName ) );
            }
            else {
                String fileName = "file:" + System.getProperty( "user.dir" ) + "/" + file;
//                System.err.println( fileName );
                image = ImageIO.read( new URL( fileName ) );
            }
            return genTextureFromBufferedImage( image );
        }
        catch( IOException e ) {
            e.printStackTrace();
        }
        // If we reach here, an error happened
        return -1;
    }

    @Override
    public void run() {
        texture = new SimpleTexture( GL_TEXTURE_2D, genTexture( mFile ) );
        if( !mKeepBufferedImage ) {
            image.flush();
            image = null;
        }
        texture.setWidth( width );
        texture.setHeight( height );
    }
}

And the SimpleTexture class:

import org.lwjgl.opengl.GL11;

/**
 * Author: FreezerburnVinny
 * Date: 1/5/12
 * Time: $(TIME}
 */
public class SimpleTexture extends Texture {
    private int mTarget, mName;
    private double mWidth, mHeight;
    private double mx1, mx2, my1, my2;

    public SimpleTexture( int target, int name ) {
        this.mTarget = target;
        this.mName = name;
        this.mWidth = 0.0;
        this.mHeight = 0.0;
        this.mx1 = 0.0;
        this.mx2 = 1.0;
        this.my1 = 0.0;
        this.my2 = 1.0;
    }
    public SimpleTexture( int target, int name, double width, double height ) {
        this.mTarget = target;
        this.mName = name;
        this.mWidth = width;
        this.mHeight = height;
    }

    public SimpleTexture( int target, int name, double width, double height,
                          double x1, double x2, double y1, double y2 ) {
        this.mTarget = target;
        this.mName = name;
        this.mWidth = width;
        this.mHeight = height;
        this.mx1 = x1;
        this.mx2 = x2;
        this.my1 = y1;
        this.my2 = y2;
    }

    public double getTexCoordx1() { return mx2; }
    public double getTexCoordy1() { return my1; }
    public double getTexCoordx2() { return mx2; }
    public double getTexCoordy2() { return my2; }
    public double getTexCoordx3() { return mx1; }
    public double getTexCoordy3() { return my2; }
    public double getTexCoordx4() { return mx1; }
    public double getTexCoordy4() { return my1; }

    public double getWidth() { return mWidth; }
    public double getHeight()  { return mHeight; }
    public int getName() { return mName; }
    public int getTarget() { return mTarget; }

    public void setWidth( double width ) { this.mWidth = width; }
    public void setHeight( double height) { this.mHeight = height; }

    public void bind() {
        if( Texture.lastBound != mName ) {
            GL11.glBindTexture( mTarget, mName );
            Texture.lastBound = mName;
        }
    }

    public boolean isValidTexture() {
        if( mName == -1 ) return false;
        return GL11.glIsTexture( mName );
    }

    @Override
    public void restart() {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public void stopAt( int frame ) {
    }

    @Override
    public void stopAfterFullCycle() {
    }

    @Override
    public int numFrames() {
        return 1;
    }
}

EDIT: Woops, almost forgot SimpleTexture relied on the Texture class. Here you go:

/**
 * Author: FreezerburnVinny
 * Date: 1/10/12
 * Time: $(TIME}
 */
public abstract class Texture {
    protected static int lastBound = -1;

    public abstract double getTexCoordx1();
    public abstract double getTexCoordx2();
    public abstract double getTexCoordx3();
    public abstract double getTexCoordx4();

    public abstract double getTexCoordy1();
    public abstract double getTexCoordy2();
    public abstract double getTexCoordy3();
    public abstract double getTexCoordy4();

    public abstract double getWidth();
    public abstract double getHeight();
    public abstract int getName();
    public abstract int getTarget();

    public abstract void setWidth( double width );
    public abstract void setHeight( double height );

    public abstract void bind();

    public abstract boolean isValidTexture();

    public abstract void restart();
    public abstract void pause();
    public abstract void resume();
    public abstract void stopAt( int frame );
    public abstract void stopAfterFullCycle();
    public abstract int numFrames();
}

Upvotes: 0

Related Questions