Implementing Cursor in Java with some Transparency
Asked Answered
B

4

7

I have a 35x40 px. png image I want to use as a custom cursor in a Swing application. The image has a glow so contains alpha transparency values. Problem is when I attempt to use the conventional method of using the Toolkit to generate the custom cursor I get black pixels where alpha transparency values should be.

Here is the image I am using for a cursor: https://dl.dropbox.com/u/1186703/cursor.png

Here is my code:

public static void main(String[] args) throws IOException {

     new Sandbox().gui();

}
private Cursor cursor;

private Toolkit kit;

private Image cursorImage;

public void gui() {

    kit = Toolkit.getDefaultToolkit();
    cursorImage = kit.createImage(getClass().getResource(
            "/aurora/V1/resources/cursor.png"));

    cursor = Toolkit.getDefaultToolkit().createCustomCursor(
            cursorImage, new Point(0, 0), "CustomCursor");

    setSize(800, 800);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);
    setVisible(true);
    setCursor(cursor);
}

Here is the current result:

result

Edit it seems that this method does not work well cross platform, for instance Windows LAF doesn't support semi-transparency. I am therefore looking for any solution to get this to work on windows, assuming this implementation does work on Mac OSX, i can just specify in code which implementation to use based on which operating system the app is running on.

Bichromate answered 10/11, 2012 at 21:28 Comment(4)
I don't actually think this is possible and it most certainly isn't possible across multiple platforms. If you know that your OS can support such an operation, you may be forced to use native methods to achieve it.Satan
Well, say this where to work in Mac OSX, what native methods would I have to take to make this work in Windows?Bichromate
I tried using JNA library but I failed: I cannot associate HCURSOR with the window.Berlinda
Java does not support partial transparency on Windows. You can load the cursor using JNA/JNI and associate it with the JFrame windows class WNDCLASSEX via SetClassLong but it has no effect on cursor over JFrame. Yet this approach works perfectly in Win32 API.Berlinda
S
6

The problem your having is to do with the Cursor class which (under Windows) doesn't take into account the transparency values of the image

This is, by no means, a "real" solution, but is more about "fudging" the result...

public class TestMouseCursor {

    public static void main(String[] args) {
        new TestMouseCursor();
    }

    public TestMouseCursor() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new MouseCursorPane());
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MouseCursorPane extends JPanel {

        private BufferedImage cursorImage;
        private Toolkit kit;

        public MouseCursorPane() {
            try {
                kit = Toolkit.getDefaultToolkit();
                cursorImage = ImageIO.read(getClass().getResource("/cursor02.png"));
                for (int i = 0; i < cursorImage.getHeight(); i++) {
                    int[] rgb = cursorImage.getRGB(0, i, cursorImage.getWidth(), 1, null, 0, cursorImage.getWidth() * 4);
                    for (int j = 0; j < rgb.length; j++) {
                        int alpha = (rgb[j] >> 24) & 255;
                        if (alpha < 128) {
                            alpha = 0;
                        } else {
                            alpha = 255;
                        }
                        rgb[j] &= 0x00ffffff;
                        rgb[j] = (alpha << 24) | rgb[j];
                    }
                    cursorImage.setRGB(0, i, cursorImage.getWidth(), 1, rgb, 0,
                            cursorImage.getWidth() * 4);
                }
                Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor(
                        cursorImage, new Point(0, 0), "CustomCursor");

                setCursor(cursor);

            } catch (Exception exp) {
                exp.printStackTrace();
            }
        }
    }
}

I got the idea for here

Satan answered 13/11, 2012 at 5:45 Comment(2)
it partially works, it removes all transparency values, even partial transparency is not there, so the blue glow is completely removed. Its better then before but unfortunately not good enough for what i'm looking for.Bichromate
I agree, but with everything I've read, this is the closet I think you'll get until some one fixes the cursorSatan
P
7

Your code and cursor image actually produces the desired result on MacOS X 10.7.5 (jdk 1.6.0_31) with semi-transparent blue border. But I did notice an off comment in this answer saying that partial transparency is not supported in the default Windows look and feel.

Phonogram answered 10/11, 2012 at 22:1 Comment(0)
S
6

The problem your having is to do with the Cursor class which (under Windows) doesn't take into account the transparency values of the image

This is, by no means, a "real" solution, but is more about "fudging" the result...

public class TestMouseCursor {

    public static void main(String[] args) {
        new TestMouseCursor();
    }

    public TestMouseCursor() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new MouseCursorPane());
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MouseCursorPane extends JPanel {

        private BufferedImage cursorImage;
        private Toolkit kit;

        public MouseCursorPane() {
            try {
                kit = Toolkit.getDefaultToolkit();
                cursorImage = ImageIO.read(getClass().getResource("/cursor02.png"));
                for (int i = 0; i < cursorImage.getHeight(); i++) {
                    int[] rgb = cursorImage.getRGB(0, i, cursorImage.getWidth(), 1, null, 0, cursorImage.getWidth() * 4);
                    for (int j = 0; j < rgb.length; j++) {
                        int alpha = (rgb[j] >> 24) & 255;
                        if (alpha < 128) {
                            alpha = 0;
                        } else {
                            alpha = 255;
                        }
                        rgb[j] &= 0x00ffffff;
                        rgb[j] = (alpha << 24) | rgb[j];
                    }
                    cursorImage.setRGB(0, i, cursorImage.getWidth(), 1, rgb, 0,
                            cursorImage.getWidth() * 4);
                }
                Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor(
                        cursorImage, new Point(0, 0), "CustomCursor");

                setCursor(cursor);

            } catch (Exception exp) {
                exp.printStackTrace();
            }
        }
    }
}

I got the idea for here

Satan answered 13/11, 2012 at 5:45 Comment(2)
it partially works, it removes all transparency values, even partial transparency is not there, so the blue glow is completely removed. Its better then before but unfortunately not good enough for what i'm looking for.Bichromate
I agree, but with everything I've read, this is the closet I think you'll get until some one fixes the cursorSatan
D
2

If you are desperate and absolutely must have transparent cursor, no matter the consequences, you can use JNI and set the cursor manually using Win32 API. Windows since XP support alpha cursors, so you should be ok with that.

But you lose platform independence. And based on Windows settings, the alpha blending might be turned off for that particular user.

Dose answered 14/11, 2012 at 14:44 Comment(1)
Why would you lose platform independence? Just do a special case IF it's windows. On the Mac the alpha works fine anyway, so this code doesn't need to be called. Also, how would one go about using JNI to set the cursor using Windows 32 API anyway? Got any links to help me get started? (Sorry about the necropost)Sheepshearing
E
2

An alternative is to fake a cursor.

Take a look at Alexander Potochkin's Well Behaved GlassPane.

In particular, run the sample code, choose Options>GlassPane is Visible and Options>Final GlassPane.

Starting from this, load up a cursor image that is completely transparent, then paint a proper, alpha-blended cursor on the glasspane.

Eleen answered 18/11, 2012 at 0:41 Comment(1)
Problem is I use a bunch of different Mouse Listeners and this method seems to cause a lot of weird behavior like buttons dont show hover states and it causes a lot of repainting which decreases performance of the application plus tracking the mouse is not as fast as a more native approach. Too many negatives using this method.Bichromate

© 2022 - 2024 — McMap. All rights reserved.