Подтвердить что ты не робот

Создайте изображение из невидимого компонента AWT?

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

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

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

4b9b3361

Ответ 1

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


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


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

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


2. Отображение на виртуальный экран

Предпосылки:

  • Можете ли вы запустить программу/компонент в безголовой среде?
  • Вы используете Linux?

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

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

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

Ответ 2

(disclamer: 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 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

Ответ 3

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. Другой ответ заключался в том, чтобы нарисовать его на виртуальном экране. Но это не нужно.

Ответ 4

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

Ответ 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 ); } }

}

код >