Irregularly shaped windows, Java 6 & 7 ed

Pretty?

In the wake of the Bremen Town Musicians


As many people probably remember, in the last century (back in the days of Windows 2000 ), it was fashionable to create all sorts of splash screens and mini-applications in non-rectangular windows (as well as unusual controls).
These Ponte were written in C \ C ++ using WinAPI using the so-called. regions. This was not so simple, because I had to not only stumble over jambs and Windows and the language, but the miscalculation of polygons for rendering was also frightening. Therefore, having "drawn" one or two rounded windows, I put this topic aside for a long time.
And this Monday the article “Windows of the“ irregular ”form flashed, again drawing my attention to this topic. Expecting to find out that in .NET for this purpose WinAPI wrapper functions are implemented , I was disappointed to see descriptions of external functions. And here I, as a programmer mainly in Java , remembered that, then Sun , promised to introduce functions for rendering an arbitrary shape window.



Cup of coffee


Well, what opportunities did Sun / Oracle give us ?
  1. JNI / JNA :
    • platform dependent
    • write additional native code for each system
  2. Emulating transparency by drawing a background:
    • the window is still clickable
    • slow rendering
    • noticeable delays when moving the window
    • does not respond promptly to a change in background (especially, trouble with animation on the background)
  3. Class com.sun.awt.AWTUtilities :
    • only available from version 1.6_10 on Sun VM
    • an inner class whose support is not guaranteed
  4. Public methods of the classes java.awt.Window , java.awt.GraphicsEnvironment , java.awt.GraphicsConfiguration :
    • Available from version 1.7.0, cheers!
    • which is annoying: what about OpenJDK ?

At the moment, the most acceptable is the penultimate method. 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 arbitrary shape)

Regions, areas, edges


Having smoked, 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, like any geometric shapes . That is, if we manage to generate a closed Shape from our picture, similar to the region in Windows, the problem will be solved.
However, according to the established 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 completely transparent windows, but also, roughly speaking, “having holes” in the windows, and also that the current device is generally able to work with transparency (alas, Windows 98 in flight) You can, of course, and do not check, but then when you try to use these functions, the corresponding exception will fly out.
Still, it may turn out that the system has old Java installed and there will be no AWTUtilities at all - then you can use the auxiliary class AWTUtilitiesWrapper from this article, which works with AWTUtilitiesthrough reflection. I did not use it, since there is a good chance that everyone has updated Java, and the code becomes more incomprehensible. We will work directly with AWTUtilities :
  1. Before showing the window, remove the title bar (disable standard OS decorations):
    this.setUndecorated(true);
  2. Ask for the ability to make the window translucent:
    com.sun.awt.AWTUtilities.setWindowOpaque(this, false);
  3. Set the transparency of the window:
    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
    clicks
  4. having constructed the desired Shape , set the shape we need for the window:
    com.sun.awt.AWTUtilities.setWindowShape(this, s);

    Now, magically, everything that is outside our figure
    will not only not be drawn, but will not respond to clicks (remember the
    approach from the book " Swing Hacks ")
  5. Since we lost the standard title bar, but 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)
  6. Add the “Close” button to the window, also non-standard

These are the forms!


Next task: from any picture with a transparent background, well separated from the foreground, make its closed loop. For their perverted torture I Google it's such a lovely lady:
The young lady is not mine, this is just an example
At first I was thinking to implement a method of the convex hull ( convex hull ), but subsequently declined - our young lady is not only a very convex, but in some places and vpuklaya. In the evening, my silly brains weren’t enough other than to use the “forehead” solution: go over the pixels, removing the transparent pixels from the initial rectangle . The result is the following code:
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 (r.y = 0; r.y < h; r.y++) {
		System.out.println(r.y + "/" + h);
		for (r.x = 0; r.x < w; r.x++) {
			if ((i.getRGB(r.x, r.y) & 0xFF000000) != 0xFF000000) {
				s.subtract(new Area( r ));
			}
		}
	}
	return s;
}

Here, translucent pixels are also considered translucent (that is, for which 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, another thing turned out to be important - speed, because no one will wait 10 minutes when the load is 100%, while our Intro deigns to appear. Yes, and generating a form every time is unnecessary, it’s better to save it somewhere, so that it can be quickly downloaded and displayed.

R Tape loading error


Java has built-in tools for saving and loading any primitives and objects that implement the ava.io.Serializable interface and are also recursively serializable. But the trouble is: neither Shape, nor any of its implementing classes are serializable! Having suffered a lot for a long time, we 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 , into which you can write the previously saved contour. And here'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 class java.io.RandomAccessFile , which implements it, but you can write in java.io.ObjectOutputStream . You can (and even better) save the file next to the class-files, so that later you could get it 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, a correct file is required for a correct recovery - for simplicity I do not conduct any checks, and no exceptions are handled (but in general it would be necessary!). The file format is as follows:
  1. Contour bypass direction: clockwise or counterclockwise (integer)
  2. Any number of blocks:
    1. Curve Type (integer)
    2. Anchor points of the curve (array of 6 fractional)

Additionally, 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 limit myself to the Close button.
  • Native SWT controls :
    • view varies by platform
  • Swing Control Extension :
    • write a class for each element
    • difficult change of appearance (by that)
  • Writing your own Look-and-Feel :
    • troublesome
    • Of course, there is Synth ...
  • Decorating Swing Controls with javax.swing.plaf.LayerUI
    • Available only 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 a window
  • Extending the JButton Class
    • Make it transparent:
      this.setOpaque(false);
    • Disable background rendering:
      this.setContentAreaFilled(false);
    • We will also not draw a frame or a focus line:
      this.setFocusPainted(false);
      this.setBorderPainted(false);
    • We set explicit sizes so that no layout managers
      ( LayoutManager ) spoil the picture:
      this.setSize(this.result.size);
      this.setMinimumSize(this.result.size);
      this.setMaximumSize(this.result.size);
  • Replacing the paintComponent (Graphics g) method for drawing a picture
  • We replace the contains (int x, int y) method by delegating the same method to our path


Finita la comedia


All! We make the finishing touches: In the standard way, add the button to the window and hang the handler on it. We start and enjoy:
Pretty?
Like many of my projects, I store it on xp-dev.com : project page , svn repository
There are two projects for Eclipse Helios : Shaped for Java6 and Shaped7 for Java7. To reduce the difference in versions, specific requests were issued in class methods, and auxiliary functions in a separate utility class.



Read on:



Also popular now: