How to take screenshots fast in Java?
Asked Answered
M

5

9

I am implementing a simple eye tracker, which requires fast screenshoting of what is happening on the screen simultaneously with capturing the video from webcam.

The thing is that the way of doing it with Robot, mentioned here: https://stackoverflow.com/questions/2475303/java-library-for-capturing-active-window-screenshot is extremely slow.

By the way, retrieving the video from a webcam works much faster and returns the byte array, which is very fast to be processed.

Does anybody know a faster solution? C++ libraries, which can be linked to Java for doing this may help as well.

Thank you!

UPDATE: Decided to switch to OpenCV, now looking for the way to make screenshot with it :)

Murry answered 26/5, 2010 at 10:18 Comment(2)
The answers to the referenced question include two completely different ways of using Robot. Which did you profile?Mettle
Check out nircmd.It has got a built in func to do this nircmd.exe cmdwait 0 savescreenshot "f:\temp\shot.png" .Put this program in your project and run it using Runtime()Hallel
H
-1

u should definitely give a shot to OpenCV

Hundred answered 26/5, 2010 at 13:11 Comment(3)
Is there any way to link OpenCV to Java? I've heard about OpenCV, but right now I want to try Java for this task, and will switch to C++ only if it will be really needed.Murry
Here is the way to do it: ubaa.net/shared/processing/opencv However I decided to try C++, thank you for the inspiration!Murry
oh thanx for the java api, i was actually looking for it too!Hundred
I
12

Here's a Windows-specific version using JNA that I am using in one of my projects.

I have found it to be an order-of-magnitude faster than Robot, even with the native call overhead.

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.W32API;
import com.sun.jna.win32.W32APIOptions;

public class JNAScreenShot {

    public static BufferedImage getScreenshot(Rectangle bounds) {
        W32API.HDC windowDC = GDI.GetDC(USER.GetDesktopWindow());
        W32API.HBITMAP outputBitmap =
            GDI.CreateCompatibleBitmap(windowDC,
                                       bounds.width, bounds.height);
        try {
            W32API.HDC blitDC = GDI.CreateCompatibleDC(windowDC);
            try {
                W32API.HANDLE oldBitmap =
                    GDI.SelectObject(blitDC, outputBitmap);
                try {
                    GDI.BitBlt(blitDC,
                               0, 0, bounds.width, bounds.height,
                               windowDC,
                               bounds.x, bounds.y,
                               GDI32.SRCCOPY);
                } finally {
                    GDI.SelectObject(blitDC, oldBitmap);
                }
                GDI32.BITMAPINFO bi = new GDI32.BITMAPINFO(40);
                bi.bmiHeader.biSize = 40;
                boolean ok =
                    GDI.GetDIBits(blitDC, outputBitmap, 0, bounds.height,
                                  (byte[]) null, bi, GDI32.DIB_RGB_COLORS);
                if (ok) {
                    GDI32.BITMAPINFOHEADER bih = bi.bmiHeader;
                    bih.biHeight = - Math.abs(bih.biHeight);
                    bi.bmiHeader.biCompression = 0;
                    return bufferedImageFromBitmap(blitDC, outputBitmap, bi);
                } else {
                    return null;
                }
            } finally {
                GDI.DeleteObject(blitDC);
            }
        } finally {
            GDI.DeleteObject(outputBitmap);
        }
    }

    private static BufferedImage
    bufferedImageFromBitmap(GDI32.HDC        blitDC,
                            GDI32.HBITMAP    outputBitmap,
                            GDI32.BITMAPINFO bi) {
        GDI32.BITMAPINFOHEADER bih = bi.bmiHeader;
        int height = Math.abs(bih.biHeight);
        final ColorModel cm;
        final DataBuffer buffer;
        final WritableRaster raster;
        int strideBits =
            (bih.biWidth * bih.biBitCount);
        int strideBytesAligned =
            (((strideBits - 1) | 0x1F) + 1) >> 3;
        final int strideElementsAligned;
        switch (bih.biBitCount) {
        case 16:
            strideElementsAligned = strideBytesAligned / 2;
            cm = new DirectColorModel(16, 0x7C00, 0x3E0, 0x1F);
            buffer =
                new DataBufferUShort(strideElementsAligned * height);
            raster =
                Raster.createPackedRaster(buffer,
                                          bih.biWidth, height,
                                          strideElementsAligned,
                                          ((DirectColorModel) cm).getMasks(),
                                          null);
            break;
        case 32:
            strideElementsAligned = strideBytesAligned / 4;
            cm = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
            buffer =
                new DataBufferInt(strideElementsAligned * height);
            raster =
                Raster.createPackedRaster(buffer,
                                          bih.biWidth, height,
                                          strideElementsAligned,
                                          ((DirectColorModel) cm).getMasks(),
                                          null);
            break;
        default:
            throw new IllegalArgumentException("Unsupported bit count: " + bih.biBitCount);
        }
        final boolean ok;
        switch (buffer.getDataType()) {
            case DataBuffer.TYPE_INT:
                {
                    int[] pixels = ((DataBufferInt) buffer).getData();
                    ok = GDI.GetDIBits(blitDC, outputBitmap, 0, raster.getHeight(), pixels, bi, 0);
                }
                break;
            case DataBuffer.TYPE_USHORT:
                {
                    short[] pixels = ((DataBufferUShort) buffer).getData();
                    ok = GDI.GetDIBits(blitDC, outputBitmap, 0, raster.getHeight(), pixels, bi, 0);
                }
                break;
            default:
                throw new AssertionError("Unexpected buffer element type: " + buffer.getDataType());
        }
        if (ok) {
            return new BufferedImage(cm, raster, false, null);
        } else {
            return null;
        }
    }

    private static final User32 USER = User32.INSTANCE;

    private static final GDI32 GDI = GDI32.INSTANCE;

}

interface GDI32 extends com.sun.jna.platform.win32.GDI32 {
    GDI32 INSTANCE =
        (GDI32) Native.loadLibrary(GDI32.class);
    boolean BitBlt(HDC hdcDest,
                   int nXDest,
                   int nYDest,
                   int nWidth,
                   int nHeight,
                   HDC hdcSrc,
                   int nXSrc,
                   int nYSrc,
                   int dwRop);
    HDC GetDC(HWND hWnd);
    boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines,
                      byte[] pixels, BITMAPINFO bi, int usage);
    boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines,
                      short[] pixels, BITMAPINFO bi, int usage);
    boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines,
                      int[] pixels, BITMAPINFO bi, int usage);
    int SRCCOPY = 0xCC0020;
}

interface User32 extends com.sun.jna.platform.win32.User32 {
    User32 INSTANCE =
        (User32) Native.loadLibrary(User32.class,
                                    W32APIOptions.UNICODE_OPTIONS);
    HWND GetDesktopWindow();
}
Insectile answered 30/1, 2011 at 13:47 Comment(7)
What version of JNA does this use? I'm unable to get it to compile in any of the versions I've found. The latest 3.3.0 has split up com.sun.jna.platform.win32.W32API into pieces, and the older versions can't find com.sun.jna.platform.win32. I've tried 3.2.3, 3.2.5, 3.2.7 and 3.3.0.Townscape
hey is there a way to save the contents of a HDC? I understand how to save them as jpegs, but i was wondering if there was another really quick way to save the contents of a HDC so I can later resue them to create jpegsAngelaangele
couldn't get it to work either. Which version of JNA did you use @Insectile ?Protagoras
@Horen, sorry I can't remember and don't have access to that codebase at the moment, but I think it was the latest version when I installed it (around June 2009.)Insectile
for anyone looking at a more recent version of this code : #6998938Norven
@user390525 no, but I fall back to the Robot version in that caseInsectile
the Robot is too slow about 0.5 second;Ilise
M
6

The robot.createScreenCapture(captureSize); call takes about 20ms for me.

Mettle answered 26/5, 2010 at 10:30 Comment(5)
I think the speed would be dependent on the framerate/drivers of the webcam in question.Lashing
I haven't much experience working with data from webcams, but I thought they would supply much lower resolution, and pre-compressed streams.Mettle
You're right: I have the regular Logitek C300 webcam, so it returns 480*640 images. I use the Video for Linux lib to process video, it's written on C and runs perfectly fast. Ones I try to catch the screenshot with the same speed in real-time - it immediately slows down the process. I capture it like BufferedImage screenshot = robot.createScreenCapture( new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())); May it happen because of the BurrefedImage? Is there any way to the the bit array from Robot?Murry
What I should add is that the most important thing in eye tracker is to catch the screenshot almost at the same moment as each webcam shot. 20ms may be actually too long. That would be also great to capture screen in the normal size, not just 480*640.Murry
Looking at the source of createScreenCapture shows that a RobotPeer is asked for the byte array, which is then wrapped in an appropriate BufferedImage. It also includes sync and restoreLocalAcceleration calls. However RobotPeer is not able to be used directly.Mettle
F
0

May be you can make use of JMF.. Checkout the Screen Grabber code @ Oracle's site. I think it will help you solve your problem.

Flurry answered 26/5, 2010 at 10:24 Comment(1)
As from the source code, it seems to use the Robot as well: java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/… Or am I wrong?Murry
H
-1

u should definitely give a shot to OpenCV

Hundred answered 26/5, 2010 at 13:11 Comment(3)
Is there any way to link OpenCV to Java? I've heard about OpenCV, but right now I want to try Java for this task, and will switch to C++ only if it will be really needed.Murry
Here is the way to do it: ubaa.net/shared/processing/opencv However I decided to try C++, thank you for the inspiration!Murry
oh thanx for the java api, i was actually looking for it too!Hundred
J
-1

To capture full screen.

Robot robot = new Robot();
Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage screenFullImage = robot.createScreenCapture(screenRect);
ImageIO.write(screenFullImage, format, new File(destination));

To capture partial screen.

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rect = new Rectangle(0, 0, screenSize.width / 2, screenSize.height / 2);
Robot robot = new Robot();
BufferedImage screenFullImage = robot.createScreenCapture(rect);
ImageIO.write(screenFullImage, format, new File(destination));

Reference : link

Jijib answered 27/5, 2019 at 6:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.