The Source for Java Technology Collaboration
User: Password:



Alexander Potochkin's Blog

November 2007 Archives


Making Rainbow: Spotlight effect and soft clipping

Posted by alexfromsun on November 29, 2007 at 11:32 AM | Permalink | Comments (6)

Few weeks ago I submitted a session for the next JavaOne and it reminded me the previous session which we presented with Kirill

Before J1, he published a nice teaser with links to blogs about his famous ghost effects and transition layout, so it's hight time for me drop a few lines about implementing a spotLight effect. You can test the whole application on the main page of the Rainbow project, here is a flash demo which I made specially for this blog (press the green button on the bottom left side)


The shortest version of the SpotLightPainter was about 30 lines of code. I slightly modified the original painter from the Rainbow project and included the SpotLightPainter and the SpotLightDemo to the demo direcotry for JXLayer.
The painter's code is quite straightforward, there are just two things I'd like to comment:

ForegroundPainter

SpotlightPainter has a layered structure: the scrollPane's view component and the tranclucent foreground with the "holes" for matched components. I divide the painting code between two painters - SpotLightPainter extends DefaultPainter, so the wrapped component is automatically painted when you call super.paint(g2, l), the foregroundPainter is responsible for painting the tranclucent foreground:
    @Override
    public void paint(Graphics2D g2, JXLayer<V> l) {
        super.paint(g2, l);
        foregroundPainter.paint(g2, l);
    }

The foregroundPainter extends AbstractBufferedPainter which means it is painted via the intermediate image, this is the best solution when it comes to translucency. I fill the whole area with the translucent color and then clip out the shapes over the matched components:

        this.foregroundPainter = new AbstractBufferedPainter<V>() {
            @Override
            protected void paintToBuffer(Graphics2D g2, JXLayer<V> l) {
                
                // cleaning up
                g2.setComposite(AlphaComposite.Clear);
                g2.fillRect(0, 0, l.getWidth(), l.getHeight());
                
                // filling with overlayColor
                g2.setComposite(AlphaComposite.SrcOver);
                g2.setColor(overlayColor);
                g2.fillRect(0, 0, l.getWidth(), l.getHeight());
                
                // clipping out the "holes" for matched components
                for (Shape shape : clipList) {
                    g2.setClip(shape);
                    g2.setComposite(AlphaComposite.Clear);
                    g2.fill(shape);
                    softClipping(g2, shape);
                }
            }
        };

The important thing that by default, AbstractBufferedPainter.isIncrementalUpdate() returns false, it means that this painter will be updated only if the whole JXLayer is repainted, e.g. when the frame is resized or when SpotLightPainter.fireLayerItemChanged() is called. This optimization allows reusing the foreground image for faster painting, it doesn't slow down the transition effect animation because when you move the mouse over a button, foregroundPainter paints the cached image.

Soft clipping

When we prepared our session, Kirill drew my attention to the fact that spotlights didn't look smooth, here the zoomed out screenshot which shows how it looked like:
Initial clipping

as the result it was possible to see the jaggies on the spotlight's borders and that made this effect look unprofessional. The remedy for this problem is called "soft clipping" and the pefect article by Chris Cambell is the first thing that came to my mind when I thought about that.

The trick from Chris produces the very good result but it works for fixed shapes only, I mean you should know the exact shape in advance to create a mask for it. I decided to find a way how to make soft clipping for arbitrary shapes and I actually found it, but... a few minutes ago I came across the next Chris's blog Light and Shadow (I missed it for some reason) where he described exactly what I thought was invented by me! :-)
In the comments for that blog there are a few more reports from people who came to this solution even earlier.

The idea is to mix in the alpha value to the pixels on the shape's border by drawing the same shape with a set BasicStroke. For the Rainbow project I painted it once with the 50% alpha and doubled basicStroke width.

Initial clipping

For the JXLayer's demo I updated SpotLightPainter to be able to set the custom width for the soft clipping, and make it as smooth as you want:

    private void softClipping(Graphics2D g2, Shape shape) {
        g2.setComposite(AlphaComposite.Src);
        for (int i = 0; i < softClipWidth; i++) {
            int alpha = (i + 1) * overlayColor.getAlpha()
                    / (softClipWidth + 1);
            Color temp = new Color(
                    overlayColor.getRed(),
                    overlayColor.getGreen(), 
                    overlayColor.getBlue(), alpha);
            g2.setColor(temp);
            g2.setStroke(new BasicStroke(softClipWidth - i));
            g2.draw(shape);
        }
    }

Spotlight demo

MouseEvents filtering

Components which are outside the spotlights shouldn't react on the mouse events and this was the simplest task for the SpotLightPainter
    @Override
    public boolean contains(int x, int y, JXLayer<V> l) {
        for (Shape shape : clipList) {
            if (shape.contains(x, y)) {
                return true;
            }
        }
        return false;
    }

Conclusion

To add this functionality to your application you just need to wrap you component with JXLayer:
JXLayer<JComponent> layer = 
     new JXLayer<JComponent>(myComponent, spotLightPainter)

add(new JScrollPane(layer));

All source and binary files you can find on the SwingHelper project site

Thanks
alexp

Debug Swing repainting

Posted by alexfromsun on November 06, 2007 at 11:22 AM | Permalink | Comments (9)

As everybody knows if you need to repaint a Swing component you should call Component.repaint() method, another well-known tip for the fast painting is:

If only part of your component needs to be updated, make paint requests using a version of repaint that specifies the painting region.

It is very simple piece of advice - not to paint more than you need, but sometimes this situation is not easy to catch. Usually it is obvious when you paint less than you need, but repainting something which hasn't been changed is a kind of "invisible" operation.

If the whole frame is repainted when any of its children is repainted you would hardly notice it on modern computer and that is the reason why I decided to return to my old love, Swing debugging and add one debugging tool to JXLayer project

A visual indication for repainting events must work for every component, no matter which color it is painted, so I created an elegant layer's UI delegate which catches repainting and shows fade-out effect using inverted colors for a repainted area.

With this painter you can study your GUI and eliminate unnecessary painting, moreover it is very interesting just to see how Swing paints the core components - what part of a JTree is repainted when you open a node? What is repainted when you open a popup ?
Now it easy to get answers for this kind of questions.

For example, it is easy to see what part of JTextField is repainted when you type in it and what happens when JTree's selection is changed.I was also very pleased to check that we don't paint more than we need when selection of JTable is changed (second tab in the demo)

I hope you'll find it useful for painting optimization for your custom components and LookAndFeels.
As usual it works for all LookAndFeels, the only thing you need is to wrap a container with your components with JXLayer and set DebugPainter from JXLayer's demo package.

A flash presentation is available, press the green button below to start:

Have a nice day
alexp



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