Java - Window Image

10,393

Solution 1

Here's a working example.

The application being captured can't be minimized but it doesn't need to have focus or be on top (i.e. visible).

The code provided in the related C# thread, the MSDN article Capturing an Image and jmemoryeditorw provided the necessary pieces.

The code uses GetDC and GetClientRect to capture the client area of the window. They can be replaced by GetWindowDC and GetWindowRect if you want to capture the whole window including window decorations.

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;

import jna.extra.GDI32Extra;
import jna.extra.User32Extra;
import jna.extra.WinGDIExtra;

import com.sun.jna.Memory;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HBITMAP;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.platform.win32.WinGDI;
import com.sun.jna.platform.win32.WinGDI.BITMAPINFO;
import com.sun.jna.platform.win32.WinNT.HANDLE;

public class Paint extends JFrame {

    public BufferedImage capture(HWND hWnd) {

        HDC hdcWindow = User32.INSTANCE.GetDC(hWnd);
        HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow);

        RECT bounds = new RECT();
        User32Extra.INSTANCE.GetClientRect(hWnd, bounds);

        int width = bounds.right - bounds.left;
        int height = bounds.bottom - bounds.top;

        HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow, width, height);

        HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
        GDI32Extra.INSTANCE.BitBlt(hdcMemDC, 0, 0, width, height, hdcWindow, 0, 0, WinGDIExtra.SRCCOPY);

        GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);
        GDI32.INSTANCE.DeleteDC(hdcMemDC);

        BITMAPINFO bmi = new BITMAPINFO();
        bmi.bmiHeader.biWidth = width;
        bmi.bmiHeader.biHeight = -height;
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = WinGDI.BI_RGB;

        Memory buffer = new Memory(width * height * 4);
        GDI32.INSTANCE.GetDIBits(hdcWindow, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.setRGB(0, 0, width, height, buffer.getIntArray(0, width * height), 0, width);

        GDI32.INSTANCE.DeleteObject(hBitmap);
        User32.INSTANCE.ReleaseDC(hWnd, hdcWindow);

        return image;

    }

    public static void main(String[] args) {
        new Paint();
    }

    BufferedImage image;

    public Paint() {
        HWND hWnd = User32.INSTANCE.FindWindow(null, "Untitled - Notepad");
        this.image = capture(hWnd);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
        setExtendedState(MAXIMIZED_BOTH);
        setVisible(true);
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.drawImage(image, 20, 40, null);
    }



}

I had to define some extra functions that weren't included in platform.jar (which can be found on the JNA website).

package jna.extra;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.win32.W32APIOptions;

public interface GDI32Extra extends GDI32 {

    GDI32Extra INSTANCE = (GDI32Extra) Native.loadLibrary("gdi32", GDI32Extra.class, W32APIOptions.DEFAULT_OPTIONS);

    public boolean BitBlt(HDC hObject, int nXDest, int nYDest, int nWidth, int nHeight, HDC hObjectSource, int nXSrc, int nYSrc, DWORD dwRop);

}



package jna.extra;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.win32.W32APIOptions;

public interface User32Extra extends User32 {

    User32Extra INSTANCE = (User32Extra) Native.loadLibrary("user32", User32Extra.class, W32APIOptions.DEFAULT_OPTIONS);

    public HDC GetWindowDC(HWND hWnd);

    public boolean GetClientRect(HWND hWnd, RECT rect);

}



package jna.extra;

import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinGDI;

public interface WinGDIExtra extends WinGDI {

    public DWORD SRCCOPY = new DWORD(0x00CC0020);

}

Solution 2

Use java.awt.Robot.createScreenCapture().

Here's an example:

    try {
        Robot robot = new Robot();
        Rectangle size = new Rectangle(Toolkit.getDefaultToolkit()
                .getScreenSize());
        BufferedImage buf = robot.createScreenCapture(size);
        ImageIO.write(buf, "png", new File("d:/test.png"));
    } catch (AWTException ae) {
        throw new RuntimeException("something went wrong");
    }

Code originally stolen from here.

Solution 3

For your original question, here it goes.

Capturing an inactive window in Windows is pretty straightforward, using the robot class, ONLY and ONLY if the window is visible at the moment of capturing. If you want to avoid that requirement, you HAVE to use the DWM API.

Using the normal Windows API (pre Vista), you can use GetWindowRect(handle,RECT) where handle is a handler to the window you want to capture. This will get you a RECT object (I assume you are using JNA), here is the sequence of code you should write:

RECT dimensionsOfWindow = new RECT();
GetWindowRect( handlerToWindow, dimensionsOfWindow );//now in the dimensionsOfWindow you have the dimensions
Robot robot = new Robot();
BufferedImage img = robot.createScreenCapture( dimensionsOfWindow.toRectangle() );//now in the img object you have only the image of your desired window

However!! This will work as a charm ONLY if your window is currently visible. If it is minimized, you will get some exception in java (because it has negative x and y ). And if it is partially hidden, you will also screenshot the other windows that are on top of it.

You can't solve your problem on boxes that don't have dwm (Desktop Windows Manager) as it has an API that allows different windows to write to a temp buffer before they actually are painted to the screen.

On XP and non - running DWM machines, however, you are stuck with the code I gave you.

Additionally , you can take a look at the following question: link text

Edit:

Here is an interesting guide (in C#, though, but you can use JNA+Java applying the same principles) that will give you a better understanding of the DWM and how to use it to do EXACTLY what you want.

link text

EditEdit Just saw you have a link to the same guide in C# that I gave you. What seems to be the problem in just rewriting the code for Java/JNA?

EditEditEdit To answer your additional question (how to convert your BitBit to a BufferedImage ), here is a guy who did it in his Open Source project. It is a nice piece of work and give him some appreciation:

http://code.google.com/p/jmemoryeditorw/

You might notice that if you run the program, it will give you all the processes and also...their Icons. If you dig in the code, you will see how they are converted from BitBit to BufferedImages.

Cheers and I have to say, a very nice question.

Share:
10,393
rog8gm
Author by

rog8gm

Updated on June 04, 2022

Comments

  • rog8gm
    rog8gm almost 2 years

    Does anyone know how to capture a screen shot in Java (not it's own screen, but any other window on the desktop and they don't necessarily have to be the active window) in Windows? There are a number of threads here on this similar subject, but I have yet to find an answer.

    I've tried using JNA, but became stuck after a few attempts. For example...

    public class Main {
    
        public static void main(String[] args) {
            Main m = new Main();
    
            List<WindowInfo> list = m.getWindows();
    
            for (int i=0;i<list.size();i++) 
            {
                WindowInfo info = list.get(i);
                System.out.println(info.getTitle());
            }
    
            WindowInfo wi = list.get(0);
    
            W32API.HDC hdcSrc = User32.instance.GetWindowDC(wi.getHwnd());
    
            W32API.HDC hdcMemory = Gdi32.instance.CreateCompatibleDC(hdcSrc);
    
            //W32API.HBITMAP hBitmapMemory = Gdi32.instance.CreateCompatibleBitmap(hdcSrc, int width, int height);
            int width = wi.getRect().right - wi.getRect().left;
            int height = wi.getRect().bottom - wi.getRect().top;
    
            W32API.HBITMAP hBitmapMemory  = Gdi32.instance.CreateCompatibleBitmap(hdcSrc, width, height);
    
            W32API.HANDLE hOld  = Gdi32.instance.SelectObject(hdcMemory, hBitmapMemory);
    
    
            Gdi32.instance.BitBlt(hdcMemory, 0, 0, width, height, hdcSrc, width+2, height+2, 0x00CC0020);
    
    
            /* # now how do we convert to a BufferedImage???  */
    
            // clean up
            Gdi32.instance.SelectObject(hdcMemory, hOld);
            Gdi32.instance.DeleteDC(hdcMemory);
            Gdi32.instance.DeleteObject(hBitmapMemory);
            User32.instance.ReleaseDC(wi.getHwnd(), hdcSrc);
    
    
        }
    
    
        /**
         *
         * @return
         */
        private List<WindowInfo> getWindows() {
    
            final List<WindowInfo> list = new ArrayList<WindowInfo>();
    
    
            User32.instance.EnumWindows(new WndEnumProc() {
                public boolean callback(int hWnd, int lParam) {
                    if (User32.instance.IsWindowVisible(hWnd)) {
                        RECT r = new RECT();
                        User32.instance.GetWindowRect(hWnd, r);
                        byte[] buffer = new byte[1024];
                        User32.instance.GetWindowTextA(hWnd, buffer, buffer.length);
                        String title = Native.toString(buffer);
                        if (title!=null&&title.length()>0) {
                            list.add(new WindowInfo(hWnd, r, title));
                        }
                    }
                    return true;
                }
            }, 0);
    
            Collections.sort(list, new Comparator<WindowInfo>() {
                public int compare(WindowInfo o1, WindowInfo o2) {
                    int i1 = (o1.getTitle()!=null&&o1.getTitle().length()>0?o1.getTitle():" ").charAt(0);
                    int i2 = (o2.getTitle()!=null&&o2.getTitle().length()>0?o2.getTitle():" ").charAt(0);
                    return i1 - i2;
                }
            });
    
            return list;
        }
    }
    

    I've also tried the equivalent of "PrintWindow()" API...

    Graphics g = form.CreateGraphics();
    Bitmap bmp = new Bitmap(form.Size.Width, form.Size.Height, g);
    Graphics memoryGraphics = Graphics.FromImage(bmp);
    IntPtr dc = memoryGraphics.GetHdc();
    bool success = PrintWindow(form.Handle, dc, 0);
    memoryGraphics.ReleaseHdc(dc);
    // bmp now contains the screenshot
    

    Or do I have to use JNI, or any other tool?

  • user4090
    user4090 over 12 years
    hey do you think you can explain how the HDC class works? I tried making a smilar program before where i could hold a bunch of instances of HDC then convert them to a bufferd image, what im wondering is where does the HDC information gets stored
  • Shorty123
    Shorty123 about 10 years
    This makes it a little bit faster: BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); MemoryImageSource source = new MemoryImageSource(width, height, buffer.getIntArray(0, width * height), 0, width);image.getGraphics().drawImage(Toolkit.getDefaultToolk‌​it().createImage(sou‌​rce), 0, 0, null);
  • acheron55
    acheron55 over 9 years
    This would not work for windows in background whereas JNA solution would.
  • acheron55
    acheron55 over 9 years
    Is there a reason you are not using GDI32Extra.INSTANCE for all gdi32 methods?
  • Johan Boberg
    Johan Boberg over 9 years
    It was a while since I wrote this so I don't remember unfortunately. Try it out and let me know.
  • Tomáš Zato
    Tomáš Zato almost 8 years
    This only works on Windows 7 with Aero on, otherwise you get the image of whatever is covering your window.
  • Danny G
    Danny G almost 8 years
    This methodology doesn't work too well when there is a notification window that appears over the capture rectangle.