Drawing a Component to BufferedImage causes display corruption

时光毁灭记忆、已成空白 提交于 2019-11-26 19:11:41
trashgod

Summary: The original JScrollNavigator uses the Swing opacity property to render a convenient green NavBox over a scaled thumbnail of the component in an adjacent JScrollPane. Because it extends JPanel, the (shared) UI delegate's use of opacity conflicts with that of the scrollable component. The images seen in edit 5 above typify the associated rendering artifact, also shown here. The solution is to let NavBox, JScrollNavigator and the scrollable component extend JComponent, as suggested in the second addendum below. Each component can then manage it's own properties individually.

I see no unusual rendering artifact with your code as posted on my platform, Mac OS X, Java 1.6. Sorry, I don't see any glaring portability violations.

A few probably irrelevant, but perhaps useful, observations.

  • Even if you use setSize(), appropriately in this case, you should still pack() the enclosing Window.

    f.pack();
    f.setSize(300, 200);
    
  • For convenience, add() forwards the component to the content pane.

    f.add(nav, BorderLayout.WEST);
    
  • Prefer StringBuilder to StringBuffer.

  • Consider ComponentAdapter in place of ComponentListener.

Addendum: As suggested here, I got somewhat more flexible results using RenderingHints instead of getScaledInstance() as shown below. Adding a few icons makes it easier to see the disparate effect on images and text.

editPane.insertIcon(UIManager.getIcon("OptionPane.errorIcon"));
editPane.insertIcon(UIManager.getIcon("OptionPane.warningIcon"));
...
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Component view = jScrollPane.getViewport().getView();
    BufferedImage img = new BufferedImage(view.getWidth(),
        view.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D off = img.createGraphics();
    off.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    off.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
        RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    view.paint(off);
    Graphics2D on = (Graphics2D)g;
    on.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    on.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
        RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    on.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}

Addendum secundum: It looks like the JPanel UI delegate is not cooperating. One workaround is to extend JComponent so that you can control opacity. It's only slightly more work to manage the backgroundColor. NavBox and JScrollNavigator are also candidates for a similar treatment.

jsp.setViewportView(new JComponent() {

    {
        setBackground(Color.red);
        setBorder(BorderFactory.createLineBorder(Color.BLACK, 16));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(300, 300);
    }
});

I am also not sure what you mean by corruption, but I noticed that the resampled image is much nicer if you specify Image.SCALE_SMOOTH as the rescaling hint:

Image scaled = img.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);

Maybe this is what you are looking for...

I was able to reproduce your problem and get you the result your looking for. The problem is that the drawing of the image wasn't complete by the time you were repainting again, so only portions of the image were being painted. To fix this, add this field to your JScrollNavigator class (as a lock):

/** Lock to prevent trying to repaint too many times */
private boolean blockRepaint = false;

When we repaint the component, this lock will be activated. It won't be released until we have been able to successfully paint the panel - then another paint can be executed.

The paintComponent needs to be changed to abide by the lock and use a ImageObserver when painting your navigation panel.

@Override
protected void paintComponent(final Graphics g) {
    super.paintComponent(g);
    if(!blockRepaint){
        final Component view = (Component)jScrollPane.getViewport().getView();
        BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // Paint JScrollPane view to off-screen image and then scale.
        // It is this action that causes the display corruption!
        view.paint(g2d);
        ImageObserver io = new ImageObserver() {

            @Override
            public boolean imageUpdate(Image img, int infoflags, int x, int y,int width, int height) {
                boolean result = true;
                g.drawImage(img, 0, 0, null);
                if((infoflags & ImageObserver.FRAMEBITS) == ImageObserver.FRAMEBITS){
                    blockRepaint = false;
                    result = false;
                }

                return result;
            }
        };

        Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0);
        blockRepaint = g.drawImage(scaled, 0, 0, io);
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!