
In the wake of the musicians of Bremen
As probably many people remember, in the past century (even in the days of
Windows 2000 ), it was fashionable to create all kinds of
splash screens and mini-appliqués in non-rectangular windows (as well as unusual controls).
These ponies were written in
C /
C ++ using
WinAPI using so-called. regions. It was not so simple, because you had to not only stumble over the jambs and Windows and the language, but also the miscalculation of the polygons for drawing was also scaring. Therefore, “drawing” one or two rounded windows, I put this topic down indefinitely.
And this Monday, the article
“Windows of the" wrong "form" flashed, again drawing my attention to this topic. Expecting to know that the
WinAPI wrapper functions were implemented in
.NET , was disappointed to see descriptions of external functions. And here I, as a programmer mostly in
Java , remembered that, then still
Sun , promised to introduce functions for drawing a window of arbitrary shape.
Cup of coffee
Well, what opportunities did
Sun /
Oracle provide us?
- JNI / JNA :
- platform dependent
- write additional native code for each system
- Transparency emulation by drawing the background:
- the window is still clickable
- slow drawing
- noticeable delays when moving the window
- does not respond quickly to changes in the background (especially trouble with animation in the background)
- Class com.sun.awt.AWTUtilities :
- Available from version 1.6_10 only in Sun VM
- inner class whose support is not guaranteed
- Public methods of java.awt.Window , java.awt.GraphicsEnvironment , java.awt.GraphicsConfiguration classes:
- Available from version 1.7.0, cheers!
- which is annoying: what about OpenJDK ?
At the moment, the penultimate method is the most acceptable. So, the task we have is this:
Draw a translucent window of complex non-rectangular shape (based on the image), with a background image, with the ability to move the window, resize the window, place any controls (both standard and also of arbitrary shape)Regions, regions, territories
Having smoked, having smoked manuals and tutorials, it became clear that the classes that implement the
java.awt.Shape interface are responsible for the shape of the window, as well as any geometric shapes. That is, if we manage to generate a closed
Shape from our image, similarly to the region in Windows, the problem will be solved.
However, according to the prevailing cross-platform tradition, you need to make sure that the OS itself is able to draw such windows. Using
AWTUtilities , we write, for example, the following code:
if((com.sun.awt.AWTUtilities.isTranslucencySupported (com.sun.awt.AWTUtilities.Translucency.PERPIXEL_TRANSLUCENT)) && (com.sun.awt.AWTUtilities.isTranslucencyCapable (GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()))) {
PERPIXEL_TRANSLUCENT means that the graphics system is capable of displaying not only fully transparent windows, but also, roughly speaking, “have holes” in the windows, and also that the current device is able to work with transparency (alas, Windows 98 in the span). and do not check, but then when you try to use these functions, the corresponding exception will crash.
Also, it may be that the old Java is installed in the system and there are no
AWTUtilities at all - then you can use the auxiliary class
AWTUtilitiesWrapper from
this article that works with
AWTUtilities through reflection. I did not begin to use it, because the probability is sufficient that everyone has updated Java, and the code becomes more incomprehensible. We will work directly with
AWTUtilities :
- Before showing the window, remove the title bar (disable the standard OS decorations):
this.setUndecorated(true);
- Ask for the ability to make the window translucent:
com.sun.awt.AWTUtilities.setWindowOpaque(this, false);
- Set the window transparency level:
com.sun.awt.AWTUtilities.setWindowOpacity(this, .75);
The value of the second argument must be between 0 - 1 (in our
case 75%).
You can even make the window completely transparent, but it will respond to
pressing - construct the desired Shape , set the shape we need for the window:
com.sun.awt.AWTUtilities.setWindowShape(this, s);
Now, in a magical way, everything that is outside of our figure is not
It will not only not be drawn, but will also not respond to pressing (recall
approach from the book " Swing Hacks ") - Once we lost the standard header strip, but we want to move the window,
add the ability to move the window by clicking anywhere in the window (for this I
used the MoveMouseListener class from the same book) - Add the “Close” button to the window, also non-standard
Here are the forms!
The next task: from any picture with a transparent background, well separated from the foreground, make up its closed contour. For my perverted torture, I googled this lovely young lady:

At first, I was thinking of implementing the
convex hull method, but later refused - our young lady is not only very convex, but in some places convex. On the other, my stupid brains were not enough in the evening, except for using the solution "head-on":
run through the pixels, removing the transparent pixels from the initial rectangle . Such code turned out:
static Shape contour(final BufferedImage i) { final int w = i.getWidth(); final int h = i.getHeight(); final Area s = new Area(new Rectangle(w, h)); final Rectangle r = new Rectangle(0, 0, 1, 1); for (ry = 0; ry < h; r.y++) { System.out.println(ry + "/" + h); for (rx = 0; rx < w; r.x++) { if ((i.getRGB(rx, ry) & 0xFF000000) != 0xFF000000) { s.subtract(new Area( r )); } } } return s; }
Transparent pixels are also considered to be translucent, too (ie, with
alpha <255) - of course, if necessary, you can specify a certain threshold value, or even assign any color, say, white: 0xFFFFFF). But it doesn’t matter, something else turned out to be important - speed, because no one will wait 10 minutes at a load of 100% while our Intro deigns to seem. Yes, and generate the form every time unnecessarily, it is better to save somewhere, which would then quickly load and show.
R Tape loading error
Java has built-in tools for saving and loading any primitives and objects that implement the
ava.io.Serializable interface, as well as recursively serializable. But the trouble is: neither Shape, nor any of its implementing classes are serializable! After quite a while, I managed to get the
java.awt.geom.PathIterator interface, which allows you to run around the contour to save it, and the
java.awt.geom.GeneralPath class, in which you can write the previously saved contour. And that's what happened:
static void save(final Shape s, final DataOutput os) throws IOException { final PathIterator pi = s.getPathIterator(null); os.writeInt(pi.getWindingRule()); System.out.println(pi.getWindingRule()); while (!pi.isDone()) { final double[] coords = new double[6]; final int type = pi.currentSegment(coords); os.writeInt(type); System.out.println(type); for (final double coord : coords) { os.writeDouble(coord); System.out.println(coord); } System.out.println(""); pi.next(); } }
Using the
java.io.DataOutput interface allows you to save data anywhere - I used the rare
java.io.RandomAccessFile class that implements it, but you can write it in
java.io.ObjectOutputStream . It is possible (and even better) to save the file next to the class files, so that later it can be obtained even from the archive with the applet.
static Shape load(final DataInput is) throws IOException { final GeneralPath gp = new GeneralPath(is.readInt()); final double[] data = new double[6]; CYC: while (true) { final int type = is.readInt(); for (int i = 0; i < data.length; i++) { data[i] = is.readDouble(); } switch (type) { case PathIterator.SEG_MOVETO: gp.moveTo(data[0], data[1]); break; case PathIterator.SEG_LINETO: gp.lineTo(data[0], data[1]); break; case PathIterator.SEG_QUADTO: gp.quadTo(data[0], data[1], data[2], data[3]); break; case PathIterator.SEG_CUBICTO: gp.curveTo(data[0], data[1], data[2], data[3], data[4], data[5]); break; case PathIterator.SEG_CLOSE: break CYC; } } return gp.createTransformedShape(null); }
Of course, the correct file is necessary for correct recovery - for simplicity, I do not conduct any checks, and no exceptions are processed (but in general it would be necessary!). The file format is:
- Contour traversal direction: clockwise or counterclockwise (integer)
- Any number of blocks:
- Curve type (integer)
- Curve anchor points (array of 6 fractional)
In addition, we draw a background image in a completely standard way - by overloading the
paint (Graphics g) method, or rather
paintComponent (Graphics g)')
All or nothing
Well, if we ourselves draw a window, then for completeness, we also need to draw our own controls. For now, I’ll confine myself to the “Close” button.
- SWT native controls:
- Swing Control Extension:
- write a class for each element
- difficult change of appearance (order)
- Writing Your Own Look-and-Feel :
- troublesome
- Of course, there is Synth ...
- Decorating Swing Controls with javax.swing.plaf.LayerUI
- Only available from version 1.7.0 :(
To demonstrate, I went the second way:
- We generate a picture and a contour in the same way as for the window.
- Extending the JButton class
- Making it transparent:
this.setOpaque(false);
- Prohibit the drawing of the background:
this.setContentAreaFilled(false);
- Also we will not draw either a frame or a line of focus:
this.setFocusPainted(false); this.setBorderPainted(false);
- Set explicit dimensions so that no layout managers
( LayoutManager ) did not spoil the picture:
this.setSize(this.result.size); this.setMinimumSize(this.result.size); this.setMaximumSize(this.result.size);
- Replace the paintComponent (Graphics g) method to draw a picture.
- Replace the contains (int x, int y) method by delegating the same method to our path.
Finita la comedia
Everything! Making the finishing touches: In a standard way, add a button to the window and hang a handler on it. Run and admire:

Like many of my projects, I keep on
xp-dev.com :
project page ,
svn repositoryThere are two projects for
Eclipse Helios : Shaped for Java6 and Shaped7 for Java7. To reduce the difference in versions, specific queries were made to class methods, and auxiliary functions to a separate utility class.
Pulp fiction: