Martin86
Martin86

Reputation: 143

Save drawn texture with OpenGL in to a file

I draw a texture (video image) with openGL. Image is drawn on map and because of that in most cases the drawn image is trapezoid. The image looks good on the map only if I use "Perspective correct texturing". My question is how can I catch the drawn texture and store it in to the file. I want to save only drawn texture not screenshot or anything else which is not drawn here in this function (public override void OnRender()). I render also other things on the map so doing screenshot is not a solution. So how to draw in to some Framebuffer to use it on screen and to save it to file.

using openTK nuGet v1.1.1589.5942 (v4.0.6)

using GMap.NET.OpenGL;
using OpenTK;
using OpenTK.Graphics.OpenGL;        

public GMapControl() : base(new OpenTK.Graphics.GraphicsMode(32, 24, 8, 4))
{
    Paint += glControl_Paint;
}

void glControl_Paint(object sender, PaintEventArgs e)
{
    if (!loaded)
        return;

    if (makeControlContext)
    {
        //VideoForm
        controlContext = new GraphicsContext(GraphicsMode, WindowInfo);
        makeControlContext = false;
    }
    if (controlContext != null)
        controlContext.MakeCurrent(WindowInfo);
    else
        MakeCurrent();

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

    GL.MatrixMode(MatrixMode.Modelview);

    GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
    GL.Enable(EnableCap.Blend);

    GL.Enable(EnableCap.LineSmooth);
    GL.Hint(HintTarget.LineSmoothHint, HintMode.Nicest);

    DrawMap();

    textureLoader();

    if (OnPaint != null)
    {
        GL.DepthMask(false);
        GL.LoadIdentity();
        OnPaint(sender, e);
        if (useViewPortFix)
            setupViewport();
    }
    GL.Disable(EnableCap.Blend);
    GL.Flush();
    SwapBuffers();
}

void DrawMap()
{
    try
    {
      
    }
    finally
    {
        if (themeFont != null)
            OnPaintOverlays();
        GL.PopMatrix();
    }
}

protected virtual void OnPaintOverlays()
{
    GL.LoadIdentity();
    GL.Translate(Core.renderOffset.X, Core.renderOffset.Y, 0);
    try
    {
        foreach (GMapOverlay o in Overlays)
        {
            if (o.IsVisibile)
            {
                o.OnRender();
            }
        }
    }
    catch { }
}

public override void OnRender()
{
    GL.Color4(backgroundColor.Value);

    lock (bitmapSync)
    {
        if (bitmap != null)
            createTexture();
    }

    GL.Enable(EnableCap.Texture2D);
    GL.BindTexture(TextureTarget.Texture2D, texture);

    //Do the magick for "Perspective correct texturing"
    // center point
    GPoint localTargetPosition = MainForm.instance.gMapControl.FromLatLngToLocalWithOffset(targetPosition);
    // determines distances to center for all vertexes
    double dUL = Common.distance(new double[] { LocalPoints[0].X, LocalPoints[0].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dUR = Common.distance(new double[] { LocalPoints[1].X, LocalPoints[1].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dLR = Common.distance(new double[] { LocalPoints[2].X, LocalPoints[2].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dLL = Common.distance(new double[] { LocalPoints[3].X, LocalPoints[3].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });

    var texCoords = new[]
    {
            new Vector4(0, 0, 1, 1),
            new Vector4(1, 0, 1, 1),
            new Vector4(1, 1, 1, 1),
            new Vector4(0, 1, 1, 1)
        };

    texCoords[0] *= (float)((dUL + dLR) / dLR);
    texCoords[1] *= (float)((dUR + dLL) / dLL);
    texCoords[2] *= (float)((dLR + dUL) / dUL);
    texCoords[3] *= (float)((dLL + dUR) / dUR);

    GL.Begin(PrimitiveType.Quads);
    {
        GL.TexCoord4(texCoords[0]); GL.Vertex4(LocalPoints[0].X, LocalPoints[0].Y, 1, 1); //UL  LocalPoints[0] gimbalUL
        GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X, LocalPoints[1].Y, 1, 1); //UR  LocalPoints[1] gimbalUR
        GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X, LocalPoints[2].Y, 1, 1); //LR  LocalPoints[2] gimbalLR
        GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X, LocalPoints[3].Y, 1, 1); //LL  LocalPoints[3] gimbalLL
    }

    GL.End();
    GL.Disable(EnableCap.Texture2D);

    //TODO store drawn texture/image to file (only the drawn texture not screenshot or anything else which is not drawn here)
}

I tryed to do this with Framebuffer but no success. Drawing into framebuffer and then reading pixel from it, the output is empty image.

int FramebufferName = -1;
int depthrenderbuffer;
int fbo_width = 1280;
int fbo_height = 720;

public override void OnRender()
{
    if (!targetPosition.IsEmpty)
    {
        if (FramebufferName == -1)
        {
            //Create new Framebuffer only once
            GL.GenFramebuffers(1, out FramebufferName);
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, FramebufferName);

            //create texture from bitmap 1280x720
            lock (bitmapSync)
            {
                if (bitmap != null)
                {
                    fbo_width = bitmap.Width;
                    fbo_height = bitmap.Height;
                    int t = GL.GenTexture();
                    GL.BindTexture(TextureTarget.Texture2D, t);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
                    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bitmap.Width, bitmap.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);

                    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
                    System.Drawing.Imaging.BitmapData data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                    GL.BindTexture(TextureTarget.Texture2D, t);
                    GL.TexSubImage2D(TextureTarget.Texture2D, 0, rect.X, rect.Y, rect.Width, rect.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

                    bitmap.UnlockBits(data);
                    bitmap.Dispose();
                    bitmap = null;

                    if (renderedTexture > 0)
                        GL.DeleteTexture(renderedTexture);
                    renderedTexture = t;
                    GL.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, renderedTexture, 0); //original texture 1280x720
                }
            }

            /* Storage must be one of: */
            /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */
            //GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent16, fbo_width, fbo_height);
            //GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, renderedTexture);

            /* Depth renderbuffer. */
            GL.GenRenderbuffers(1, out depthrenderbuffer);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthrenderbuffer);
            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent24, fbo_width, fbo_height);
            GL.FramebufferRenderbuffer(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, depthrenderbuffer);

            //GL.ReadBuffer(ReadBufferMode.ColorAttachment0);

            //DrawBuffersEnum[] drawBuffersEnum = new DrawBuffersEnum[] { DrawBuffersEnum.ColorAttachment0 };
            //GL.DrawBuffers(1, drawBuffersEnum);

            if (GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete)
            {
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); //would draw to the default framebuffer again, basically finishing the drawing to the other framebuffer(the backbuffer which will be brought to front by SwapBuffers)
                GL.DeleteFramebuffers(1, ref FramebufferName);
                GL.DeleteFramebuffers(1, ref depthrenderbuffer);
                return;
            }
            checkGlError();
        }

        //drawInFramebuffer
        //GL.BindTexture(TextureTarget.Texture2D, 0);
        //GL.Enable(EnableCap.Texture2D);
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, FramebufferName);
        //GL.Viewport(0, 0, fbo_width, fbo_height);
        checkGlError();

        //clear all
        GL.ClearColor(1, 1, 1, 0);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        //GL.MatrixMode(MatrixMode.Projection);
        //GL.LoadIdentity();
        checkGlError();

        //bind texture to framebuffer
        GL.Enable(EnableCap.Texture2D);
        checkGlError();
        GL.ActiveTexture(TextureUnit.Texture0);
        checkGlError();
        GL.BindTexture(TextureTarget.Texture2D, renderedTexture);
        checkGlError();
        //GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, renderedTexture);
        //checkGlError();

        //draw in framebuffer
        //Do the magick for "Perspective correct texturing"
        // center point
        GPoint localTargetPosition = MainForm.instance.gMapControl.FromLatLngToLocalWithOffset(targetPosition);
        // determines distances to center for all vertexes
        double dUL = Common.distance(new double[] { LocalPoints[0].X, LocalPoints[0].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dUR = Common.distance(new double[] { LocalPoints[1].X, LocalPoints[1].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dLR = Common.distance(new double[] { LocalPoints[2].X, LocalPoints[2].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dLL = Common.distance(new double[] { LocalPoints[3].X, LocalPoints[3].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });

        var texCoords = new[]
        {
            new Vector4(0, 0, 1, 1),
            new Vector4(1, 0, 1, 1),
            new Vector4(1, 1, 1, 1),
            new Vector4(0, 1, 1, 1)
        };

        texCoords[0] *= (float)((dUL + dLR) / dLR);
        texCoords[1] *= (float)((dUR + dLL) / dLL);
        texCoords[2] *= (float)((dLR + dUL) / dUL);
        texCoords[3] *= (float)((dLL + dUR) / dUR);

        GL.Begin(PrimitiveType.Quads);
        {
            GL.TexCoord4(texCoords[0]); GL.Vertex4(LocalPoints[0].X, LocalPoints[0].Y, 1, 1); //UL  LocalPoints[0] gimbalUL
            GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X, LocalPoints[1].Y, 1, 1); //UR  LocalPoints[1] gimbalUR
            GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X, LocalPoints[2].Y, 1, 1); //LR  LocalPoints[2] gimbalLR
            GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X, LocalPoints[3].Y, 1, 1); //LL  LocalPoints[3] gimbalLL
        }

        GL.End();
        GL.Disable(EnableCap.Texture2D);
        checkGlError();

        //TODO, get size an location where image is in framebuffer
        fbo_width = 1280;
        fbo_height = 720;
        long minX = 0;
        long maxY = 0;

        #endregion
        using (Bitmap bitmap = new Bitmap(fbo_width, fbo_height))
        {
            BitmapData bits = bitmap.LockBits(new Rectangle(0, 0, fbo_width, fbo_height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.ReadPixels((int)minX, (int)maxY, fbo_width, fbo_height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0);
            bitmap.UnlockBits(bits);
            bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
            bitmap.Save(@"c:\Downloads\aaa\ReadPixels_" + now.ToString("HHmmss_fff") + ".png", ImageFormat.Png); //getting empty image, alpha = 0
        }
        
        checkGlError();
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); //would draw to the default framebuffer again, basically finishing the drawing to the other framebuffer(the backbuffer which will be brought to front by SwapBuffers)

        //TODO draw framebuffer on screen. HOW???
        /*
        GL.Enable(EnableCap.Texture2D);
        GL.BindTexture(TextureTarget.Texture2D, renderedTexture);
        GL.BlitFramebuffer(0, 0, fbo_width, fbo_height, 0, 0, fbo_width, fbo_height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest);
        */
    }
    else
    {
        base.OnRender();
    }
}

private void checkGlError()
{
    ErrorCode errorCode = GL.GetError();
    if (errorCode != ErrorCode.NoError)
    {
        Console.WriteLine("ERROR: " + errorCode);
    }
}

Upvotes: 0

Views: 1469

Answers (1)

Rabbid76
Rabbid76

Reputation: 210878

If you want to read a rectangular area form the framebuffer, then you can use GL.ReadPixels. For instance:

Bitmap bmp = new Bitmap(width, height);
System.Drawing.Imaging.BitmapData data =
    bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
                 System.Drawing.Imaging.PixelFormat.Format24bppRgb);
GL.ReadPixels(x, y, width, height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
bmp.UnlockBits(data);

The pixel data from a texture object can be read by GL.GetTexImage. This function is only provided in desktop OpenGL, but not in OpenGL ES:

GL.BindTexture(TextureTarget.Texture2D, texture);
GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgr, PixelType.UnsignedByte, target);

In OpenGL ES you need to attach the texture to a framebuffer. See opengl es 2.0 android c++ glGetTexImage alternative.

Upvotes: 0

Related Questions