Создать изображение из невидимого компонента AWT? - PullRequest
22 голосов
/ 27 октября 2010

Я пытаюсь создать изображение (снимок экрана) невидимого компонента AWT.Я не могу использовать функцию захвата экрана Robot классов, потому что компонент не виден на экране.Попытка использовать следующий код:

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

Иногда работает, но не работает, если компонент содержит такие вещи, как текстовое поле или кнопку, или какой-то компонент OpenGL / 3D (эти вещи опущеныизображения!).Как я могу сделать правильный скриншот всего этого?

Ответы [ 5 ]

11 голосов
/ 11 ноября 2010

(дисклеймер: woops .. похоже, это не работает для AWT) -:

Не могу поверить, что никто не предложил SwingUtilities.paintComponent или CellRendererPane.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

5 голосов
/ 12 ноября 2010

Отличный вопрос, я сам об этом думал время от времени!


Как вы уже писали, отрисовка на изображении тяжелых компонентов, таких как 3D и AWT, является большой проблемой. Эти компоненты (почти) напрямую переносятся на графическую карту , поэтому их нельзя перерисовать на изображение с помощью обычного paintComponent материала, вам нужна помощь операционной системы или выполнение ваших собственных рендеринга этих данных. компоненты.


1. Создание собственного рендерера изображений

Для каждого компонента, у которого нет метода рендеринга изображений, вам нужно создать свой собственный. Например, используя jogl , вы можете сделать снимок экрана за пределами экрана, используя этот метод ( SO post ).


2. Рендеринг на виртуальный экран

Необходимые условия:

  1. Можете ли вы запустить программу / компонент в автономном режиме?
  2. Вы используете Linux?

Затем вы можете использовать Xvfb , чтобы отобразить всю программу на виртуальном экране, а затем сделать снимок с этого виртуального экрана, например:

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

Может быть, вам нужно немного настроить Xvfb, передав размер программы, которую вы хотите отрендерить (-screen 0 1024x768x24).

5 голосов
/ 07 ноября 2010

Component имеет метод paintAll(Graphics) (как вы уже нашли). Этот метод будет рисовать себя на переданной графике. Но нам нужно предварительно настроить графику, прежде чем мы вызовем метод рисования. Вот что я нашел о рендеринге компонентов AWT на java.sun.com :

Когда 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 для видимых компонентов.


EDIT:

Давным-давно я использовал код, который выложил здесь выше, и он работал, но сейчас нет. Поэтому я искал дальше. У меня есть проверенный, рабочий способ. Это грязно, но работает. Идея в том, чтобы создать 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 голосов
/ 27 октября 2010

Класс Screen Image показывает, как это можно сделать для компонентов Swing. Я никогда не пробовал это с компонентами AWT, но могу предположить, что концепция будет такой же.

1 голос
/ 10 ноября 2010

Как насчет этого? 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;</p>

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

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

}

...