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

发布于 2024-09-29 15:13:43 字数 359 浏览 6 评论 0原文

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

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

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

I'm trying to create an image (screen-shot) of a non-visible AWT component. I can't use the Robot classes' screen capture functionality because the component is not visible on the screen. Trying to use the following code:

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

Works sometimes, but does not work if the component contains things such as a text box or button, or some sort of OpenGL / 3D component (these things are left out of the image!). How can I take a proper screenshot of the whole thing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

指尖微凉心微凉 2024-10-06 15:13:43

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

这是生成的图像:

                 在此处输入图像描述

(disclamer: woops.. this doesn't seem to work for AWT )-:

I can't believe no one has suggested SwingUtilities.paintComponent or CellRendererPane.paintComponent which are made for this very purpose. From the documentation of the former:

Paints a component to the specified Graphics. This method is primarily useful to render Components that don't exist as part of the visible containment hierarchy, but are used for rendering.


Here is an example method that paints a non-visible component onto an image:

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

Here is a snippet of code that tests the above class:

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

And here is the resulting image:

                      enter image description here

空袭的梦i 2024-10-06 15:13:43

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

当 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。另一个答案是在虚拟屏幕上绘制它。但这不需要它。

Component has a method paintAll(Graphics) (as you already have found). That method will paint itself on the passed graphics. But we have to pre-configure the graphics before we call the paint method. That's what I found about the AWT Component rendering at java.sun.com:

When AWT invokes this method, the
Graphics object parameter is
pre-configured with the appropriate
state for drawing on this particular
component:

  • The Graphics object's color is set to the component's foreground property.
  • The Graphics object's font is set to the component's font property.
  • The Graphics object's translation is set such that the coordinate (0,0) represents the upper left corner of the component.
  • The Graphics object's clip rectangle is set to the area of the component that is in need of repainting.

So, this is our resulting method:

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

This is also the better way instead of using Robot for the visible components.


EDIT:

A long time ago I used the code I posted here above, and it worked, but now not. So I searched further. I have a tested, working way. It is dirty, but works. The Idea of it is making a JDialog, put it somewhere out of the Screen bounds, set it visible, and then draw it on the graphics.

Here is the code:

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

So, this will work also on Windows and Mac. The other answer was to draw it on a virtual screen. But this doesn't need it.

夏末 2024-10-06 15:13:43

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


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


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

对于每个没有图像渲染方法的组件,您需要创建自己的图像渲染器。例如,使用 jogl 您可以使用此 方法 (SO 帖子)。


2. 渲染到虚拟屏幕上

先决条件:

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

然后你可以使用 Xvfb 将整个程序渲染到虚拟屏幕上,然后从像这样的虚拟屏幕:

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

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

Excellent question, I've thought about this myself from time to time!


As you already have written, that rending heavy weight components such as 3D and AWT onto an image is a big problem. These components are (almost) directly transferred to the graphic card so they cannot be re-rendered to an image using the normal paintComponent stuff, you need help from the operative system or doing your own rendering of these components.


1. Making your own to image renderer

For each component that does not have a to image rendering method you need to create your own. For example using jogl you can take a off-screen screenshot using this method (SO post).


2. Rendering onto a virtual screen

Prerequisites:

  1. Can you start the program/component in a headless environment?
  2. Are you using Linux?

Then you can use Xvfb to render the whole program onto a virtual screen and then taking a screenshot from that virtual screen like this:

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

Maybe you need to tweek Xvfb a little bit by passing the size of the program you want to render to it (-screen 0 1024x768x24).

情话墙 2024-10-06 15:13:43

Screen Image 类展示了如何为 Swing 组件完成此操作。我从未尝试过使用 AWT 组件,购买后我猜想概念是相同的。

The Screen Image class shows how this can be done for Swing components. I've never tried it with AWT components, buy I could guess the concept would be the same.

记忆之渊 2024-10-06 15:13:43

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

/** * 捕获不可见的awt组件 * @作者dvargo */ 公共类屏幕截图 {

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

}

<代码>

How about something like this. The JFrame that holds all of the components is not visible.


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

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文