如何提高JPictureBox对大图像的绘制速度?

时间:2014-12-01 00:13:06

标签: java image swing awt paint

我有一个JPictureBox扩展自java.awt.Component,请参阅此处的代码http://pastebin.com/SAJc6Sht。但它只有在没有图像拉伸时才能正常工作。特别是对于大局而言,它会大大减慢程序。如何提高JPictureBox对大图像的绘制速度?

@Override
public void paint(Graphics g) {    
    super.paint(g);

    int x = 0;
    int y = 0;
    int w = 0;
    int h = 0;
    if (image != null) {
        switch (sizeMode) {
            case AUTO_SIZE:
            case NORMAL:
                w = image.getWidth();
                h = image.getHeight();
                break;
            case CENTER_IMAGE:
                w = image.getWidth();
                h = image.getHeight();
                x = (getWidth() - w) / 2;
                y = (getHeight() - h) / 2;
                break;
            case STRETCH_IMAGE:
                w = getWidth();
                h = getHeight();
                break;
            case ZOOM:
                w = (int) Math.round(image.getWidth() * zoomFactor);
                h = (int) Math.round(image.getHeight() * zoomFactor);
                break;
            case FIT_BOTH:
                if (image.getWidth() > image.getHeight()) {
                    w = getWidth();
                    h = (int) (w / getAR());

                    if (h > getHeight()) {
                        h = getHeight();
                        w = (int) (h * getAR());
                    }
                } else {
                    h = getHeight();
                    w = (int) (h * getAR());

                    if (w > getWidth()) {
                        w = getWidth();
                        h = (int) (w / getAR());
                    }
                }
                break;
            case FIT_WIDTH:
                w = getWidth();
                h = (int) (w / getAR());
                break;
            case FIT_HEIGHT:
                h = getHeight();
                w = (int) (h * getAR());
                break;
        }

        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.drawImage(image, x, y, w, h, this);
    } else if (errorIcon != null) {
        w = errorIcon.getIconWidth();
        h = errorIcon.getIconHeight();
        x = (getWidth() - w) / 2;
        y = (getHeight() - h) / 2;
        errorIcon.paintIcon(this, g, x, y);
    }
}

1 个答案:

答案 0 :(得分:2)

基本上,您希望将图像缩放加载到后台线程,缩放非常耗时,并且您不希望在事件调度线程的上下文中进行。

然后再提出一些问题。你不想在你真正需要之前缩放图像,而你真的只想要最新的结果。

您可以设置一个小的单个重复计时器,而不是尝试在每次更改时缩放图像,每次要进行更改时都会重置。这会将多个调整大小请求合并到尽可能少的请求。此示例使用javax.swing.Timer设置为125毫秒的短延迟。因此,在实际触发更新之前,它将在更改请求之间等待至少125毫秒。

接下来,它使用单个线程设置ExecutorService。这为我们提供了“尝试”取消任何预先存在的操作的方法,因为我们不希望产生结果并开始我们的最新请求。

接下来,实际的缩放操作采用两步缩放,首先,它尝试执行快速,低质量的缩放,可以快速放在屏幕上,然后执行更慢,更高质量的缩放,并在某个时间更新在将来...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel implements Scrollable {

        private BufferedImage master;
        private Image scaled;
        private double zoom = 1d;
        private ExecutorService service;
        private List<Future> scaleTasks;
        private final Timer zoomTimer;

        public TestPane() {
            scaleTasks = new ArrayList<>(5);
            service = Executors.newSingleThreadExecutor();
            try {
                master = ImageIO.read(new File("Some image some where"));
                scaled = master;
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            zoomTimer = new Timer(125, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("Update Zoom to " + getZoom());
                    updateToZoomFactor(getZoom());
                }
            });
            zoomTimer.setRepeats(false);

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "plus");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "minus");

            am.put("plus", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    double zoom = getZoom() + 0.1;
                    setZoom(zoom);
                }
            });
            am.put("minus", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    double zoom = getZoom() - 0.1;
                    setZoom(zoom);
                }
            });

        }

        @Override
        public Dimension getPreferredSize() {
            return scaled == null
                            ? new Dimension(master.getWidth(), master.getHeight())
                            : new Dimension(scaled.getWidth(this), scaled.getHeight(this));
        }

        public BufferedImage getMaster() {
            return master;
        }

        public void setZoom(double value) {
            if (value < 0.1) {
                value = 0.1;
            } else if (value > 2) {
                value = 2d;
            }
            if (value != zoom) {
                zoom = value;
                zoomTimer.restart();
            }
        }

        public double getZoom() {
            return zoom;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (scaled != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                int x = (getWidth() - scaled.getWidth(this)) / 2;
                int y = (getHeight() - scaled.getHeight(this)) / 2;
                g2d.drawImage(scaled, x, y, this);
                g2d.dispose();
            }
        }

        protected void setScaledResult(final Image image) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    scaled = image;
                    invalidate();
                    revalidate();
                    repaint();
                }
            });
        }

        protected void updateToZoomFactor(double zoom) {
            Future[] tasks = scaleTasks.toArray(new Future[scaleTasks.size()]);
            for (Future task : tasks) {
                if (!task.isCancelled()) {
                    task.cancel(true);
                } else {
                    scaleTasks.remove(task);
                }
            }
            service.submit(new RescaleTask(zoom));
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(400, 400);
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return false;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

        protected class RescaleTask implements Callable<Image> {

            private double zoom;

            protected RescaleTask(double zoom) {
                this.zoom = zoom;
            }

            @Override
            public Image call() throws Exception {
                if (zoom == 1) {
                    scaled = getMaster();
                } else {
                    int width = (int) (getMaster().getWidth() * zoom);
                    int height = (int) (getMaster().getHeight() * zoom);
                    Image scaled = getMaster().getScaledInstance((int) width, (int) height, Image.SCALE_FAST);
                    if (!Thread.currentThread().isInterrupted()) {
                        setScaledResult(scaled);

                        if (zoom < 1) {
                            scaled = getScaledDownInstance(getMaster(), (int) width, (int) height);
                        } else {
                            scaled = getScaledUpInstance(getMaster(), (int) width, (int) height);
                        }

                        if (!Thread.currentThread().isInterrupted()) {
                            setScaledResult(scaled);
                        } else {
                            System.out.println("Was interrupted during quality scale");
                        }

                    } else {
                        System.out.println("Was interrupted during fast scale");
                    }
                }
                return scaled;
            }

            protected BufferedImage getScaledDownInstance(BufferedImage img,
                            int targetWidth,
                            int targetHeight) {

                int type = (img.getTransparency() == Transparency.OPAQUE)
                                ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

                BufferedImage ret = (BufferedImage) img;

                if (targetHeight > 0 || targetWidth > 0) {

                    int w = img.getWidth();
                    int h = img.getHeight();

                    do {

                        System.out.println(w + "x" + h + " -> " + targetWidth + "x" + targetHeight);

                        if (w > targetWidth) {
                            w /= 2;
                            if (w < targetWidth) {
                                w = targetWidth;
                            }
                        }

                        if (h > targetHeight) {
                            h /= 2;
                            if (h < targetHeight) {
                                h = targetHeight;
                            }
                        }

                        BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
                        Graphics2D g2 = tmp.createGraphics();
                        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                        g2.drawImage(ret, 0, 0, w, h, null);
                        g2.dispose();

                        ret = tmp;

                    } while (w != targetWidth || h != targetHeight);

                } else {

                    ret = new BufferedImage(1, 1, type);

                }

                return ret;

            }

            protected BufferedImage getScaledUpInstance(BufferedImage img,
                            int targetWidth,
                            int targetHeight) {

                int type = BufferedImage.TYPE_INT_ARGB;

                BufferedImage ret = (BufferedImage) img;
                int w = img.getWidth();
                int h = img.getHeight();

                do {

                    if (w < targetWidth) {
                        w *= 2;
                        if (w > targetWidth) {
                            w = targetWidth;
                        }
                    }

                    if (h < targetHeight) {
                        h *= 2;
                        if (h > targetHeight) {
                            h = targetHeight;
                        }
                    }

//          createCompatibleImage(w, h, type)
                    BufferedImage tmp = new BufferedImage(w, h, type);
                    Graphics2D g2 = tmp.createGraphics();
                    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                    g2.drawImage(ret, 0, 0, w, h, null);
                    g2.dispose();

                    ret = tmp;
                    tmp = null;

                } while (w != targetWidth || h != targetHeight);

                return ret;

            }

        }

    }

}

nb:这有点过分杀人,但展示了一些关键的想法

其他可能有用的功能之一是将图像转换为GraphicsDevice的兼容颜色模型,例如......

            master = ImageIO.read(new File("Some image some where"));
            GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
            GraphicsConfiguration gc = gd.getDefaultConfiguration();
            BufferedImage compatible = gc.createCompatibleImage(master.getWidth(), master.getHeight(), Transparency.TRANSLUCENT);
            Graphics2D g2d = compatiable.createGraphics();
            g2d.drawImage(master, 0, 0, this);
            g2d.dispose();
            master = compatible;