16

Is it possible to load PNG Textures and draw Strings in LWJGL WITHOUT using the Slick Framework?

Everytime I google "how to load png images in lwjgl" I get answers like this -> "hey just use the textureloader from the slick framework".
Same for "how to draw strings in lwjgl" -> "just use the TTFFont Class from the slick framework"

But I don't want to use this half-way-crossframework design. Because I don't think that this is the best way.

Are there any Libraries or Extensions for LWJGL that are only made for Textures or Strings?

benka
  • 4,732
  • 35
  • 47
  • 58
Michael Malura
  • 1,131
  • 2
  • 13
  • 30

2 Answers2

27

Basically, you take a BufferedImage, use getRGB() to get the RGB of each pixel, take that data and put it into a ByteBuffer (the data type used to input image data to OpenGL), set some texture data, and create the GL_TEXTURE_2D.

This code by Krythic does it:

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(MainClass.class.getResource(loc));
            } catch (IOException e) {
                //Error Handling Here
            }
           return null;
       }
}

To use this code, do something like this:

BufferedImage image = TextureLoader.loadImage("/res/test.png");//The path is inside the jar file
int textureID = TextureLoader.loadTexture(image);

You can either save the textureID as a final variable(if the texture never changes), or unload the texture after each render using GL11.glDeleteTextures(textureID);

To do text, just create a BufferedImage manually, and use createGraphics() to get a graphics2D() instance for the image. Then, use drawString() to draw onto the BufferedImage, load it into the TextureLoader, render it onscreen, and unload the texture using the method above.

thedayturns
  • 9,723
  • 5
  • 33
  • 41
Flafla2
  • 545
  • 7
  • 12
  • 7
    The above code is mine, I wrote it some time ago. Imagine my surprise to find it randomly on Stack Overflow! Makes me feel all warm and fuzzy to know that it has been making its way around the internet! =P – Krythic Jan 20 '14 at 11:23
  • 1
    It should also be noted that I gave this same snippet to Dinnerbone to help him create Tinted Glass in Minecraft. – Krythic Jan 20 '14 at 11:38
  • 2
    Funny thing is i didn't add "FOR THE LOVE OF GOD DO NOT FORGET THIS" to the original snippet. I think i said something like: "Make sure you don't forget this" or something of the like. Its apparently gone through a couple of hands since i made it. – Krythic Jan 20 '14 at 11:43
  • "//3 for RGB, 4 for RGBA" is the same and the uncaught image load is the same, also its an internal image load, which is exactly how i did the first build. (it was something quickly thrown together to help articulate how it could be done via a call list and without Slick for Dinnerbone) – Krythic Jan 20 '14 at 11:47
  • @Krynn Thanks for the code! I will add in credit for you now. – Flafla2 Jan 21 '14 at 12:56
  • sry for the push, but how can i load this texture from the id again? So is there something like glSetTexture(int textureId) ?? – T_01 Jan 26 '14 at 20:01
  • 1
    @T_01 You would use `glBindTexture(GL_TEXTURE_2D, textureID);` – Flafla2 Jan 26 '14 at 20:40
  • Okay i have a knew problem... im creating to textures with this: background and logo. But logo is... a kind of a mix of the backgroud and the logo. Its a mix of different colors where you cannot see anything... why? Have i "unbind" the backgound first or something like this? When im painting only the second texture, this comes up too. So the problem is in the second texture creation... any idea? – T_01 Jan 26 '14 at 22:14
  • @T_01 I am not sure exactly what your problem is but make sure this is the course of action you are taking each time you render: 1. Bind background 2. Render quad with background coordinates 3. Bind logo 4. render logo – Flafla2 Jan 27 '14 at 00:45
  • Should it be `pixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());` rather than just `image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());`? – Newbyte Feb 15 '21 at 09:12
4

LWJGL now includes STB bindings, which is the preferred way of loading images and fonts, without having to use Slick or even AWT.

To load a PNG :

import static org.lwjgl.opengl.GL11.GL_REPEAT;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glPixelStorei;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL30.glGenerateMipmap;
import static org.lwjgl.stb.STBImage.stbi_load_from_memory;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.demo.util.IOUtils.ioResourceToByteBuffer;
  
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.lwjgl.system.MemoryStack;

public class Texture{
    private int width;
    private int height;
    private int id;

    public Texture(String imagePath) {
        ByteBuffer imageData = ioResourceToByteBuffer(imagePath, 1024);
    
        try (MemoryStack stack = stackPush()) {
            IntBuffer w = stack.mallocInt(1);
            IntBuffer h = stack.mallocInt(1);
            IntBuffer components = stack.mallocInt(1);

            // Decode texture image into a byte buffer
            ByteBuffer decodedImage = stbi_load_from_memory(imageData, w, h, components, 4);
        
            this.width = w.get();
            this.height = h.get();
        
            // Create a new OpenGL texture 
            this.id = glGenTextures();
        
            // Bind the texture
            glBindTexture(GL_TEXTURE_2D, this.id);

            // Tell OpenGL how to unpack the RGBA bytes. Each component is 1 byte size
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        
            // Upload the texture data
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this.width, this.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, decodedImage);
        
            // Generate Mip Map
            glGenerateMipmap(GL_TEXTURE_2D);
        }
    }
}

More complete examples for image loading, and text printing can be found in LWJGL source code :

org.lwjgl.demo.stb

Newbyte
  • 2,421
  • 5
  • 22
  • 45
aTom
  • 406
  • 6
  • 17