Click here to Skip to main content
Click here to Skip to main content

Very fast screen capture using DirectX in C#

By , 27 Oct 2011
Rate this:
Please Sign up or sign in to vote.

Introduction

Recently I programmed my new project - homemade ambilight. Ambilight is a backlight behind television. The light is the average of some pixels in the screen. In order to get the colors for the ambilight, I needed fast screen capture. After some search, I heard about the front buffer. DirectX devices have this cool property. It contains the actual screen image. It is faster than GDI. DirectX puts an image in the surface object and it's faster for processing than GDI's bitmap.

Requirements and project preparing

Your project must be STAThread. First you need the DirectX SDK. It contains all the libraries that you need. When you have it downloaded and installed, add to your project the following references:

  • Microsoft.DirectX
  • Microsoft.DirectX.Direct3D
  • Microsoft.DirectX.Direct3DX

If you can't find these in the list of references, then look for these libraries in "C:\Windows\Microsoft.NET\DirectX for Managed Code" and "C:\Windows\Microsoft.NET".

In order to get Direct3D working, we have to add these lines into the app.config (if it doesn't exist, add a new file):

<startup useLegacyV2RuntimeActivationPolicy="true">
  <supportedRuntime version="v4.0" 
       sku=".NETFramework,Version=v4.0,Profile=Client"/>
</startup>

In the supportedRuntime tag, change the version to the .NET version that you use.

A class for capturing screen

Our new class DxScreenCapture will have functions for capturing screen. In order to get the front buffer, we need a DirectX device. It can be created from Form or another Control. So our class must inherit from Form. Next, declare the device (of course, add using statements for DirectX too).

public class DxScreenCapture : Form
{
    Device d;
}

Next, let's setup the device.

public DxScreenCapture()
{
    PresentParameters present_params = new PresentParameters();
    present_params.Windowed = true;
    present_params.SwapEffect = SwapEffect.Discard;
    d = new Device(0, DeviceType.Hardware, this, 
            CreateFlags.SoftwareVertexProcessing, present_params);
}

The device renders images with hardware and processes vertexes by software. It's irrelevant for our project. Now we can access the front buffer! This is the method for getting the print screen:

public Surface CaptureScreen()
{
    Surface s = d.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, 
                Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
    d.GetFrontBufferData(0, s);
    return s;
}

Surface is a DirectX type of image. We can convert this to Bitmap, but it takes up much time. Locking the pixels of the surface is fast, so for processing it is OK.

First, the method creates a new surface. Next, we get the front buffer from the device to the surface and then return the surface. The class for capturing is ready. Isn't this easy?

Examples of usage

This method is fast, but if you save images to the hard drive, it takes much time. It isn't the best way to record the video of the screen. If you want to get a screen capture, you can use normal print screen from Graphics.

Saving and viewing are slow, but capturing is fast. So if you have an application that needs some pixels or average, this is the best way: simple and fast.

This solution I found when I wrote my homemade ambilight driver. This project needed the average of colors of the screen's edges and it had to refresh a minimum of ten times per second. Maybe, GDI would have sufficed, but it charged the system.

The example "Colors average" is a part of my ambilight. It works very fast. First, I calculate the positions of pixels in the locked pixels stream:

Collection<long> tlPos = new Collection<long>();
Collection<long> tPos = new Collection<long>();
Collection<long> trPos = new Collection<long>();
Collection<long> lPos = new Collection<long>();
Collection<long> rPos = new Collection<long>();
Collection<long> blPos = new Collection<long>();
Collection<long> bPos = new Collection<long>();
Collection<long> brPos = new Collection<long>();
int o = 20;
int m = 8;
int sx = Screen.PrimaryScreen.Bounds.Width - m;
int sy = Screen.PrimaryScreen.Bounds.Height - m;
int bx = (sx - m) / 3 + m;
int by = (sy - m) / 3 + m;
int bx2 = (sx - m) * 2 / 3 + m;
int by2 = (sy - m) * 2 / 3 + m;

long x, y;
long pos;

y = m;
for (x = m; x < sx; x += o)
{
    pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp;
    if (x < bx)
        tlPos.Add(pos);
    else if (x > bx && x < bx2)
        tPos.Add(pos);
    else if (x > bx2)
        trPos.Add(pos);
}

y = sy;
for (x = m; x < sx; x += o)
{
    pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp;
    if (x < bx)
        blPos.Add(pos);
    else if (x > bx && x < bx2)
        bPos.Add(pos);
    else if (x > bx2)
        brPos.Add(pos);
}

x = m;
for (y = m + 1; y < sy - 1; y += o)
{
    pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp;
    if (y < by)
        tlPos.Add(pos);
    else if (y > by && y < by2)
        lPos.Add(pos);
    else if (y > by2)
        blPos.Add(pos);
}

x = sx;
for (y = m + 1; y < sy - 1; y += o)
{
    pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp;
    if (y < by)
        trPos.Add(pos);
    else if (y > by && y < by2)
        rPos.Add(pos);
    else if (y > by2)
        brPos.Add(pos);
}

I created a Calculate method and I raise it with each timer tick. I capture the screen and lock pixels. Locking pixels is converting the surface or bitmap to a stream with pure pixel values. To read the stream, you must know its width and in which format it is saved. In DirectX, the format is specified when the surface is created. In the CaptureScreen method, there is:

Surface s = d.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, 
            Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);

A8R8G8B8 is a 32-bit RGB format, where each pixel has one byte for alpha, one for red, one for green, and one byte for blue. In the stream, the first four bytes are the first pixel, next 4 bytes are the second pixel, and so on. So in the Calculate method, I wrote:

Surface s = sc.CaptureScreen();
GraphicsStream gs = s.LockRectangle(LockFlags.None);

Next, I wrote the avcs method that reads the pixels specified in the table containing their locations and returns their average.

Color avcs(GraphicsStream gs, Collection<long> positions)
{
    byte[] bu = new byte[4];
    int r = 0;
    int g = 0;
    int b = 0;
    int i = 0;

    foreach (long pos in positions)
    {
        gs.Position = pos;
        gs.Read(bu, 0, 4);
        r += bu[2];
        g += bu[1];
        b += bu[0];
        i++;
    }

    return Color.FromArgb(r / i, g / i, b / i);
}</long>

Finally, I set the colors to preview and dispose the objects:

topLeft.BackColor = avcs(gs, tlPos);
topRight.BackColor = avcs(gs, trPos);
bottomLeft.BackColor = avcs(gs, blPos);
bottomRight.BackColor = avcs(gs, brPos);

top.BackColor = avcs(gs, tPos);
bottom.BackColor = avcs(gs, bPos);
left.BackColor = avcs(gs, lPos);
right.BackColor = avcs(gs, rPos);

gs.Close();
gs.Dispose();
s.UnlockRectangle();
s.ReleaseGraphics();
s.Dispose();

If you run some video behind the example's window, you can see how fast this method is.

You can use the DirectX solution for all screen processing problems, if you don't view or save full image.

Conclusion

The Print Screen button captures the screen by GDI. This slow method is wrapped in System.Drawing. In order to get fast print screens for processing, not for saving or viewing, DirectX is a better solution than GDI. A DirectX device has a front buffer which contains the rendered screen.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

TeapotDev

Poland Poland

My homepage
Follow on   Twitter

Comments and Discussions

 
GeneralRe: compare two captured image Pinmemberjob@3deden.com10-Jan-12 21:07 
GeneralMy vote of 5 PinmvpMd. Marufuzzaman14-Nov-11 4:03 
GeneralMy vote of 5 PinmemberJH646-Nov-11 11:33 
SuggestionInteresting Article Pinmemberdburr31-Oct-11 13:40 
GeneralRe: Interesting Article PinmemberWhitW11-Nov-12 13:44 
GeneralRe: Interesting Article Pinmemberdburr11-Nov-12 14:06 
Questiongood job PinmemberSlacker00728-Oct-11 1:18 
SuggestionAverage? PinmemberJulien Villers27-Oct-11 23:15 
Nice explanation for the capture.
 
I have a question/suggestion for you though: are you sure using a plain average will give you a good result with AmbiLight? I don't know how this specific system works, but I know light perception as well as light combinations aren't uniform. Maybe this will give you a 'good enough' approximation though.
'As programmers go, I'm fairly social. Which still means I'm a borderline sociopath by normal standards.' Jeff Atwood
 
'I'm French! Why do you think I've got this outrrrrageous accent?' Monty Python and the Holy Grail

GeneralRe: Average? PinmemberMilfje11-Dec-12 2:24 
GeneralRe: Average? PinmemberJulien Villers11-Dec-12 6:03 
GeneralMy vote of 5 PinmemberGPUToaster™27-Oct-11 20:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 27 Oct 2011
Article Copyright 2011 by TeapotDev
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid