Question

I draw a complex 2D scene to an OpenGL window. I would like the user to be able to take a screenshot of the scene and save it as a JPG. However, I would like them to be able to specify that the scene can be drawn bigger than their screen (which will allow them to see more details, etc).

When they specify numbers bigger than the viewport that fits their screen, everything that would not be visible to them simply does not get drawn to the bitmap. I feel I have a false understanding of the behavior of some of these OpenGL functions that I haven't been able to track down.

The below code works totally fine when renderSize is smaller than the default viewport I start the program with. If renderSize is bigger, then a JPG of the appropriate size gets created, but the right/top section that is past the screen's visible area is simply blank. If I click and drag my viewport across my second monitor, then more of the picture gets drawn, but still not all of it if renderSize is bigger than both monitors. How can I make this drawing independent of what the viewport shown to the screen is?

(I can verify that renderLocation and renderSize are set correctly when this function is called, with renderSize being some large number, like 7000 x 2000)

public Bitmap renderToBitmap(RenderMode mode)
    {
        // load a specific ortho projection that will contain the entire graph
        GL.MatrixMode(MatrixMode.Projection);
        GL.PushMatrix();
        GL.LoadIdentity();

        GL.Ortho(0, renderSize.Width, 0, renderSize.Height, -1, 1);
        GL.Viewport(0, 0, renderSize.Width, renderSize.Height);

        GL.MatrixMode(MatrixMode.Modelview);
        GL.PushMatrix();
        GL.LoadIdentity();


        // move the graph so it starts drawing at 0, 0 and fills the entire viewport
        GL.Translate(-renderLocation.X, -renderLocation.Y, 0);

        GL.ClearColor(Color.White);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        // render the graph
        this.render(mode);

        // set up bitmap we will save to
        Bitmap bitmap = new Bitmap(renderSize.Width, renderSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        BitmapData bData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);

        // read the data directly into the bitmap's buffer (bitmap is stored in BGRA)
        GL.ReadPixels(0, 0, renderSize.Width, renderSize.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bData.Scan0);

        bitmap.UnlockBits(bData);
        bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); // compensate for openGL/GDI y-coordinates being flipped

        // revert the stuff about openGL we changed
        GL.MatrixMode(MatrixMode.Projection);
        GL.PopMatrix();
        GL.MatrixMode(MatrixMode.Modelview);
        GL.PopMatrix();

        return bitmap;
    }

And here's how I initialize my GL window, in case that comes into play.

private void initGL()
    {
        GL.ClearColor(Color.AntiqueWhite);
        GL.Disable(EnableCap.Lighting);

        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        resetCamera();
    }

    private void resetCamera()
    {
        offsetX = offsetY = 0; // Bottom-left corner pixel has coordinate (0, 0)
        zoomFactor = 1;
        setupViewport();
        glWindow.Invalidate();
    }

    private void setupViewport()
    {
        int w = glWindow.Width;
        int h = glWindow.Height;
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadIdentity();
        GL.Ortho(0 + offsetX, w + offsetX, 0 + offsetY, h + offsetY, -1, 1);
        GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
    }
Was it helpful?

Solution

Sounds like you're running into a pixel ownership problem:

14.070 Why don't I get valid pixel data for an overlapped area when I call glReadPixels() where part of the window is overlapped by another window?

This is due to a portion of the OpenGL specification called the Pixel Ownership test. If a window is obscured by another window, it doesn't have to store pixel data for the obscured region. Therefore, a glReadPixels() call can return undefined data for the obscured region.

The Pixel Ownership test varies from one OpenGL implementation to the next. Some OpenGL implementations store obscured regions of a window, or the entire window, in an off-screen buffer. Such an implementation can return valid pixel data for an obscured window. However, many OpenGL implementations map pixels on the screen one-to-one to framebuffer storage locations and don't store (and can't return) pixel data for obscured regions of a window.

One strategy is to instruct the windowing system to bring the window forward to the top of the window stack, render, then perform the glReadPixels() call. However, such an approach still risks user intervention that might obscure the source window.

An approach that might work for some applications is to render into a nonvisible window, such as a Pixmap under X Windows. This type of drawing surface can't be obscured by the user, and its contents should always pass the pixel ownership test. Reading from such a drawing surface should always yield valid pixel data. Unfortunately, rendering to such drawing surfaces is often not accelerated by graphics hardware.

Look into FBOs or PBuffers for larger-than-window and/or offscreen rendering.

There's also libtr if you can't/won't use FBOs or PBuffers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top