Creare un'immagine da un non-visibile AWT componente?
-
26-09-2019 - |
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?
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:
- Si può avviare il programma / componente in un ambiente senza testa?
- 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:
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.
I href="http://tips4java.wordpress.com/2008/10/13/screen-image/" rel="nofollow"> spettacoli di classe come questo può essere fatto per componenti swing. Non ho mai provato con componenti AWT, compro ho potuto indovinare il concetto sarebbe stato lo stesso.
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 );
}
}
}