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

时间:2010-10-26 23:41:31

标签: java graphics awt bufferedimage

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

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

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

5 个答案:

答案 0 :(得分:11)

(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"));

这是结果图像:

enter image description here

答案 1 :(得分:5)

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

  

当AWT调用此方法时,   图形对象参数是   预先配置适当的   借鉴这个特殊的状态   成分:

     
      
  • Graphics对象的颜色设置为组件的前景属性。
  •   
  • 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。另一个答案是在虚拟屏幕上绘制它。但这并不需要它。

答案 2 :(得分:5)

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


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


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

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


2。渲染到虚拟屏幕

先决条件:

  1. 您可以在无头环境中启动程序/组件吗?
  2. 您使用的是Linux吗?
  3. 然后您可以使用Xvfb将整个程序渲染到虚拟屏幕上,然后从该虚拟屏幕截取屏幕截图如下:

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

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

这样的事情怎么样?包含所有组件的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;

/** * Captures an invisible awt component * @author dvargo */ public class ScreenCapture {

private static List<String> 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 ); } }
private static List<String> 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 ); } }

}