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

2022-09-02 01:36:54

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

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

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


答案 1

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

我不敢相信没有人建议使用SwingUtilities.paintComponentCellRendererPane.paintComponent,它们正是为此目的而制作的。从前者的文档:

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


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

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


答案 2

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

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

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


推荐