Java Tips Weblog

  • Blog Stats

    • 2,569,704 hits
  • Categories

  • Archives

Screen Image

Posted by Rob Camick on October 13, 2008

Do you need the ability to create an image of your GUI or a component on your GUI? The easiest way to do this would be to use the Robot class and its createScreenCapture(…) method. This will work whether you have an AWT or Swing application. However, this method is relatively slow as it gets the pixels directly from the screen. There must be a better way.

The better way is to use the Swing JComponent.print(…) method to draw the Swing component directly onto a BufferedImage. The basic code would be something like:

Dimension d = component.getSize();
BufferedImage image = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
component.print( g2d );
g2d.dispose();
ImageIO.write(image, ".jpg", new File(...));

Of course this can only be used on Swing components. This may lead to some confusion because although JFrame, JDialog and JWindow are part of Swing, they actually extend from Component, not JComponent, which means you can’t use the print(…) method and must use the Robot.

No reason to worry though because the ScreenImage class will take the above into account when creating an image. There are various static methods that can be used to create images:

  • createImage(JComponent) – create an image of the specified Swing component.
  • createImage(JComponent, Rectangle) – create an image of the specified Rectangle of a Swing component.
  • createImage(Component) – create an image of the specified component. JFrame and JDialog (for example) extend from Component, not JComponent.
  • createDestopImage() – is a convenience method which will create an image of the whole desktop.
  • createImage(Rectangle) – create an image of a specified Rectangle of the desktop.

In general all you need to provide is a reference to the component you want to create an image for. However, if you don’t want the entire component you can specify a region of that component. This is important to understand when creating images for components in a scroll pane. For example, lets say you have a text area with 1,000 lines in scroll pane. You really have 3 ways to create the image:

  • use the JTextArea as the component and all 1,000 lines will be painted, even though they are not visible on the screen
  • use the JTextArea as the component but specify a region so that only a certain number of lines are painted
  • use the JViewport as the component and only the visible lines will be painted.

This class also contains a static writeImage(…) method that allows you to save the created image to a file. The type of file must be supported by the ImageIO write method.

Download the code and check out the API, I’m sure you’ll be using it in no time. I used the following code to create the attached image showing a portion of my desktop:

Rectangle r = new Rectangle(375, 50, 600, 300);
BufferedImage bi = ScreenImage.createImage(r);
ScreenImage.writeImage(bi, "Screen-Image.jpg");

Swing components not added to a GUI

Although this class was originally designed to create an image of a component on the screen it can be used to create an image of Swing components not displayed on a GUI. Behind the scenes the ScreenImage class will:

  • set the component size – when the size of a component is 0, the size will be set to the preferred size of the component. Or, you can manually set the size of the component before invoking a createImage(…) method
  • layout the component – components like JPanel, JComboBox, JScrollPane need to be layed out before then can be painted. This class will recursively invoke the doLayout() method on the Containers of the component.

Even with the above features, there is still a possiblity the components image will not be what you expect. For example the image of a table added to a scroll pane will be missing the table header. This is because the header is not added to the scroll pane until the scroll pane has been added to a realized GUI window. That is the window must be packed or made visible. A simple work around for this problem is:

JScrollPane scrollPane = new JScrollPane( table );
scrollPane.setColumnHeaderView( table.getTableHeader() );
BufferedImage bi = ScreenImage.createImage( scrollPane );

If you still encounter problems then the only other suggestion I have is to add the component to a realized frame:

JFrame frame = new JFrame();
frame.setContentPane( someComponent );
frame.pack();
ScreenImage.createImage( someComponent );

Thanks to Maxideon for his comments in this posting which lead to improvements in the creation of images for non displayed components.

Get The Code

ScreenImage.java

17 Responses to “Screen Image”

  1. Anonymous said

    Hi, The code does not seem working for .gif and .bmp.

    I was trying to write a BufferedImage using .createImage method for gif and bmp. GIF says “Unknown writer file suffix gif”.

    BMP, I understand requires some special way of writing. I am not sure why gif would not work as createImage just in turn uses ImageIO write.

  2. camickr said

    Support exists for those two formats in JDK6. I don’t know why they are not supported in earlier versions.

    I have never done it (so don’t ask me how), but I believe you can download support for other writers and instal them into your JDK. You may want to start by reading the ImageWriter API and then search the web for detailed instructions as I don’t think its that simple.

  3. krishna said

    Hi,

    i am facing a problem while saving the panel as a image. i am using bufferedimage to do that .but the problem is it is throwing out of memory exception. my panel size is 1200 width and 64000 height. please help me out

  4. StanislavL said

    Missing part of image in special case with HTML and JEditorPane. See the code below.

    If you uncomment the pane adding and comment the label you will see the difference.

    
        public static void main(String[] args) {
            JTextPane pane=new JTextPane();
            pane.setEditorKit(new HTMLEditorKit());
            pane.setText("texttext");
            pane.setSize(180, 140);
    
            ImageIcon img=new ImageIcon(createImage(pane));
            JFrame fr=new JFrame();
            fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            fr.getContentPane().add(new JLabel(img));
    //        fr.getContentPane().add(pane);
            fr.setSize(200,150);
            fr.setLocationRelativeTo(null);
            fr.setVisible(true);
        }
    
    
    • Rob Camick said

      Sorry, I don’t see a problem. When I run the code as is I notice that the top half of the text is not painted. This is only because you manually set the size of the frame. If you resize the frame, then the text is there. When I use fr.pack() then I see the entire text.

      Maybe this is a version/platform issue? I use 1.6.0_07 on XP.

      If you want we can take the discussion offline and I’ll send you an image of what I see when I run your code.

      • The JEditorPane needs some time to build the page of the html-file. This time you must wait until you fetch the picture. The following code shows an example, in which a thread is created, that waits until the main thread is ready with building the page-appearance. This is notified by a PropertyChangeListener.
        What you need is the class ImageOfHTMLEditorPane, of which the code is printed below, the class ScreenImage the subject of this blog-entry and a html-page, that is named “myHTMLFile.html”. Run the code an you’ ll find a jpeg-image of the html-site in your project-directory.

        import java.awt.Toolkit;
        import java.awt.image.BufferedImage;
        import java.beans.PropertyChangeEvent;
        import java.beans.PropertyChangeListener;
        import java.io.File;
        import java.io.IOException;
        import java.net.MalformedURLException;
        import java.net.URL;
        import javax.swing.JEditorPane;
        import javax.swing.text.html.HTMLEditorKit;

        /*
        * A class to reliable create an image of the html-content of a JEditorPane
        *
        * @autor: Friedrich Forck
        * @version: 1.0.0 from November 12, 2011
        */
        public class ImageOfHTMLEditorPane {

        boolean isLoadingComplete = false;
        JEditorPane pane = new JEditorPane();
        BufferedImage bi = null;

        public void createImageOfEditorPane(){
        HTMLEditorKit ediKit = new HTMLEditorKit();
        pane.setEditorKit(ediKit);
        pane.addPropertyChangeListener(new MyLoadingCompleteListener());
        setPage(new File(“myHTMLFile.html”));
        pane.setPreferredSize(Toolkit.getDefaultToolkit().getScreenSize());
        pane.repaint();
        Thread photoShooter = new Thread(new PhotoShooter());
        photoShooter.start();
        }

        private void setPage(File file){
        URL helpURL = null;
        try {
        helpURL = file.toURI().toURL();
        } catch (MalformedURLException e1) {
        e1.printStackTrace();
        }
        try {
        pane.setPage(helpURL);
        } catch (IOException e) {
        System.out.println(e.getLocalizedMessage());
        }
        }

        class PhotoShooter implements Runnable{
        @Override
        public void run() {
        while(!isLoadingComplete){
        try {
        Thread.sleep(100);
        } catch (InterruptedException e1) {
        e1.printStackTrace();
        }
        }
        bi = ScreenImage.createImage(pane);
        try {
        ScreenImage.writeImage(bi, “imageOfHTMLSite.jpg”);
        } catch (IOException e) {
        e.printStackTrace();
        }
        }
        }

        class MyLoadingCompleteListener implements PropertyChangeListener{
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
        if(evt.getPropertyName().equals(“page”)){
        isLoadingComplete = true;
        }
        }
        }

        public static void main(String[] args) {
        ImageOfHTMLEditorPane imageOfHTMLEditorPane = new ImageOfHTMLEditorPane();
        imageOfHTMLEditorPane.createImageOfEditorPane();
        }

        }

  5. StanislavL said

    I run under 1.5 on XP. There is no TextField on image but there is in frame.

    I think that’s a problem of ComponentView class.
    As you can see the method just sets bounds of the component but not draws the component.

    public void paint(Graphics g, Shape a) {
    if (c != null) {
    Rectangle alloc = (a instanceof Rectangle) ?
    (Rectangle) a : a.getBounds();
    c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height);
    }
    }

    If you would like we can continue offline. Hope you can get my email from the post.

  6. Edward said

    Is this class part of some third-party API? I can’t seem to find any information on this class in the JavaDocs.

  7. Hi,
    thanks for the ScreenImage class. I just read through the code and learned a lot from it.

  8. A person essentially lend a hand to make critically posts I would state.

    That is the very first time I frequented your web page and
    up to now? I amazed with the analysis you made to create this
    particular publish extraordinary. Magnificent task!

  9. Anonymous said

    Sweet ginger brown! Nice code, thanks!

  10. Jason said

    Darn. Actually, I’ve just found out that this doesn’t seem to paint my JPanel fully. The JPanel has a JList, the JList has a custom renderer, and the items in that custom renderer don’t show up when using this method. If I toss them into a JFrame and call setVisible, then they show up, but I didn’t want to have to go that route. I tried changing the call from component.paint to component.paintAll and component.paintComponents, but then I just get a black-screen. It’s like the children get painted right, but not the grandchildren, if that makes sense. None of my components are double-buffered. Any ideas?

    • Rob Camick said

      Don’t know what you are doing. All Swing components are double buffered by default. There is no need to invoke paint() or paintAll(). All you do is create a JList add it to a JScrollPane and add the scrollpane to the panel. Try this first with a standard JList to see if the problem is your custom renderer. Although I still have no idea why using a custom renderer would cause a problem. This component is just a JPanel.

  11. Thanks for your code.
    I have used it in an application and it is great :)

Leave a comment