The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov's Blog

October 2006 Archives


Animating layouts part VI - welcome to the real world

Posted by kirillcool on October 30, 2006 at 11:02 PM | Permalink | Comments (0)

This is the sixth part in a series about automatically animated layouts in Swing applications.

  • The first part introduced the TransitionLayout. This part showed animated versions of BorderLayout and FlowLayout.
  • The second part showed the TransitionLayout applied to a (at least partially) real-world image viewer that allows live thumbnail resizing.
  • The third part described the current implementation and alternatives that were considered.
  • The fourth part showed a simple demo of an application that TransitionLayout installed on different containers.
  • The fifth part introduced overlapping / layered transitions over the same component.

Once i started to test the TransitionLayout on the Substance test application (and not on a simple app shown in the fourth part of this series), i saw all sorts of visual inconsistencies. The most glaring of these were on borders and on desktop panes.

As you may already know, the borders are not really a part of the look-and-feel domain. Sure, the look-and-feel can install the borders on the component hierarchy, but only if the currently set border is not a UIResource. Hence, if you have installed a TitledBorder, a LineBorder or some custom border, it will remain installed and respected by all core and most third-party LAFs. When the border is painted? The answer is in a quite convoluted JComponent.paint(Graphics) method. It is done after the component is painted (by the relevant LAF delegate) and before the component's children are painted. What does it mean? That the neither TransitionLayout nor the look-and-feel can change the translucency of the border painting without replacing the border itself.

The answer, therefore is quite simple. After a TransitionLayout is installed, it is called every time that the layout should be performed. When the layoutContainer is called, we scan the entire component hierarchy and replace all the borders with the TransitionBorder implementation which simply delegates all the logic to the original border. The only thing it adds is to set the opacity on the Graphics in the paintBorder implementation. When the TransitionLayout is unset, it restores the original borders. Special care needs to be taken in order to preserve the UIResource-ness of the installed border - see the code (scroll down to the installBorders method.

Is it an optimal solution? Not really, since it may interfere with the application code. The application code may have some property change listeners on the components, and it may also have some hard-coded assumptions about the installed borders (like changing the text of the TitledBorder or the color of the LineBorder). If you have better alternatives - add them in the comment section.

Another issue that i ran into was with the JDesktopPane. This drove me crazy for a few good hours of debugging and perusing the code. The problem was this - whenever a transition (fade-in / fade-out) involved a panel with JDesktopPane, the background of that pane was opaque. After losing some hair (well, that would've happened anyway), i found two very interesting pieces of code deep down in Swing

The first is the JDesktopPane.isOpaque() which always returns true (huh?). The second is in the JLayeredPane.paint(Graphics) which paints the entire component in the currently set background color / light gray if it's null when the component is opaque. Since JDesktopPane extends the JLayeredPane, the translucency settings set in the look-and-feel delegate are simply ignored. As far as i can tell, this is the only core Swing component that has painting logic outside of the LAF delegates. It is quite strange, but i guess that the TransitionLayout is pushing Swing into directions not thought of ten years ago :)

Thankfully, the implementation of the JLayeredPane.paint(Graphics) left a loophole that i have been able to exploit. If a completely transparent color is set as a background, the background of JDesktopPane is not filled. All that was left to do in the desktop pane delegate was to fill the background based on the current transition opacity and then paint the component itself.

Is it an optimal solution? Almost. The only thing left to do in the next version is to respect the original background color set on the desktop pane in the painting and restore it when the LAF is unset. Will it provide a complete solution? No - since the getBackground method will return an incorrect (completely transparent) value.

There were some other less interesting issues encountered and fixed along the way. As always, i'm thoroughly impressed by what Swing allows us to do and as always, i learned something new about it. The code is the release candidate stage as of today and will be officially released on November 13 (unless some real big problems are found).



Adding a date stamp to your digital pictures

Posted by kirillcool on October 30, 2006 at 10:17 AM | Permalink | Comments (0)

You can call me old-fashioned, but i prefer holding a photo album in my hands (the same goes for the books). Sure, it feels bulky and may require more energy spent to page through it, but it just feels right. Especially now with the new baby in town we take quite a lot of pictures and send them to the proud grandparents and other selected family members. How do we do it - copy the pictures to a disk-on-key, drop it at the nearest photoshop and pick up the printed pictures an hour later. The only problem is that neither our camera (one of this Pentax line, not that photoshop can add a timestamp in the bottom-right (or any for that matter) corner of the picture.

This may be fine for now, but when i look through older pictures of me and my family, it's nice to know when the pictures were taken. Of course, i could look for another photoshop, but then i can do it myself. So, without further ado, here is how you can add a date stamp in the bottom-right corner and have it not cut-off when your 4:3 file is printed on a 3:2 paper:

  public static void addDate(File inputFilethrows Exception {
    String inputFilename = inputFile.getPath();
    System.out.println("Starting with '" + inputFilename + "'");
    BufferedImage input = ImageIO.read(inputFile);
    Graphics2D graphics = (Graphics2Dinput.createGraphics();
    Date lastModified = new Date(inputFile.lastModified());
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    String dateStamp = sdf.format(lastModified);
    int fontSize = (int) (0.4 * input.getHeight() 10.0);
    Font font = new Font("Arial", Font.BOLD, fontSize);
    graphics.setFont(font);
    int height = graphics.getFontMetrics().getHeight();
    int width = graphics.getFontMetrics().stringWidth(dateStamp);

    BufferedImage dateImage = new BufferedImage(* fontSize + width, 2
        * fontSize + height, BufferedImage.TYPE_INT_ARGB);
    Graphics2D dateGraphics = (Graphics2DdateImage.getGraphics().create();
    dateGraphics.setColor(new Color(0000));
    dateGraphics.setComposite(AlphaComposite.Src);
    dateGraphics.fillRect(00, width, height);

    dateGraphics.setComposite(AlphaComposite.SrcOver);
    dateGraphics.setFont(font);
    dateGraphics.setColor(Color.black);
    dateGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    GlyphVector gv = font.createGlyphVector(dateGraphics
        .getFontRenderContext(), dateStamp);
    // dateGraphics.drawString(dateStamp, fontSize, fontSize);
    dateGraphics.translate(fontSize, * fontSize);
    for (int i = 0; i < gv.getNumGlyphs(); i++) {
      Shape glyph = gv.getGlyphOutline(i);
      dateGraphics.setStroke(new BasicStroke(fontSize / 10));
      dateGraphics.draw(glyph);
    }
    float[] kernel = new float[25];
    for (int i = 0; i < kernel.length; i++)
      kernel[i1.0f / kernel.length;
    ConvolveOp cOp = new ConvolveOp(new Kernel(55, kernel));
    BufferedImage blurred = cOp.filter(dateImage, null);
    dateGraphics.dispose();

    int iWidth = input.getWidth();
    int iHeight = input.getHeight();
    int cHeight = iWidth * 3;
    int dHeight = (iHeight - cHeight2;

    int y = (iHeight - dHeight- blurred.getHeight();
    int x = iWidth - blurred.getWidth();

    graphics.drawImage(blurred, x - fontSize, y - * fontSize, null);
    graphics.setFont(font);
    graphics.setColor(Color.white);
    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    graphics.drawString(dateStamp, x, y);
    graphics.dispose();

    int lastDotIndex = inputFilename.lastIndexOf('.');
    String outputFilename = inputFilename.substring(0, lastDotIndex)
        ".new" + inputFilename.substring(lastDotIndex);
    FileOutputStream out = new FileOutputStream(outputFilename);
    /* encodes image as a JPEG data stream */
    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
    JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(input);
    param.setQuality(0.9ftrue);
    encoder.setJPEGEncodeParam(param);
    encoder.encode(input);
    System.out.println("Ending with '" + inputFilename + "'");
  }

Of course, it's not a pinnacle of Java2D programming, nor was it intended to. Since i don't have any imaging-related java.net project, i'm not intending to polish it to perfection. It even uses classes in the com.sun.image.codec.jpeg package. Hereby it is released to the public domain - feel free to use, reuse and abuse it in any way - but don't come back if your nuclear reactor stops functioning because of this :)

The result is quite OK:

imagedate.png

Animating layouts part V - layered / overlapping transitions

Posted by kirillcool on October 27, 2006 at 08:04 AM | Permalink | Comments (4)

This is the fifth part in a series about automatically animated layouts in Swing applications.

  • The first part introduced the TransitionLayout. This part showed animated versions of BorderLayout and FlowLayout.
  • The second part showed the TransitionLayout applied to a (at least partially) real-world image viewer that allows live thumbnail resizing.
  • The third part described the current implementation and alternatives that were considered.
  • The fourth part showed a simple demo of an application that TransitionLayout installed on different containers.

With the release candidate of laf-widget version 2.0 (code-named Bluebottle) on October 30th (this Monday), i have started some more intensive testing of the TransitionLayout and (quite expectedly), i ran into quite a few visual issues. Some of them will be addressed in the next part (over this weekend), and the rest is discussed here.

First, you are welcome to read this thread on Java2D mailing list about compound composites on Graphics2D. As Chris points out, the best way to combine multiple composites is to create intermediate images. This is what i have been doing in Substance to support the transition fades (see the third part of this series for the reasons why). It works, but obviously it has some disadvantages. One would be that you are creating intermediate images which are later garbage collected, and another that the code becomes much more complicated and harder to read (especially a few weeks after you wrote it :). Going back to the issue at hand - the transition layout. Here is a scenario:

You have a tabbed pane with two panels. The first panel has some buttons, the second has some other controls (scroll down to see Flash movies). When you click some button in the first panel, it disappears (setVisible(false)) and some other controls appear instead. While they appear, you switch to the second panel. Now, if the TransitionLayout is installed on both the first panel and on the tabbed pane, the controls on the first panel (the disappearing button and the appearing controls) participate in two different transitions. The first transition is within the panel itself, and the second transition is the fade-out of the first panel when the tabbed pane is switched to the second panel. This means that the resulting composite of the controls on the first panel depend on multiple transitions that are happening at the same time.

Previously, this has not been taken into account. You would see two transitions "competing" over the same control - the visual result would be far from the expected (sudden changes in translucency in the middle of the fade cycles and so on). It was especially noticeable under slow animation speed (which can be set in the debug UI mode as described in this entry). These issues have been addressed in the latest drops of laf-widget and Substance and here are the examples.

This example shows tab switch to a panel that contains a button. While the button panel is fading in, the button is clicked, causing it to disappear and three other buttons to appear. There is a section when the three new buttons are fading in on top of the switched-off tab:

This example shows tab switch from a panel that contains a button. Before the button panel is fading out, the button is clicked, causing it to disappear and three other buttons to appear. There is a section when the three new buttons are fading in on top of the switched-on tab:

This example shows tab switch from a panel that contains six buttons. Before the tab switch is initiated, another button is clicked, causing two of the six buttons to disappear and other four to move (the original layout is FlowLayout). While the button panel is fading out, you can see the buttons disappear / move on top of the switched-on tab:

This example shows tab switch to a panel that contains five buttons. After the tab switch is initiated, another button is clicked, causing another (sixth) button to appear and other two to move (the original layout is FlowLayout). While the button panel is fading in, you can see the buttons disappear / move on top of the switched-off tab:

There is a new helper API in the TransitionLayout class targeted at look-and-feel delegates and custom application painting code. This API retrieves the opacity and the composite that should be used by the painting code in order to respect the currently executing (possibly multiple / overlaying) transition(s):

  /**
   * Returns the composite to use for painting the specified component. The
   * result should be set on the {@link Graphics2D} before any custom
   * rendering is done. This method can be used by application painting code
   * and by look-and-feel delegates.
   
   @param c
   *            Component.
   @return The composite to use for painting the specified component.
   */
  public static Composite getAlphaComposite(Component c)

  /**
   * Returns the composite to use for painting the specified component. The
   * result should be set on the {@link Graphics2D} before any custom
   * rendering is done. This method can be used by application painting code
   * and by look-and-feel delegates.
   
   @param c
   *            Component.
   @param translucency
   *            The translucency of the original painting (when the component
   *            is not under any transition fade effect).
   @return The composite to use for painting the specified component.
   */
  public static Composite getAlphaComposite(Component c, float translucency)

  /**
   * Returns indication whether the specified component is opaque. This method
   * can be used by look-and-feel delegates to decide whether the component
   * background should be filled. Use in conjunction with
   {@link #getAlphaComposite(Component)} or
   {@link #getAlphaComposite(Component, float)} to correctly fill the
   * component background during the transition fade animations. Note that
   * during the transition fades, the components are marked as non-opaque so
   * that Swing will handle them correctly. This means that calling
   {@link Component#isOpaque()} will return <code>false</code>,
   * incorrectly signifying to the painting code that the background fill
   * should be omitted.
   
   @param c
   *            Component.
   @return <code>true</code> if the specified component is opaque,
   *         <code>false</code> otherwise.
   */
  public static boolean isOpaque(Component c)

If your application doesn't have any custom painting code and you're running under the latest Substance, you don't have to change a single line in your code (except installing the TransitionLayout, obviously) to get the effects shown above. If you do have custom painting logic, all you need to do is to create a Graphics2D and set the appropriate composite:

  protected void paintComponent(Graphics g) {
    Graphics2D graphics = (Graphics2Dg.create();
    graphics.setComposite(TransitionLayout.getAlphaComposite(this));

Summing up - the new version of TransitionLayout handles cases like:

  • Component is fading in while its parent is fading out.
  • Component is fading out while its parent is fading in.
  • Component is fading in while its parent is fading in.
  • Component is fading out while its parent is fading out.

And not necessarily limited to two overlapping transitions. You're welcome to try it on your apps.



Caution: rough seas ahead. You appear to be using an older version of Firefox.

Posted by kirillcool on October 25, 2006 at 10:42 PM | Permalink | Comments (1)

I don't know about you, but some of the error messages out there are pretty lame. The Daily WTF site contains some of these here and here. Today this has happened to me, but not for very obvious reason.

My inner geek installed the development (daily) version of Firefox (code-named Bon Echo) on my home machine. It downloads and installs the latest version of Bon Echo every evening and politely asks to reinstall itself. Since it restores the session and doesn't crash too often, i'm OK with it for now. But here is what Yahoo! Mail showed me after the last Bon Echo restart:

roughseas.png

While this is admittedly funny (and for now the majority of Firefox users are geeks, so it's OK), here is what the Help/About screen shows:

bonecho.png

So - how about investing some time in verifying that something is an error instead of investing time in reporting it?



Checkers Easter egg for Swing applications

Posted by kirillcool on October 22, 2006 at 10:45 PM | Permalink | Comments (2)

The laf-widget project allows not only plugging additional "feel" part to the Swing apps (described in this java.net article), but also allows planting Easter Eggs in Swing apps without any change to the app code. The Matrix rain adds a Matrix-inspired screensaver, while Trellis project adds a line of Tetris-inspired games. And today another one of my pet projects was revived.

The game of Checkers (or Draughts) is much simpler than Chess (although I'm Russian, i never got past the basic moves :) and is real fun to play (i remember i played it for hours when i was a kid). Now you can play it against the computer using the Checkers project. It can be launched as either a standalone application (main class is org.jvnet.lafwidget.checkers.Checkers) or as a laf-widget plugin. Simply drop the checkers.jar somewhere in the classpath, run your app under laf-widget compliant look-and-feel, press Ctrl+Alt+C in any frame and begin to play.

Note that this was written in 2001 for AWT and Java 1.1. The GUI code is a complete mess, and shouldn't be reused anywhere. The engine itself, however, is quite fine. It uses the alpha-beta pruning search to find the next best move for the computer. It actually is quite good (i never have beaten it on the hard level, but then it's only me) - enjoy. Here are some screenshots:




SVG and Java UIs part 6: transcoding SVG to pure Java2D code

Posted by kirillcool on October 20, 2006 at 11:01 PM | Permalink | Comments (31)

This entry is the sixth part in the ongoing series about using SVG-based icons in Swing-based applications.

  • The first part outlined the need for using scalable icons in next-generation Java UIs and proposed using SVG images as a possible format, showing the ribbon component which employs heavy icon rescaling.
  • The second entry introduced an SVG file previewer based on the breadcrumb bar component, a ribbon button from the ribbon component and SVG Salamander renderer
  • The third part showed how the Apache Batik SVG renderer can initialize and scale SVG-based application in an asynchronous and non-blocking fashion.
  • The fourth entry showed what happens beneath the hood and described some of the problems found with Batik integration.
  • The fifth entry described the feedback from Batik developer and improvements made to the ribbon and SVG file previewer.

Over the past few months i have seen quite a few questions about converting SVG files to pure Java2D painting code. Since no such converter (to the best of my knowledge) exists (at least in the open-source world), this has been implemented in the latest drop of Flamingo project (release candidate of version 1.1 code-named Briana is scheduled for October 30). How do you run it? Very simple - click on the WebStart link below, grant all permissions (it needs read access to read the SVG files and write access to create the Java2D-based classes), use the breadcrumb bar to navigate to a folder that contains SVG files, wait for them to appear (they'll be loaded asynchronously) and just start clicking on the icons.

Clicking on an icon will create a Java class under the same folder with the same name (spaces and hyphens are replaced by the underscores). The class will have a single static paint method that gets a Graphics2D object and paints the icon. Note that before you call this method, you can set any AffineTransform on the Graphics2D that you pass to the method in order to scale, shear or rotate the painting.

Here are few known limitations of this tool:

  • The generated code requires Mustang to run, since it uses the LinearGradientPaint and RadialGradientPaint mentioned in Chris's blog.
  • Since Mustang's versions of these classes are a little different from Batik's implementation, some of the SVGs fail to transcode (when the stop fractions are not strictly increasing).
  • The TextNode is not supported. The support for this would involve writing and debugging tons of text-related code. I don't know about general usage, but Tango iconset uses this only on 2 out of 203 icons. If you want to chip in and provide the support - you're welcome.

Here is how you use the generated code:

public class Test extends JFrame {
  public static class TestPanel extends JPanel {
    @Override
    public void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2Dg.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
          RenderingHints.VALUE_INTERPOLATION_BILINEAR);
      g2.translate(1010);
      address_book_new.paint(g2);
      g2.translate(500);
      g2.transform(AffineTransform.getScaleInstance(2.02.0));
      internet_web_browser.paint(g2);
      g2.dispose();
    }
  }

  public Test() {
    super("SVG samples");
    this.setLayout(new BorderLayout());
    this.add(new TestPanel(), BorderLayout.CENTER);

    this.setSize(180140);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
  }

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new Test().setVisible(true);
      }
    });
  }
}

The address_book_new and internet_web_browser are the transcoded classes. Note that before the second icon is painted, i apply the scaling transformation (to illustrate how it is done). The result is:

Note how the second icon is scaled (relative to the first one).

The size of the generated code is comparable to the size of the original SVG file. The first icon in the above example takes 20KB in SVG format and 22KB in Java2D code. The second icon is 50KB in SVG format and 55KB in Java2D code. The generated code itself contains a few comments to help in mapping the Java2D sections to the corresponding SVG sections, but in general you don't need to look at it at all (as you wouldn't look at the SVG contents).

The implementation is quite straightforward. It uses the Apache Batik library (that's why the WebStart is so big) to load the SVG file and create a renderer tree (called GVT renderer tree). The nodes in this tree can be mapped directly to Java2D code. The only tricky part is in chaining and restoring transformations on nested nodes. In addition, some Batik classes do not provide getters for the relevant properties - i had to use reflection to obtain the field values.

As already mentioned, this tool has been successfully tested on the Tango iconset. Apart from two known issues (TextNode support and non-strictly increasing fractions), all the icons have been converted and displayed properly. If you're trying it on other SVG files and see UnsupportedOperationException, feel free to send me the relevant SVG file. Happy converting.



Animating your lists, tables and trees - part II

Posted by kirillcool on October 20, 2006 at 10:31 PM | Permalink | Comments (5)

The first part of this series introduced animation fades on lists, trees and tables in Swing applications running under the latest development version of Substance look-and-feel. The release candidate for version 3.1 (code-named Honolulu) is on October 30, and there is still room to add minor user-requested features. The users who download and try the development drops inevitably face defects, but they also have a great opportunity to comment and affect new features. One of the users (thanks, Raj) requested that the table rollover animation should highlight the entire row.

Now, this is not as straightforward as it sounds, since the JTable API provides two methods, setRowSelectionAllowed and setColumnSelectionAllowed. When both are set to either true or false, clicking a cell selects that cell. When the first is set to true and the second to false, clicking on a cell selects the entire row. When the first is set to false and the second to true, clicking on a cell selects the entire column.

The latest version of 3.1dev drop features the rollover animation effects that follow the same algorithm. Click on a play button below to view a short screencast that illustrates this feature.





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds