How to increase drawing speed of JPictureBox for large image?

五迷三道 提交于 2019-12-20 05:38:21

问题


I have a JPictureBox extends from a java.awt.Component see code here http://pastebin.com/SAJc6Sht. But it only works well when there is no image stretching. Especially for the big picture, it slows program dramatically. How to increase drawing speed of JPictureBox for large image?

@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:


Basically, you want to off load the scaling of the image to a background thread, scaling is time consuming and you don't want to do within the context of the Event Dispatching Thread.

This then raises a few more issues. You don't want to scale the image until you really have to to and you really only want the latest result.

Instead of trying to scale the image on EVERY change, you can setup a small, single repeat timer which you reset each time you want to make a change. This will consolidate the multiple resize requests down to as few requests as possible. This example uses a javax.swing.Timer set to a short 125 millisecond delay. So it will wait at least 125 milliseconds between requests for a change before actually triggering the update.

Next, it uses a ExecutorService set up with a single thread. This provides us with the means to "attempt" to cancel any pre-existing operations, as we don't want there result and start our latest request.

Next, the actual scaling operation employs a two step scale, first, it tries to do a fast, low quality scale which can be put on the screen quickly and then performs a slower, high quality scale which is updated at some time in the future...

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: This is little over kill, but demonstrates some key ideas

One of the other things that might help is to convert the image to a compatible color model for the GraphicsDevice, for example...

            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;


来源:https://stackoverflow.com/questions/27219914/how-to-increase-drawing-speed-of-jpicturebox-for-large-image

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!