Capture OpenGL screen C#

Hello!

I have a problem with capture my screen using OpenGL. I want to take screenshots when i’m ingame (Counter-Strike 1.6). This is my code and I’m using OpenTK.

public Bitmap GrabScreenshot()
        {
            Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            System.Drawing.Imaging.BitmapData data =
                bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
                             System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte,
                         data.Scan0);
            bmp.UnlockBits(data);
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            return bmp;
        }

Calling the function with this.

Bitmap bmp = new Bitmap(GrabScreenshot());
            bmp.Save("data/" + textBox1.Text + "/" + DateTime.Now.Minute + DateTime.Now.Second + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

The screenshots is all black.

can you check the memory output in data.Scan0 after the ReadPixels call? should have meaningful values instead bunch of zeros (black).
check the GetError() as well afterwards.

How do i check the memory output?
Well, I do get an error message now.
“Objektreferensen har inte angetts till en instans av ett objekt.”
It’s Swedish so I’ll translate.
“The object’s referens is not defined to an instance of an object.”
The title of my error is in English though.
“NullReferenceException”

The error wants me to put a “new” before my code. This is the code giving me the error:

GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);

im not familiar with C#. isnt it possible to see the inside the memory like whats the memory holding under a pointer? this would be the first step to see if ReadPixels returns black pixels or the Bitmap class is messing up. also you sure that data.Scan0 is pointing to pixels inside the Bitmap class (makes sense but who knows)?

I’m a beginner in C# so I don’t really know the answer of those questions. Although, the Bitmap class is not the problem here i beleive. But, let’s aim at my error, and it might get solved.

What should I do?

ok, guess this will be a looong thread. though i only could help in the OpenGL part here. if that.
check first if any errors are generated by OpenGL. after GL.ReadPixels call GL.GetError and see if its anything other then GL_NO_ERROR.

I can’t do GL.GetError() because it won’t gewt executed because of my error. :stuck_out_tongue:

you should solve your error first. i really can’t help on that. not into C#, sorry.
ask some C# forums maybe.

http://www.velocityreviews.com/forums/t101600-c-systemnullreferenceexception.html
http://www.daniweb.com/forums/thread127013.html

Use it like this:

Bitmap MyBitmap = GrabScreenshot();

Another problem could be that bmp.Unlockbits() is executed before GL.ReadPixels() is done writing to it. Simply add GL.Finish() after GL.ReadPixels() to ensure this is not the case.

Well, adding GL.Finish() after the GL.ReadPixels() didn’t make any difference. Thanks though for trying!!

bump :smiley:

I’ve solved 1 part. :slight_smile: I added a glControl, and now i don’t get any errors, not even with GL.GetError(). Although, i’ve got some other difficulties now. The screenshots is still all black and the screenshots is only as big as my application is. I want them to be as big as the screen is.

I’d really appreciate help! Really!

the code looks actually ok, the only thing i can think of is, do you grab the screenshot after drawing the scene?

I capture the screen before saving it.

        public Bitmap GrabScreenshot()
        {
            Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            System.Drawing.Imaging.BitmapData data = bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, 
                System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            GL.ReadPixels(0, 0, 800, 600, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
            GL.Finish();
            bmp.UnlockBits(data);
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            return bmp;
        }
        private void capScreen(object sender, EventArgs eArgs)
        {
            Bitmap bmp = GrabScreenshot();
            bmp.Save("data/" + textBox1.Text + "/" + DateTime.Now.Minute + DateTime.Now.Second + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
            getPList();
        }

And i create the surface in the beginning of my program.

no. do you capture the screen after you render the scene? i.e. you clear the backbuffer with black and capture it you will have a black bitmap. my thought was that you maybe accidentally capturing it in the wrong place.

ooh, nope, I don’t do it like that.

last thing i would try is setting the read buffer with GL.ReadBuffer(GL_BACK). it’s on GL_BACK by default but again, who knows what the problem is.

Where? Should i put it before GL.ReadPixels() or after?

before GL.ReadPixels(). it’s a state and once set is applied to the rest of those calls until set otherwise. it’s been a while i done glReadPixels() mainly because this is slow on some Intels, i switched to textures. but no doubt this is the only way to capture screenshots. though it is not that bigger deal to to use it i recommend you go over the documentation and check for some hints:
http://opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/readpixels.html

the order of operations to obtain a screenshot must be like this:

  1. draw scene
  2. take the screenshot
  3. swapbuffers

You’re using an event to take the screenshot and if this is triggered after swapbuffers the backbuffer contents is undefined.

A simple workaround would be


private bool TakeScreenShot = false;

private void capScreen(object sender, EventArgs eArgs)
        {
           TakeScreenShot = true;
        }

private void capScreenBeforeNextSwap()
        {
            Bitmap bmp = GrabScreenshot();
            bmp.Save("data/" + textBox1.Text + "/" + DateTime.Now.Minute + DateTime.Now.Second + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
            getPList();
        }

private void Render()
{
   // draw scene

   if (TakeScreenShot)
   { 
     capScreenBeforeNextSwap()
     TakeScreenShot = false;  
   }
   this.SwapBuffers();
}

you should probably also change the line

GL.ReadPixels(0, 0, 800, 600, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);

to

GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);