如何提高 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 的大图绘制速度?的主要内容,如果未能解决你的问题,请参考以下文章