我有一个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);
}
}
答案 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;