I'm trying to create a texture from a loaded Buffered image like this:
public static long loadTexture(String img) throws IOException{
File imgPath = new File(img);
BufferedImage bufferedImage = ImageIO.read(imgPath);
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, ByteBuffer.wrap(pixels));
glGenerateMipmap(GL_TEXTURE_2D);
return id;
}
But this code gives me a java sigsegv error. I am using a Java.nio.Bytebuffer because the sun one isn't supported in java 11.
So what am I doing wrong? The Image is loaded correctly, with 4bpp:
//last 2 digits are lenght
FF-AD-6F-CB-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-00-00-FF-FF-00-00-FF-FF-FF-FF-00-FF-FF-FF-00-FF-00-00-FF-FF-00-00-FF-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-64
Heres the image:
its pretty small of course but the data is correct.
So why am I getting a sigsegv? the log is pretty useless and long so I can't post it.
And how do I create an opengl texture from a 4bpp byte array?
As suggested in Java Code Examples for org.lwjgl.opengl.GL11.glTexImage2D() (Example 5) you have to copy the data to a ByteBuffer in loops:
public static long loadTexture(String img) throws IOException{
File imgPath = new File(img);
BufferedImage image = ImageIO.read(imgPath);
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
ByteBuffer buffer = ByteBuffer.allocateDirect(image.getWidth() * image.getHeight() * 4);
for(int h = 0; h < image.getHeight(); h++) {
for(int w = 0; w < image.getWidth(); w++) {
int pixel = pixels[h * image.getWidth() + w];
buffer.put((byte) ((pixel >> 16) & 0xFF));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(),
0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glGenerateMipmap(GL_TEXTURE_2D);
return id;
}
Note, glPixelStorei(GL_UNPACK_ALIGNMENT, 1) is unnecessary in that case, because the size of a RGBA pixel is 4 bytes, so each row is aligned to 4 bytes and GL_UNPACK_ALIGNMENT by default is 4.
Furthermore, if you want to use Mipmaps (glGenerateMipmap), then the minifying function (GL_TEXTURE_MIN_FILTER) has to be GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR. (See glTexParameter)
Related
I want to dynamically create an image, and the created image must meet some requirements.
The created image should be a png, and it must have the same behavior as if it's a loaded png from a file.
It's for creating a texture to use in LWJGL.
When I load a png image as a file and have a BufferedImage, I can use the following code for my texture:
(The Texture constructor is designed for using with loaded images)
public class Texture {
public Texture(BufferedImage bi) {
width = bi.getWidth();
height = bi.getHeight();
System.out.println(bi.toString());
int[] pixels_raw = new int[width * height];
pixels_raw = bi.getRGB(0, 0, width, height, null, 0, width);
ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4);
for(int i = 0; i < width; i++) {
for(int j = 0; j < height; j++) {
int pixel = pixels_raw[i * width + j]; // This is the error line.
pixels.put((byte)((pixel >> 16) & 0xFF)); // red
pixels.put((byte)((pixel >> 8) & 0xFF)); // green
pixels.put((byte)(pixel & 0xFF)); // blue
pixels.put((byte)((pixel >> 24) & 0xFF)); // alpha
}
}
pixels.flip();
id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
}
But when I try to create an image dynamically, without loading anything from a file, then I get an ArrayIndexOutOfBoundsException on line 18 of the above code (see comment in code).
Of course it has something to do with the bits per pixel of the created BufferedImage. I tried changing the image type for my BufferedImage, and changing the array size when initializing the pixels_raw array. But I still get array exceptions. So, the above constructor method does only works when I pass a BufferedImage instance which comes from a loaded png. When I pass in a BurfferedImage I created dynamically with the code below, it gives me the exceptions I mentioned before.
public class TextDrawer {
public BufferedImage drawText(String text, Font font, Color color) {
BufferedImage graphicsGetterBi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics g = graphicsGetterBi.getGraphics();
Graphics2D g2 = (Graphics2D) g;
Rectangle2D bounds = font.getStringBounds(text, 0, text.length(), g2.getFontRenderContext());
BufferedImage bi = new BufferedImage((int) bounds.getWidth(), (int) bounds.getHeight(), BufferedImage.TYPE_INT_ARGB);
System.out.println("Created the image. \n");
g2.setColor(color);
g2.setFont(font);
g2.drawString(text, 0, 0);
return bi;
}
}
instead of int pixel = pixels_raw[i * width + j]; it should be int pixel = pixels_raw[i * height + j]; or int pixel = pixels_raw[j * width + i];. Consider you have image of width = 2x and height = x. Then the array size is 2x^2, while the maximum index you request for is (2x - 1) * 2x + x - 1 = 4x^2 - x - 1, which is more than 2x^2 for x > 2
Trying to find a way to render a simple 2D image in Java using OpenGL I stumbled upon a more or less understandable class that does all of the image loading for you which I could not understand how to do. I believe it belongs to a person named Krythic and I assume that it works fine, I even preinted out the height and width of the image it is reading to see if it reads the proper image, which it does.
But here is said class nonetheless (I don't fully understand how it works):
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL12;
import static org.lwjgl.opengl.GL11.*;
public class TextureLoader {
private static final int BYTES_PER_PIXEL = 4;//3 for RGB, 4 for RGBA
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//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;
}
public static BufferedImage loadImage(String loc)
{
try {
return ImageIO.read(Main.class.getResource(loc));
} catch (IOException e) {
//Error Handling Here
}
return null;
}
And here is the code I wrote thinking it would render the image on a quad, and that is probably where the problem lies.
while ( glfwWindowShouldClose(window) == GL_FALSE ) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
BufferedImage image = TextureLoader.loadImage("test.png");
int textureID = TextureLoader.loadTexture(image);
glBindTexture(GL_TEXTURE_2D, textureID);
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(-1f, -1f);
glTexCoord2f(1, 0);
glVertex2f( 0f, -1f);
glTexCoord2f(1, 1);
glVertex2f( 0f, 0f);
glTexCoord2f(0, 1);
glVertex2f(-1f, 0f);
glEnd();
glfwSwapBuffers(window);
glfwPollEvents();
}
So it draws a quad in the lower left corner of the screen as it did before, but it's white and the texture is supposed to be brown. It doesn't give me any exceptions so I don't know exactly where I messed up.
Enable textures with
glEnable(GL_TEXTURE_2D);
I am making a game with LWJGL and by using openGL, I believe my best option is to use Textures and render them with quads. However, I can only seem to find information on loading a texture from an image where the entire image is only ONE texture. What I would like to do is read an entire spritesheet in and be able to separate it into different textures. Is there a somewhat simple way to do this?
You could load the image, from e.g. a .png file to a BufferedImage with
public static BufferedImage loadImage(String location)
{
try {
BufferedImage image = ImageIO.read(new File(location));
return image;
} catch (IOException e) {
System.out.println("Could not load texture: " + location);
}
return null;
}
Now you are able to call getSubimage(int x, int y, int w, int h) on that resulting BufferedImage, giving you the seperated part. You now just need to create a Texture of the BufferedImage. This code should do the work:
public static int loadTexture(BufferedImage image){
if (image == null) {
return 0;
}
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();
glBindTexture(GL_TEXTURE_2D, textureID);
//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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//Send texel data to OpenGL
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); //GL_RGBA8 was GL_RGB8A
return textureID;
}
You are now able to bind the returned textureID with glBindTexture(GL_TEXTURE_2D, textureID); if you need the texture.
This way you only have to split the BufferedImage in the desired parts.
I recommend reading this: LWJGL Textures and Strings
I searched for Texture Implementations without the Slick Utils library.
I found 2 ways, to do this:
The first, saves the pixels with strange byteshifting in a byte buffer:
int loadTexture(){
try{
BufferedImage img = ImageIO.read(getClass().getClassLoader().getResourceAsStream("background.png"));
int pixels[] = new int[img.getWidth() * img.getHeight()];
img.getRGB(0, 0, img.getWidth(), img.getHeight(), pixels, 0, img.getWidth());
ByteBuffer buffer = BufferUtils.createByteBuffer(img.getWidth() * img.getHeight() * 3);
for(int x = 0; x < img.getWidth(); x++){
for(int y = 0; y < img.getHeight(); y++){
int pixel = pixels[y * img.getWidth() + x];
buffer.put((byte) ((pixel >> 16) & 0xFF));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
}
}
buffer.flip();
int textureId = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureId);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, img.getWidth(), img.getHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
return textureId;
}
catch(Exception e){
e.printStackTrace();
return 0;
}
}
This returns a texture id as well, and i haven't any idea how tu use this id.
The second way doesnt do any byteshifting, and uses a IntBuffer: Also, it is a ready class to save different textures with names and so on.
The Code of these:
ublic class TextureIO {
private final IntBuffer texture;
private final int width;
private final int height;
private int id;
public TextureIO(final InputStream inputStream) throws IOException {
BufferedImage image = ImageIO.read(inputStream);
width = image.getWidth();
height = image.getHeight();
final AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -height);
final AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
image = op.filter(image, null);
final int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);
texture = BufferUtils.createIntBuffer(pixels.length);
texture.put(pixels);
texture.rewind();
}
public void init() {
GL11.glEnable(GL11.GL_TEXTURE_2D);
final IntBuffer buffer = BufferUtils.createIntBuffer(1);
GL11.glGenTextures(buffer);
id = buffer.get(0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
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);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, texture);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
public void bind() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
}
public void unbind() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
Im really new to lwjgl development, and want to know which version is better. Cause im a friend of implementing such things by myself, i want the lwjgl.jar to be the own library im using.
I read on different sites, the buffer.flip() method would be necassary. but why? And why the second version doesnt do this? Also, i want to understand the difference between this two implementations, what happens in the first and what in the second?
Thank you!
Both of those are pretty bad implementations IMO. I would recommend watching this video for a more standard approach. Although you would have to also use the PNGDecoder.jar library it is like an extension to the lwjgl library.
I have fix the problem and following is how I did it,
The binding code in RenderEngine:
public int bindTexture(String location)
{
BufferedImage texture;
File il = new File(location);
if(textureMap.containsKey(location))
{
glBindTexture(GL_TEXTURE_2D, textureMap.get(location));
return textureMap.get(location);
}
try
{
texture = ImageIO.read(il);
}
catch(Exception e)
{
texture = missingTexture;
}
try
{
int i = glGenTextures();
ByteBuffer buffer = BufferUtils.createByteBuffer(texture.getWidth() * texture.getHeight() * 4);
Decoder.decodePNGFileToBuffer(buffer, texture);
glBindTexture(GL_TEXTURE_2D, i);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.getWidth(), texture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
textureMap.put(location, i);
return i;
}
catch(Exception e)
{
e.printStackTrace();
}
return 0;
}
And the PNG decoder method:
public static void decodePNGFileToBuffer(ByteBuffer buffer, BufferedImage image)
{
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
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));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
}
I hope this helps anybody with the same problem
P.S. textureMap is just a HashMap with String as the key and a Integer as the value
You've got the order completely wrong. You need to:
Generate a texture name/ID with glGenTextures – store that ID in a variable
Bind that ID using glBindTexture
any only then you can upload the data with glTexImage
In your drawing code you're calling the whole texture load, which is inefficient, also you're recreating a new texture name each time. Use a map to map texture filenames to an ID, and only if no ID has been assigned yet Gen/Bind/TexImage the texture. Otherwise, just Bind it.