Reputation: 143
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
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