LWJGL Forum

Programming => OpenGL => Topic started by: CodeBunny on November 04, 2010, 21:44:26

Title: Setting the Cursor
Post by: CodeBunny on November 04, 2010, 21:44:26
I've tried to set the cursor using BufferedImage, and I run into serious problems with how the image is drawn.

I do the following:

Load a BufferedImage via ImageIO.read(url).
Paint that BufferedImage onto one with the data type BufferedImage.TYPE_INT_ARGB_PRE (don't know if this is necessary, thought that it would help stabilize the method for all image types compatible with BufferedImage).
Create a cursor using an IntBuffer obtained from the image via
Code: [Select]
IntBuffer.wrap(((DataBufferInt) image.getData().getDataBuffer()).getData());
I get a cursor of the right dimensions and all, but the image data is extremely messed up - patches of it have the wrong color, other patches have the wrong transparency.

I think the problem might be that my computer only supports one bit transparency for the cursor, but the returned intbuffer holds data for a full byte of alpha data, and that causes the problem somehow. Is this right? If so, how can I fix it?
Title: Re: Setting the Cursor
Post by: wondersonic on November 05, 2010, 06:24:03
Hi,
what is the format of the image? PNG?

Regards,
WS
Title: Re: Setting the Cursor
Post by: CodeBunny on November 05, 2010, 12:20:12
Yeah. I use a 24 x 33 png image.
Title: Re: Setting the Cursor
Post by: wondersonic on November 05, 2010, 14:13:51
something related to this post? http://lwjgl.org/forum/index.php/topic,3332.0.html
Title: Re: Setting the Cursor
Post by: Matthias on November 05, 2010, 14:32:11
You could also use TWL's PNGDecoder (also available as separate JAR): http://www.javagaming.org/index.php/topic,22435.0.html (http://www.javagaming.org/index.php/topic,22435.0.html) - it is faster and easier to use.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 05, 2010, 20:51:15
Isn't that limited to just PNG images? I'm trying to create a method that will support as many image types as possible - PNG, bitmap, GIF, JPEG (though granted jpeg would be a bad choice for cursor format), etc. The simplest way to do that, code wise, seems to be to use Java2D's native methods for reading images, then dealing with the single format that results.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 06, 2010, 18:30:11
Does anyone know how to set the cursor with Java2D methods? I'm running into a bit of a wall here and could use some help.
Title: Re: Setting the Cursor
Post by: jediTofu on November 07, 2010, 06:04:45
Perhaps the width and height of the cursor needs to be in powers of 2?
Title: Re: Setting the Cursor
Post by: CodeBunny on November 07, 2010, 12:07:21
I just tried that and it didn't change anything... good idea, though.

This is really bugging me - has anyone gotten this to work? I believe bobjob said this was how he did it, and I tried to base the method on what he recommended...
Title: Re: Setting the Cursor
Post by: Matthias on November 07, 2010, 13:42:12
TWL contains code to change the cursor - and it seems to work on every system :) You can take a look at the code.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 07, 2010, 14:13:21
One question: It's called "PNGDecoder." Doesn't that mean it works only for PNG images?
Title: Re: Setting the Cursor
Post by: Matthias on November 07, 2010, 15:40:14
sure - only PNGs :)
Title: Re: Setting the Cursor
Post by: jediTofu on November 07, 2010, 19:33:57
This works perfectly for me for both PNG and BMP files.  In order to get PNG's to work with varying alpha values, I had to take a percentage of each red, green, and blue value based off of the alpha value.  This uses Slick-Util, but Slick-Util uses ImageIO anyway.  You should be able to figure out how to change this for your Buffered Images.

Code: [Select]
   Texture tex = TextureLoader.getTexture("BMP",new FileInputStream("test.bmp"));
    int byteCount = 3; //Set this to 4 for PNG, TGA, etc.
    int trueWidth = tex.getImageWidth() * byteCount; //For speed

    int[] array = new int[tex.getImageWidth() * tex.getImageHeight()];
    //If you call getTextureData() continuously in the loop, it's very slow.
    byte[] data = tex.getTextureData();

    for(int y = 0; y < tex.getImageHeight(); y++) {
      for(int x = 0; x < tex.getImageWidth(); x++) {
        int i = y * trueWidth + x * byteCount;

        //Without using "& 0xFF," there were some bad color problems.
        //This is due to conversion from byte to int (a byte is -128 to 127,
        //and we want 0 to 255).
        int r = data[i]     & 0xFF;
        int g = data[i + 1] & 0xFF;
        int b = data[i + 2] & 0xFF;
        int a = 0xFF;

        if(byteCount == 4) {
          a = data[i + 3] & 0xFF;

          if(a > 0) {
            double ap = a / 255.0; //alpha percentage
            r = (int)Math.round(r * ap);
            g = (int)Math.round(g * ap);
            b = (int)Math.round(b * ap);
            a = 0xFF;
          }
        }

        array[y * tex.getImageWidth() + x] =
          (a << 24) |
          (r << 16) |
          (g <<  8) |
          (b);
      }
    }

    Cursor cursor = new Cursor(tex.getImageWidth(),tex.getImageHeight(),0,
      tex.getImageHeight() - 1,1,IntBuffer.wrap(array),null);
    Mouse.setNativeCursor(cursor);


EDIT:  For a Targa (TGA) file with transparency, use a byteCount of 4; for a Targa file without transparency, use a byteCount of 3.  Slick-Util does not support Targa files that use RLE compression... ::)
Title: Re: Setting the Cursor
Post by: jediTofu on November 08, 2010, 00:06:16
For a white background you could do the inverse of above...

You could just set a programmer-adjustable tolerance.  If alpha is below 50% (255 * 0.50), then set it to 0x00, else set it to 0xFF.  This is probably the best way, and just let the programmer know not to use varying degrees of alpha.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 10, 2010, 16:06:23
Thanks for the example.

One question - if you try to supply an intbuffer that holds data for translucent images, and the computer only supports 1-bit alpha cursors, does it simply convert the image, or does it mess up the cursor?
Title: Re: Setting the Cursor
Post by: CodeBunny on November 10, 2010, 18:36:45
Okay, I'm really confused here.  ???

I changed the method I use to see if your method would work, but I get almost the same problem as before.

Here's what I do to load the IntBuffer for the cursor:

Code: [Select]
int numBytes = 3;
if(type == "PNG" || type == "TGA")
{
numBytes = 4;
}
int width = texture.getImageWidth();
int height = texture.getImageHeight();
int[] imageData = new int[width * height];
byte[] bytes = texture.getTextureData();

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

int r = bytes[i] & 0xFF;
int g = bytes[i + 1] & 0xFF;
int b = bytes[i + 2] & 0xFF;
int a = 0xFF;

if(numBytes == 4)
{
a = bytes[i + 3] & 0xFF;

if(a > 0)
{
double ap = a / 255.0;
r = (int)Math.round(r * ap);
g = (int)Math.round(g * ap);
b = (int)Math.round(b * ap);
a = 0xFF;
}
}
imageData[y * width + x] = (a << 24) | (r << 16) | (g << 8) | (b);
}
}
return IntBuffer.wrap(imageData);

I tried the method with both a bmp and a png image and both became screwed up.
Title: Re: Setting the Cursor
Post by: jediTofu on November 10, 2010, 19:38:02
The only thing that I noticed wrong was your i variable, should be this:

int i = y * width * numBytes + x * numBytes;

Also, I forgot about texture.hasAlpha(), you can replace <code>type == "PNG" || type == "TGA"</code> with that.

If that still doesn't give you what you want, you can try an alpha tolerance as I suggested:

Code: [Select]
//parameter
double alphaTolerancePercentage; //between 0.0 and 1.0, play around with this; try 0.5 at first

//up at very top
int alphaTolerance = 256 * alphaTolerancePercentage; //256 so that "<" works

//replace in middle of code
if(numBytes == 4)
{
a = bytes[i + 3] & 0xFF;

if(a < alphaTolerance) {
a = 0;
}
else {
a = 0xFF;
}
}
Title: Re: Setting the Cursor
Post by: jediTofu on November 10, 2010, 19:46:57
One question - if you try to supply an intbuffer that holds data for translucent images, and the computer only supports 1-bit alpha cursors, does it simply convert the image, or does it mess up the cursor?

My machine only supports 1-bit alpha cursors.  When trying a PNG with varying alpha values, everything that is below 0xFF is truncated to 0, so only one pixel in the center of the image is shown (the only pixel with 0xFF alpha).

Also, I used a 64x64 image for all of my tests, forgot to mention this, so you might want to try a 64x64 image until you get your code working, and I tested it with a BMP, TGA (with and without transparency), and PNG; all worked fine.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 10, 2010, 20:01:21
You're right -

When I test it as a 64 x 64, it works fine. If I use different dimensions, it blows up.

I checked 32 x 64, and that worked. It looks like it needs to be a power of two, for whatever reason.
Title: Re: Setting the Cursor
Post by: jediTofu on November 10, 2010, 20:08:16
You're right -

When I test it as a 64 x 64, it works fine. If I use different dimensions, it blows up.

I checked 32 x 64, and that worked. It looks like it needs to be a power of two, for whatever reason.

Since I had my testing project open, I tried a 32x40 image, and it worked.  I think that there is just a minimum cursor size.  I couldn't find an equivalent LWJGL method for this, but the Toolkit works fine:

Dimension d = Toolkit.getDefaultToolkit().getBestCursorSize(0,0);

On my machine, d.height and d.weight are 32, so I assume any height/width below 32 won't work, and from my testing, it doesn't need to be power of 2 (at least on my machine).
Title: Re: Setting the Cursor
Post by: CodeBunny on November 10, 2010, 20:34:24
*sigh*

I found why it was locking to square roots of 2 - I needed to change

Code: [Select]
int i = y * width + x * numBytes;
To multiply y by the texture width, not the image width.



So, now the method sort of works for most images. There are two problems, though:

First, I'm getting the right colors but the alpha is still messed up somehow - sections that should be transparent appear black, and, more disturbingly, sections that should be opaque and colored in are showing images behind them.

Secondly, the method seems to break when the cursor gets very small for some reason. If I make the image something like 7 x 12, it starts getting wonky again.

If I make the cursor read the entire image, not just the image on top of it, it works better.
Title: Re: Setting the Cursor
Post by: jediTofu on November 10, 2010, 21:03:25
If I make the image something like 7 x 12, it starts getting wonky again.

Yes, this is probably due to the minimum cursor size I bet.  When passing in an int buffer below 32x32, it's all "wonky" for me.

Yeah, completely forgot should be using texture width/height and not image width/height, but this does perplex me...as my 32x40 image worked using the image height/width and not the texture height/width.  Your width still needs to be multiplied by numBytes though.

I'm not sure about your alpha problems, as it works fine for me.  You could upload the image you're trying to your post and post the code for setting the cursor, and I could try it.
Title: Re: Setting the Cursor
Post by: Matzon on November 10, 2010, 21:50:26
Since I had my testing project open, I tried a 32x40 image, and it worked.  I think that there is just a minimum cursor size.  I couldn't find an equivalent LWJGL method for this, but the Toolkit works fine:

Dimension d = Toolkit.getDefaultToolkit().getBestCursorSize(0,0);

http://lwjgl.org/javadoc/org/lwjgl/input/Cursor.html

get*CursorSize ?
Title: Re: Setting the Cursor
Post by: jediTofu on November 10, 2010, 21:55:37
http://lwjgl.org/javadoc/org/lwjgl/input/Cursor.html

get*CursorSize ?

Ah right, good call haha.
Title: Re: Setting the Cursor
Post by: CodeBunny on November 11, 2010, 12:56:16
I've managed to fix the cursor code for all supplied images, finally. It'll work when the image is under size by just making the additional needed space have no alpha.

Thanks for the help. :) That was an annoying problem.