我有一个从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); } }
基本上,您希望将图像的缩放卸载到后台线程,缩放很耗时,并且您不想在事件调度线程的上下文中进行。
然后,这又引发了一些问题。除非确实需要,否则您不希望缩放图像,而实际上只想要最新的结果。
您可以设置一个小的单一重复计时器,而不必每次缩放都按比例缩放图像,每次您要进行更改时都会重置该计时器。这会将多个调整大小的请求合并为尽可能少的请求。本示例使用一个javax.swing.Timer短125毫秒的延迟设置。因此,在两次更改请求之间至少要等待125毫秒,然后才能实际触发更新。
javax.swing.Timer
接下来,它使用ExecutorService具有单个线程的设置。这为我们提供了“尝试”取消任何先前存在的操作的方法,因为我们不希望得到结果并开始我们的最新请求。
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,例如…
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;