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

Posted

技术标签:

【中文标题】如何提高 JPictureBox 的大图绘制速度?【英文标题】:How to increase drawing speed of JPictureBox for large image? 【发布时间】:2015-01-28 22:59:29 【问题描述】:

我有一个从 java.awt.Component 扩展而来的 JPictureBox,请参见此处的代码 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);
    

【问题讨论】:

g2d.drawImage(image, x, y, w, h, null); 更改为g2d.drawImage(image, x, y, w, h, this); 不会有任何影响。在事件调度线程中不调整图像大小怎么样? 没有任何变化。我只用 JSplitPane 调整它的大小。 为了尽快获得更好的帮助,请发布MCVE(最小完整可验证示例)或SSCCE(简短、自包含、正确示例)。我的意思是在这里发布,而不是发布链接。 “我正在更新我的问题”当有 MCVE 时告诉我。 【参考方案1】:

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

这又引发了一些问题。您不想缩放图像,直到您真的必须这样做并且您真的只想要最新的结果。

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

【讨论】:

我看到了你的代码(我不明白,一点也不:D)然后我尝试进行一些更改:在单独的线程中绘制它并在绘制成功后设置缓冲区。它使我的程序似乎运行得更快,但我在屏幕上看到的不是很好但可以接受。谢谢你。 pastebin.com/XtCpyv2v 这就是为什么我要进行两项规模化操作,一项快速(但很糟糕),一项缓慢而良好.. 不要在每次调用paint 时重新缩放图像,调用paint 的原因有很多,并非所有原因都因为组件已调整大小。当缩放因子改变时更新图像,但同样,setZoomFactor 可以快速连续调用,最好使用类似Timer 示例来减少尝试缩放图像的次数。不要使用单步秤,这真的非常糟糕。更多详情请见***.com/questions/14115950/…

以上是关于如何提高 JPictureBox 的大图绘制速度?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PyQtGraph 提高速度并使用多个绘图拆分数据?

PhotoView大图绘制——硬件加速限制

PhotoView大图绘制——硬件加速限制

Android大图绘制——硬件加速限制分析与方案

Android大图绘制——硬件加速限制分析与方案

在大图像中绘制边界框