从不可见的AWT组件创建图像?

我正在尝试创建一个不可见的AWT组件的图像(屏幕截图)。 我无法使用Robot类的屏幕捕获function,因为该组件在屏幕上不可见。 尝试使用以下代码:

 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); component.paintAll(g); 

有时可以工作,但如果组件包含诸如文本框或按钮之类的东西,或者某种OpenGL / 3D组件(这些东西都不在图像中!),则无效。 如何对整个事物进行适当的截图?

很好的问题,我不时地想到这个!


正如您已经写过的那样,将重量较大的组件(如3D和AWT)渲染到图像上是一个很大的问题。 这些组件(几乎) 直接传输到图形卡,因此无法使用普通的paintComponent材料将其重新渲染为图像,您需要操作系统的帮助或自己渲染这些组件。


1.制作自己的图像渲染器

对于没有图像渲染方法的每个组件,您需要创建自己的组件。 例如,使用jogl,您可以使用此方法拍摄屏幕截图( SOpost )。


2.渲染到虚拟屏幕上

先决条件:

  1. 你能在无头环境中启动程序/组件吗?
  2. 你在用Linux吗?

然后你可以使用Xvfb将整个程序渲染到虚拟屏幕上,然后从这个虚拟屏幕截取屏幕截图如下:

 Xvfb :1 & DISPLAY=:1 java YourMainClass xwd -display :1 -root -out image.xwd 

也许您需要通过将要渲染的程序的大小传递给它来调整Xvfb( -screen 0 1024x768x24 )。

(disclamer: woops ..这似乎不适用于AWT) – :

我无法相信没有人建议为此目的而制作的SwingUtilities.paintComponentCellRendererPane.paintComponent 。 从前者的文件:

将组件绘制到指定的Graphics 此方法主要用于呈现不作为可见包含层次结构的一部分存在但用于呈现的组件。


下面是一个将不可见组件绘制到图像上的示例方法:

 import java.awt.*; import java.awt.image.BufferedImage; import javax.swing.*; public class ComponentPainter { public static BufferedImage paintComponent(Component c) { // Set it to it's preferred size. (optional) c.setSize(c.getPreferredSize()); layoutComponent(c); BufferedImage img = new BufferedImage(c.getWidth(), c.getHeight(), BufferedImage.TYPE_INT_RGB); CellRendererPane crp = new CellRendererPane(); crp.add(c); crp.paintComponent(img.createGraphics(), c, crp, c.getBounds()); return img; } // from the example of user489041 public static void layoutComponent(Component c) { synchronized (c.getTreeLock()) { c.doLayout(); if (c instanceof Container) for (Component child : ((Container) c).getComponents()) layoutComponent(child); } } } 

以下是测试上述类的代码片段:

 JPanel p = new JPanel(); p.add(new JButton("Button 1")); p.add(new JButton("Button 2")); p.add(new JCheckBox("A checkbox")); JPanel inner = new JPanel(); inner.setBorder(BorderFactory.createTitledBorder("A border")); inner.add(new JLabel("Some label")); p.add(inner); BufferedImage img = ComponentPainter.paintComponent(p); ImageIO.write(img, "png", new File("test.png")); 

以下是生成的图像:

在此处输入图像描述

Component有一个方法paintAll(Graphics) (正如你已经发现的那样)。 该方法将在传递的图形上绘制自己。 但是我们必须在调用paint方法之前预先配置图形。 这就是我在java.sun.com上发现的关于AWT组件渲染的内容:

当AWT调用此方法时,Graphics对象参数已预先配置为在此特定组件上绘制的适当状态:

  • Graphics对象的颜色设置为组件的foreground属性。
  • Graphics对象的字体设置为组件的font属性。
  • 设置Graphics对象的转换,使得坐标(0,0)表示组件的左上角。
  • Graphics对象的剪辑矩形设置为需要重新绘制的组件区域。

所以,这是我们得到的方法:

 public static BufferedImage componentToImage(Component component, Rectangle region) { BufferedImage img = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); Graphics g = img.getGraphics(); g.setColor(component.getForeground()); g.setFont(component.getFont()); component.paintAll(g); g.dispose(); if (region == null) { return img; } return img.getSubimage(region.x, region.y, region.width, region.height); } 

这也是更好的方法,而不是使用Robot作为可见组件。


编辑:

很久以前我使用过上面发布的代码,但它确实有效,但现在没有了。 所以我进一步搜索。 我有一个经过测试的工作方式。 它很脏,但有效。 它的想法是创建一个JDialog,把它放在屏幕边界之外,将其设置为可见,然后在图形上绘制它。

这是代码:

 public static BufferedImage componentToImageWithSwing(Component component, Rectangle region) { BufferedImage img = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics g = img.createGraphics(); // Real render if (component.getPreferredSize().height == 0 && component.getPreferredSize().width == 0) { component.setPreferredSize(component.getSize()); } JDialog f = new JDialog(); JPanel p = new JPanel(); p.add(component); f.add(p); f.pack(); f.setLocation(-f.getWidth() - 10, -f.getHeight() -10); f.setVisible(true); p.paintAll(g); f.dispose(); // --- g.dispose(); if (region == null) { return img; } return img.getSubimage(region.x, region.y, region.width, region.height); } 

因此,这也适用于Windows和Mac。 另一个答案是在虚拟屏幕上绘制它。 但这并不需要它。

Screen Image类显示了如何为Swing组件完成此操作。 我从来没有用AWT组件试过它,买我猜我的概念是一样的。

这样的事情怎么样? 包含所有组件的JFrame不可见。

import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextArea; 

import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextArea;

/ **
*捕获不可见的awt组件
* @author dvargo
* /
公共课ScreenCapture
{

private static List types = Arrays.asList( ImageIO.getWriterFileSuffixes() ); /** * Build GUI * @param args */ public static void main(String [] args) { JFrame invisibleFrame = new JFrame(); invisibleFrame.setSize(300, 300); JPanel colorPanel = new JPanel(); colorPanel.setBackground(Color.red); colorPanel.setSize(invisibleFrame.getSize()); JTextArea textBox = new JTextArea("Here is some text"); colorPanel.add(textBox); invisibleFrame.add(colorPanel); JButton theButton = new JButton("Click Me"); colorPanel.add(theButton); theButton.setVisible(true); textBox.setVisible(true); colorPanel.setVisible(true); //take screen shot try { BufferedImage screenShot = createImage((JComponent) colorPanel, new Rectangle(invisibleFrame.getBounds())); writeImage(screenShot, "filePath"); } catch (IOException ex) { Logger.getLogger(ScreenCapture.class.getName()).log(Level.SEVERE, null, ex); } } /** * Create a BufferedImage for Swing components. * All or part of the component can be captured to an image. * * @param component component to create image from * @param region The region of the component to be captured to an image * @return image the image for the given region */ public static BufferedImage createImage(Component component, Rectangle region) { // Make sure the component has a size and has been layed out. // (necessary check for components not added to a realized frame) if (!component.isDisplayable()) { Dimension d = component.getSize(); if (d.width == 0 || d.height == 0) { d = component.getPreferredSize(); component.setSize(d); } layoutComponent(component); } BufferedImage image = new BufferedImage(region.width, region.height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = image.createGraphics(); // Paint a background for non-opaque components, // otherwise the background will be black if (!component.isOpaque()) { g2d.setColor(component.getBackground()); g2d.fillRect(region.x, region.y, region.width, region.height); } g2d.translate(-region.x, -region.y); component.paint(g2d); g2d.dispose(); return image; } public static void layoutComponent(Component component) { synchronized (component.getTreeLock()) { component.doLayout(); if (component instanceof Container) { for (Component child : ((Container) component).getComponents()) { layoutComponent(child); } } } } /** * Write a BufferedImage to a File. * * @param image image to be written * @param fileName name of file to be created * @exception IOException if an error occurs during writing */ public static void writeImage(BufferedImage image, String fileName) throws IOException { if (fileName == null) return; int offset = fileName.lastIndexOf( "." ); if (offset == -1) { String message = "file suffix was not specified"; throw new IOException( message ); } String type = fileName.substring(offset + 1); if (types.contains(type)) { ImageIO.write(image, type, new File( fileName )); } else { String message = "unknown writer file suffix (" + type + ")"; throw new IOException( message ); } }

}