2013-06-10 6 views
2

Je voudrais peindre un JPanel dans un BufferedImage en mode sans tête (aucune interface graphique sur l'écran).Peinture sans tête

final JPanel panel = createPanel(); 
panel.setSize(panel.getPreferredSize()); 
panel.validate(); 

// JFrame frame = new JFrame(); 
// frame.getContentPane().add(panel); 
// frame.pack(); 
// frame.setVisible(true); 

final BufferedImage image = new BufferedImage(
      panel.getBounds().width, 
      panel.getBounds().height, 
      BufferedImage.TYPE_INT_ARGB 
); 

final Graphics2D gc = image.createGraphics(); 
gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 

try { 
    panel.paint(gc); 
    ...save the image somewhere... 
} finally { 
    gc.dispose(); 
} 

Mais je reçois toujours l'image vide jusqu'à ce que je mets le panneau dans un composant lourd poids et le montrer à l'écran (voir le code commenté). Je ne veux pas le montrer, cette application fonctionne sur le serveur.

Voici SSCCE:

public class Example { 

    private static JPanel createPanel() { 
     final JPanel panel = new JPanel(new GridBagLayout());   
     final JLabel label = new JLabel("Yeah, it's working!", SwingConstants.CENTER); 
     label.setFont(new Font("Arial", Font.PLAIN, 12));   
     final GridBagConstraints constraints = new GridBagConstraints(); 
     constraints.fill = GridBagConstraints.BOTH; 
     constraints.weightx = 1; 
     constraints.weightx = 1; 
     panel.add(label, constraints);   
     return panel; 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       final JPanel panel = createPanel(); 
       panel.setSize(panel.getPreferredSize()); 
       panel.validate(); 

    //    JFrame frame = new JFrame(); 
    //    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    //    frame.getContentPane().add(panel); 
    //    frame.pack(); 
    //    frame.setVisible(true); 

       final BufferedImage image = new BufferedImage(
         panel.getBounds().width, 
         panel.getBounds().height, 
         BufferedImage.TYPE_INT_ARGB 
       );  
       final Graphics2D gc = image.createGraphics(); 
       gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
       gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);  
       try { 
        panel.paint(gc); 
        ImageIO.write(image, "png", new File("image.png")); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } finally { 
        gc.dispose(); 
       }     
      } 
     }); 
    }  
} 
+0

La manière traditionnelle de faire cela sous Linux est d'exécuter un «faux» fournisseur de fenêtre X qui n'affiche rien. Mais ça garde Java content. –

+1

Pour une meilleure aide plus rapidement, publiez un [SSCCE] (http://sscce.org) –

+0

Voir [Pourquoi l'en-tête JTable n'apparaît-il pas dans l'image?] (Http://stackoverflow.com/q/7369814/418556) pour des conseils sur la peinture des composants non réalisés. Si vous ne pouvez pas le faire fonctionner, suivez les conseils de @GuillaumePolet et postez un SSCCE. –

Répondre

3

composants ont une taille zéro jusqu'à ce que le composant a été réalisé pour que les méthodes de peinture ne fonctionnent pas.

Découvrez Screen Image. Il va gérer ce problème pour vous en invoquant doLayout() sur le panneau pour vous assurer que tous les composants ont une taille valide.

+0

Mon erreur, c'est un problème de taille zéro mais sur ses sous-composants. Votre réponse est absolument correcte: +1 –

+0

Oui, cela fonctionne. Je me demande pourquoi je dois appeler récursivement doLayout() moi-même. Cela devrait être fait en validant simplement le composant. À mon humble avis, un autre piège de Swing. – Behnil

0

Essayez de instancier le BufferedImage avec l'un de ses constructeur à la place. GraphicsEnvironment classe a probablement pour être utilisé avec un vrai GraphicsEnvironment (écran, ...)

new BufferedImage(panel.getBounds().width, panel.getBounds().height, BufferedImage.TYPE_INT_ARGB) 
+0

'GraphicsEnvironment.getLocalGraphicsEnvironment()' déclenche une exception en mode sans tête –

+0

@GuillaumePolet Oui, mais je suppose que @Behnil teste sur un ordinateur avec un 'LocalGraphicsEnvironment' mais cela peut générer une image incorrecte s'il n'y a pas d'interface graphique (c'est à dire un 'JFrame'). – gma

+0

Cela signifie que même s'il a un "vrai" GE, son code ne fonctionne pas. Finalement, cela ne fonctionnera pas du tout sur un environnement sans tête.Donc, le problème ne vient pas directement de là (même si je suis d'accord que ça ne marchera pas). –

2

Voici un extrait qui peint une simple étiquette dans un fichier image, puis le fichier image est ouvert (si sur un ordinateur de bureau).

import java.awt.Desktop; 
import java.awt.image.BufferedImage; 
import java.io.File; 
import java.io.IOException; 

import javax.imageio.ImageIO; 
import javax.swing.JLabel; 

public class Test { 

    public static void main(String[] args) throws IOException { 
     JLabel label = new JLabel("Hello world"); 
     label.setSize(label.getPreferredSize()); 
     BufferedImage image = new BufferedImage(label.getWidth(), label.getHeight(), BufferedImage.TYPE_INT_ARGB); 
     label.paint(image.getGraphics()); 
     File output = new File("C:\\test\\hello world.png"); 
     if (!output.getParentFile().exists()) { 
      output.getParentFile().mkdirs(); 
     } 
     ImageIO.write(image, "png", output); 
     Desktop.getDesktop().open(output); 
    } 

} 

EDIT (avec votre SSCCE):

Ne pas appeler validate() mais plutôt doLayout() sur votre panneau (si vous avez des panneaux imbriqués, assurez-vous de l'appeler récursive):

import java.awt.Desktop; 
import java.awt.Font; 
import java.awt.Graphics2D; 
import java.awt.GridBagConstraints; 
import java.awt.GridBagLayout; 
import java.awt.RenderingHints; 
import java.awt.image.BufferedImage; 
import java.io.File; 
import java.io.IOException; 

import javax.imageio.ImageIO; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.SwingConstants; 
import javax.swing.SwingUtilities; 

public class Example { 

    private static JLabel label; 

    private static JPanel createPanel() { 
     final JPanel panel = new JPanel(new GridBagLayout()); 

     label = new JLabel("Yeah, it's working!", SwingConstants.CENTER); 
     label.setFont(new Font("Arial", Font.PLAIN, 12)); 

     final GridBagConstraints constraints = new GridBagConstraints(); 
     constraints.fill = GridBagConstraints.BOTH; 
     constraints.weightx = 1; 
     constraints.weightx = 1; 
     panel.add(label, constraints); 

     return panel; 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       final JPanel panel = createPanel(); 
       panel.setSize(panel.getPreferredSize()); 
       panel.doLayout(); 
       System.err.println(label.getSize() + ""); 
       // JFrame frame = new JFrame(); 
       // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       // frame.getContentPane().add(panel); 
       // frame.pack(); 
       // frame.setVisible(true); 

       final BufferedImage image = new BufferedImage(panel.getBounds().width, panel.getBounds().height, 
         BufferedImage.TYPE_INT_ARGB); 

       final Graphics2D gc = image.createGraphics(); 
       gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
       gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 

       try { 
        panel.paint(gc); 
        File output = new File("image.png"); 
        ImageIO.write(image, "png", output); 
        Desktop.getDesktop().open(output); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } finally { 
        gc.dispose(); 
       } 

      } 
     }); 
    } 
} 
+0

Encore une fois, doLayout() ne fonctionne que si vous n'avez pas de panneaux imbriqués. ScreenImage fait doLayout() récursivement pour s'assurer que tous les composants de tous les panneaux ont une taille. – camickr

+0

@camickr Oui, en effet, cela doit être fait récursivement. J'ai suivi votre lien et lu le code de 'ScreenImage'. –

0

L'appel du composant racine que vous tentez d'imprimer/peindre, addNotify, résoudra également le problème. Le nœud du problème semble être que la validation appelle un court-circuit à moins que le conteneur n'ait un «pair». L'appel addNotify initialise l'homologue qui permet aux appels suivants à Component.validate de fonctionner normalement comme dans un scénario non sans tête.

En l'envoyant comme une solution alternative, car l'appel doLayout ne fonctionnera pas dans les cas où les composants sont plus profondément imbriqués en raison de doLayout ne présentant pas de sous-composants. (Bien que la classe ScreenImage mentionnée dans la réponse de camickr résout ce problème en invoquant récursivement doLayout.)