Changing the Icon

Started by CodeBunny, October 21, 2010, 11:21:51

Previous topic - Next topic

CodeBunny

So I'm trying to figure out how to change the icon of the window and I'm running into a little bit of trouble.

Here's what I've figured out about the requirements for the icon.


  • You need to supply an array of ByteBuffers - each ByteBuffer should be a version of the icon with varying size (16, 32, 128). Should the buffers be in ascending order of image size?
  • The icon needs to have a certain bit depth (at least, I think so, because I got an error "invalid pixel depth" when attempting to set it

My goal is to let the developer choose any image that can be placed on a texture (png, bmp, jpg, etc.) of any size and have it be read in such a way as to work for the icon. I've noticed that the Slick-Util Texture object has a "getTextureData()" call that can be wrapped into a ByteBuffer, and that looks like a good way to go. This is my plan:


  • Place the image on a texture and ensure it's the appropriate bit depth
  • Create (for each of the 3 sizes required) a texture of the right size and draw the image onto it - if it's too large, scale it down, and if it's too small, center it.
  • Wrap the texture data into a ByteBuffer, place it into an array for the list of icons.

I'm not sure how to go about #1 and #2. What're the best ways of doing this?
Also, is this a good way of setting the icon?

CodeBunny

Well, I'm trying to change the icon of the game window - are you saying I should use Cursor functionality to do so?

No offense, I'm just a little confused.

jediTofu

Oh sorry, completely not thinking haha.  This is why staying up all night is a bad idea.  :-\  Deleting reply.
cool story, bro

jediTofu

Let me try to be on topic this time  ;)

QuoteShould the buffers be in ascending order of image size?
The javadoc says to order them by preference.  I assume this means it will try the first icon; if that doesn't work, try the second, and so on.  If someone passes you a 128x128 image, you'll want to set that as the first icon.  If someone passes you a 64x64 image, you'll want to set that as the first icon, and so on.

In order to handle scaling, I just drew and read from the back buffer.  This is slower than Pbuffer's or Frame Buffer Objects, but more reliable on all systems I believe.  (I think LWJGL only has support for Pbuffers?).  This worked for me (wrote a little convenience method for ya):

 public void init() {
    Texture texture = TextureLoader.getTexture("BMP",new FileInputStream("test.bmp"));
    Texture texture1 = TextureLoader.getTexture("PNG",new FileInputStream("test.png"));
    ByteBuffer[] icons = new ByteBuffer[4];

    icons[0] = createIcon(16,16,true,false,texture);
    icons[1] = createIcon(32,32,false,false,texture1);
    icons[3] = createIcon(128,128,true,true,texture1);

    System.out.println("DEBUG: Number of Icons Used: " + Display.setIcon(icons));
  }

  public ByteBuffer createIcon(int width,int height,boolean fixAlphas,boolean makeBlackTransparent,Texture texture) {
    //Save original draw buffer
    int drawBuffer = glGetInteger(GL_DRAW_BUFFER);

    //Draw & stretch a width by height icon onto the back buffer
    glDrawBuffer(GL_BACK);
    texture.bind();
    glBegin(GL_QUADS);
      glTexCoord2f(0,0); glVertex2f(0    ,0);
      glTexCoord2f(1,0); glVertex2f(width,0);
      glTexCoord2f(1,1); glVertex2f(width,height);
      glTexCoord2f(0,1); glVertex2f(0    ,height);
    glEnd();

    //Read the back buffer into the byte buffer icon
    glReadBuffer(GL_BACK);
    ByteBuffer icon = BufferUtils.createByteBuffer(width * height * 4);
    glReadPixels(0,0,width,height,GL_RGBA,GL_BYTE,icon);

    //fixAlphas:            In case of OpenGL blending and/or bitmap problems
    //makeBlackTransparent: Cycle through and set black to be transparent
    if(fixAlphas || makeBlackTransparent) {
      for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
          int color = y * 4 * width + x * 4;
          int red   = icon.get(color);
          int green = icon.get(color + 1);
          int blue  = icon.get(color + 2);

          if(makeBlackTransparent && red == 0 && green == 0 && blue == 0) {
            icon.put(color + 3,(byte)0);
          }
          else if(fixAlphas) {
            icon.put(color + 3,(byte)255);
          }
        }
        System.out.println();
      }
    }

    //Set back to original draw buffer
    glDrawBuffer(drawBuffer);

    return(icon);
  }



Not sure how reliable this is.  You may just want to crop large images (and center small images like you said).  You'd just need to convert the Texture data into an RGBA format byte buffer (16 * 16 * 4, 32 * 32 * 4, etc.).  TextureLoader seems insufficient for stretching, even the InternalTextureLoader that it uses.  You could look into stretching using the Java SE (ImageIO, BufferedImage, etc.) and figure out how to read the bytes and what not; I think that this would be a reliable and effective solution, just more code to write though.
cool story, bro

CodeBunny

Quote from: jediTofu on October 22, 2010, 02:03:41
Oh sorry, completely not thinking haha.  This is why staying up all night is a bad idea.  :-\  Deleting reply.

It's fine, lol. I've done the same myself any number of times.  :P

Thanks for the info. I'll see where it gets me, but it looks like I should be good.

CodeBunny

I actually went ahead and used Java2D calls to set the icon - it seemed simpler and easier to implement.

public static ByteBuffer[] loadIcon(String filepath)
	{
		BufferedImage image = null;
		try
		{
			image = ImageIO.read(BufferLoader.class.getClassLoader().getResource(filepath));
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		ByteBuffer[] buffers = new ByteBuffer[3];
		buffers[0] = loadIconInstance(image, 128);
		buffers[1] = loadIconInstance(image, 32);
		buffers[2] = loadIconInstance(image, 16);
		return buffers;
	}
	
	private static ByteBuffer loadIconInstance(BufferedImage image, int dimension)
	{
		BufferedImage scaledIcon = new BufferedImage(dimension, dimension, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = scaledIcon.createGraphics();
		double ratio = 1;
		if(image.getWidth() > scaledIcon.getWidth())
		{
			ratio = (double) (scaledIcon.getWidth()) / image.getWidth();
		}
		else
		{
			ratio = (int) (scaledIcon.getWidth() / image.getWidth());
		}
		if(image.getHeight() > scaledIcon.getHeight())
		{
			double r2 = (double) (scaledIcon.getHeight()) / image.getHeight();
			if(r2 < ratio)
			{
				ratio = r2;
			}
		}
		else
		{
			double r2 =  (int) (scaledIcon.getHeight() / image.getHeight());
			if(r2 < ratio)
			{
				ratio = r2;
			}
		}
		double width = image.getWidth() * ratio;
		double height = image.getHeight() * ratio;
		g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2), (int) ((scaledIcon.getHeight() - height) / 2),
				(int) (width), (int) (height), null);
		g.dispose();
		
		byte[] imageBuffer = new byte[dimension*dimension*4];
		int counter = 0;
		for(int i = 0; i < dimension; i++)
		{
			for(int j = 0; j < dimension; j++)
			{
				int colorSpace = scaledIcon.getRGB(j, i);
				imageBuffer[counter + 0] =(byte)((colorSpace << 8) >> 24 );
				imageBuffer[counter + 1] =(byte)((colorSpace << 16) >> 24 );
				imageBuffer[counter + 2] =(byte)((colorSpace << 24) >> 24 );
				imageBuffer[counter + 3] =(byte)(colorSpace >> 24 );
				counter += 4;
			}
		}
		return ByteBuffer.wrap(imageBuffer);
	}


It's a little bit messy, but that's how it worked out.

jediTofu

Yeah, I think that will be more portable and reliable also.  They need to add this to lwjgl's util :P
cool story, bro

CodeBunny

Yeah...

I really think LWJGL would be improved if they had some convenience methods in place for generic operations (such as setting the icon). It doesn't make much sense to have to jump through so many hoops to do something achievable with literally 1 call in Java2D. It feels like a step in the wrong direction.

Overall, LWJGL is actually proving much simpler to use than Java2D, though. It's really surprising how much better it is.

bobjob

I havnt really tested setting custom icons, yet I have used custom cursors, and all you need to do is load a bufferedImage and get the dataBuffer from it.
If setting the icon is in anyway similar, you should just be able to look over your texture loading source to figure out the process.

therefore for a bufferedImage you can just call getScaledInstace() for each variation.

If the bufferedImage is not the correct type, the just redraw it on a bufferdImage that is.

Matthias

Or you could just use TWL's PNGDecoder and store the icon PNGs in the various sizes.

CodeBunny

Quote from: Matthias on October 24, 2010, 18:32:13
Or you could just use TWL's PNGDecoder and store the icon PNGs in the various sizes.

Wouldn't that only accept PNG images?

Matthias

Yep, but what else do you want to load - and how much code do you want to load just to decode an icon? TGA is also easy to decode.

CodeBunny

Well, ImageIO.read(url) returns a BufferedImage for quite a variety of formats. I just use that. Then, it's a simple method to scan through the image and get the data.

CodeBunny

Quote from: bobjob on October 24, 2010, 18:29:01
I havnt really tested setting custom icons, yet I have used custom cursors, and all you need to do is load a bufferedImage and get the dataBuffer from it.
If setting the icon is in anyway similar, you should just be able to look over your texture loading source to figure out the process.

therefore for a bufferedImage you can just call getScaledInstace() for each variation.

If the bufferedImage is not the correct type, the just redraw it on a bufferdImage that is.

You said you can just get the dataBuffer? I tried using that method to set the cursor, but the cursor I get is very strangely messed up - it has very wrong alpha settings and certain portions of it are off color.

What's the method you use?