【问题标题】:Create an image from a non-visible AWT Component?从不可见的 AWT 组件创建图像?
【发布时间】:2011-05-01 01:03:08
【问题描述】:

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

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

有时有效,但如果组件包含文本框或按钮或某种 OpenGL / 3D 组件(这些东西不在图像中!)之类的东西,则不起作用。如何正确截取整个画面?

【问题讨论】:

  • +1 因为我只能给+1,其实我想给你+44+3 因为这是一个非常好的问题。 +1 为您在这个问题上提供赏金的勇气和+40 为您失去的声誉点。如果你问我,人们应该获得声望积分,因为他们有勇气花费他们的积分来创建一个悬赏问题。

标签: java graphics awt bufferedimage


【解决方案1】:

(免责声明: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);
        }
    }
}

这是一个测试上述类的sn-p代码:

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

这是生成的图像:

                      

【讨论】:

  • 这不是问题。 OP 想使用 AWT,而不是 Swing。
  • 确实,鉴于 OP 的 AWT 重点,这个答案是题外话,但我很高兴它仍然被发布,因为在我找到它之前,我没有意识到我得到一块纯色的原因是不可见且因此未布局的组件的大小为零,因此在渲染之前调用 setSize 至关重要(如果有子组件,则布局子组件)。关于这个主题的其他线程关于不相关的方面,但忽略了大小问题。
  • PS:如果要渲染的组件是透明的,还需要先在Graphics上直接绘制合适的背景。
  • 谢谢!这是我几个小时以来一直在寻找的东西!但在我的情况下,我需要更改一个细节:在调用paintComponent() 时我不能使用c.getBounds(),因为我会收到一个带有负坐标的矩形和一个具有与坐标相同的值(但符号相反)的正尺寸。对于new Rectangle(c.getSize()),它工作正常。
【解决方案2】:

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

当 AWT 调用此方法时, 图形对象参数是 预先配置了适当的 绘制这个特定的状态 组件:

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

【讨论】:

    【解决方案3】:

    很好的问题,我自己也时常思考这个问题!


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


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

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


    2。渲染到虚拟屏幕上

    先决条件:

    1. 能否在无头环境中启动程序/组件?
    2. 您使用的是 Linux 吗?

    然后您可以使用Xvfb 将整个程序渲染到虚拟屏幕上,然后像这样从该虚拟屏幕截取屏幕截图:

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

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

    【讨论】:

    • +1,这可能是(唯一?!)重量级组件的方式。
    【解决方案4】:

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

    【讨论】:

    • 代码没有使用paintAll()方法(不知道有没有区别)。但是,发布的关键是所有“do layout”代码,以确保组件具有适当的大小。
    【解决方案5】:

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

    }

    【讨论】:

    • -1,该代码取自两周前发布的 Screen Image 类。
    • 哦,对了,请原谅我构建了一个示例。我怎么敢。
    猜你喜欢
    • 1970-01-01
    • 2022-08-12
    • 2011-01-11
    • 2012-07-25
    • 2020-02-04
    • 1970-01-01
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多