Domanda

Sto cercando di creare un'immagine (screen-shot) di un componente AWT non visibile. Non posso usare la funzionalità di cattura schermo delle classi Robot perché il componente non è visibile sullo schermo. Cercando di utilizzare il seguente codice:

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

Opere a volte, ma non funziona se il componente contiene cose come una casella di testo o un pulsante, o una sorta di componente OpenGL / 3D (queste cose sono lasciate fuori l'immagine!). Come posso prendere una corretta screenshot di tutta la faccenda?

È stato utile?

Soluzione

Ottima domanda, ci ho pensato io stesso di tanto in tanto!


Come già hai scritto, che strazianti componenti di peso pesanti come 3D e AWT su un'immagine è un grosso problema. Questi componenti sono (quasi) direttamente trasferito alla scheda grafica in modo che non può essere ri-resi a un'immagine utilizzando la roba paintComponent normale, hai bisogno di aiuto da parte del sistema operativo o di fare la propria prestazione di questi componenti .


1. Effettuare la propria immagine al renderer

Per ogni componente che non ha un metodo rendering delle immagini è necessario creare il proprio. Ad esempio, utilizzando JOGL si può prendere un off-screen screenshot utilizzando questo metodo (SO posta ).


2. Rendering su uno schermo virtuale

Prerequisiti:

  1. Si può avviare il programma / componente in un ambiente senza testa?
  2. Stai usando Linux?

Quindi è possibile utilizzare Xvfb di rendere l'intero programma su uno schermo virtuale e poi prendendo uno screenshot da quello schermo virtuale come questo:

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

Forse avete bisogno di tweek Xvfb un po 'passando la dimensione del programma che si desidera rendere ad esso (-screen 0 1024x768x24).

Altri suggerimenti

(Disclamer: woops .. questo non sembra lavorare per AWT) -:

Non posso credere che nessuno ha suggerito SwingUtilities.paintComponent o CellRendererPane.paintComponent che sono fatti per questo scopo. Dalla documentazione della ex:

  

Colore un componente alla Graphics specificato. Questo metodo è utile soprattutto per rendere i componenti che non esistono come parte della gerarchia di contenimento visibile, ma vengono utilizzati per il rendering.


Ecco un metodo esempio che le vernici un componente non visibile su un'immagine:

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

Ecco un frammento di codice che mette alla prova la classe di cui sopra:

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

E qui è l'immagine risultante:

entrare descrizione dell'immagine qui

Component ha un metodo paintAll(Graphics) (come avete già trovato). Tale metodo si dipingerà sulla grafica passati. Ma dobbiamo preconfigurare la grafica prima che noi chiamiamo il metodo paint. Questo è quello che ho trovato circa il rendering AWT componente a java.sun.com :

  

Quando AWT richiama questo metodo, il   parametro oggetto Graphics è   pre-configurato con l'appropriato   Stato per disegnare su questo particolare   Componente:

     
      
  • il colore dell'oggetto Graphics è impostata alla proprietà primo piano del componente.
  •   
  • carattere dell'oggetto Graphics è impostata alla proprietà carattere del componente.
  •   
  • definizione dell'oggetto Graphics è impostato in modo che le coordinate (0,0) rappresenta l'angolo superiore sinistro del componente.
  •   
  • Clip rettangolo dell'oggetto Graphics è impostato per l'area del componente che ha bisogno di riverniciatura.
  •   

Quindi, questo è il nostro metodo risultante:

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

Questo è anche il modo migliore invece di utilizzare Robot per i componenti visibili.


Modifica

Molto tempo fa ho usato il codice che ho postato qui sopra, e ha funzionato, ma ora no. Così ho cercato ulteriormente. Ho un modo di lavoro collaudato. E 'sporca, ma funziona. L'idea di esso sta facendo un JDialog, metterlo da qualche parte fuori dai limiti dello schermo, impostare visibile, e quindi disegnare sul grafico.

Ecco il codice:

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

Quindi, questo funzionerà anche su Windows e Mac. L'altra risposta è stata per disegnare su uno schermo virtuale. Ma questo non ne ha bisogno.

Che ne dite di qualcosa di simile. Il JFrame che contiene tutti i componenti non è visibile.


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

}

scroll top