Read pixels from a rectangle in an OpenGL texture - c#

I know the glGetTexImage OpenGL function allows reading the pixels from an entire texture. Is there an OpenGL function that does the same thing but allows passing a rectangle to restrict the pixels read?

Here is how I ended up doing it in OpenTK (should translate easily to C++ or other OpenGL / OpenGL ES enabled languages):
public Bitmap GetBitmap()
{
int fboId;
GL.Ext.GenFramebuffers(1, out fboId);
CheckGLError();
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, fboId);
CheckGLError();
GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, TextureId, 0);
CheckGLError();
Bitmap b = new Bitmap(Width, Height);
var bits = b.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
GL.ReadPixels(Rect.Left, Rect.Top, Width, Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0);
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
GL.Ext.DeleteFramebuffers(1, ref fboId);
b.UnlockBits(bits);
return b;
}

Related

what's the simplest framebuffer example for opengl?

im trying to add post processing to my game but i can't understand the framebuffer tutorial on learnopengl and opengl-tutorial i just need a simple framebuffer example with color and depth textures and how to use it and render it
im not sure how to implement a framebuffer
public class FrameBuffer
{
public int FBO,DBO;
public int Width, Height;
public int tex,depth;
public FrameBuffer(int width, int height)
{
this.Width = width;
this.Height = height;
FBO = GL.GenFramebuffer();
Bind();
tex = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, tex);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, (IntPtr)null);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, tex, 0);
}
public void Bind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FBO);
}
public void Unbind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
}
Your code sets up a proper framebuffer with a color plane.
If the framebufer has to have a depth buffer, then you've to create a depth texture, with the format PixelFormat.DepthComponent and one of the internal formats PixelInternalFormat.DepthComponent16, PixelInternalFormat.DepthComponent24, PixelInternalFormat.DepthComponent32 or PixelInternalFormat.DepthComponent32f:
depth = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, depth);
GL.TexParameter(TextureTarget.Texture2D,
TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D,
TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent24,
Width, Height, 0, PixelFormat.DepthComponent, PixelType.Float, (IntPtr)null);
Attach it to the framebuffer, to FramebufferAttachment.DepthAttachment
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer,
FramebufferAttachment.DepthAttachment,
TextureTarget.Texture2D, depth, 0);
Note after you've attached the color buffer and the depth buffer you should validate the framebuffer completeness, which should return a status value of FramebufferErrorCode.FramebufferComplete.:
FramebufferErrorCode status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
Alternatively to a depth texture you can attach a (depth) renderbuffer:
depth = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depth);
GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer,
RenderbufferStorage.DepthComponent24, Width, Height);
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer,
FramebufferAttachment.DepthAttachment,
RenderbufferTarget.Renderbuffer, depth);

size of bitmap in drawImage

I've asked question on proper scaling without rim on right and bottom. link
I've checked and size of bitmap is 32 by 32 pixels.
When i try to draw it on pictureBox with drawImage(Image, int, int) it draws it little bit bigger than size of bitmap like 36 by 36 or 40 by 40. Not sure.
But when i add width and height to drawImage it draws it 32 by 32. Is this supposed to happend.
Edit:
internal void draw(Graphics g, int x, int y)
{
int s = Game.scale;
Bitmap resized = (s > 1) ? ResizeImage(image, width*s, height*s) : image;
g.DrawImage(resized, x * width * s, y * height * s);
}
private static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
Feature, not a bug. If you don't specify the target size then DrawImage() pays attention to the physical size of the image. The size it had when it was created, in inches. Something you can see back in the debugger, look at the bitmap's HorizontalResolution and VerticalResolution properties.
With the diagnostic that it is probably 120 dots-per-inch, programmers commonly run their video adapter at 125% today. So the drawn image becomes 1.25 * 32 = 40 pixels.
Keep in mind that this kind of rescaling tends to be important, such an image can easily turn into but a fleck of dust on an upscale 4K monitor. They are getting pretty affordable today. Whether you want this or not depends on how the rest of your UI rescales. Check out this post about dpiAwareness in a Winforms app.

Why doesn't this code always create an identical copy of a Bitmap?

public static class BitmapHelper {
public static Bitmap Clone(Bitmap bmp) {
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
Bitmap newbmp = new Bitmap(rect.Width, rect.Height);
using (Graphics gfx = Graphics.FromImage(newbmp)) {
gfx.InterpolationMode = InterpolationMode.High;
gfx.PixelOffsetMode = PixelOffsetMode.None;
gfx.DrawImage(bmp, rect, rect, GraphicsUnit.Pixel);
}
return newbmp;
}
}
Most of the time, this does create an identical copy, but if the original Bitmap passed to Clone was created by painting a section from a larger Bitmap where the section extended to the right-most edge of the larger Bitmap, then the copy I get back will have slightly altered pixels on its right-most edge.
What am I doing wrong?
The code that creates the original looks like this:
var SmallerImage = new Bitmap(_sqSize, _sqSize);
using (var gfx = Graphics.FromImage(SmallerImage)) {
gfx.InterpolationMode = InterpolationMode.High;
gfx.PixelOffsetMode = PixelOffsetMode.None;
gfx.DrawImage(LargerImage,
new Rectangle(0, 0, _sqSize, _sqSize),
new Rectangle(p.X, p.Y, _sqSize, _sqSize),
GraphicsUnit.Pixel);
}
The important thing to know there is that Point p and _sqSize are such that the source rectangle (the second rectangle in the DrawImage call) goes right up to the right-most edge of LargerImage.
This same code WILL create an identical copy when Point p and _sqSize are such that the source rectangle does not butt up to the right edge of LargerImage.
Can anyone tell me why this happens?
Or, can someone tell me how to RELIABLY create an EXACT pixel-for-pixel duplicate of a Bitmap? Seems like it ought to be easy, but I can't seem to do it.
new Bitmap(bmp) has the same problems as my Clone method. bmp.Clone() does seem to work, but it has problems under the covers (something about it not being a deep copy -- sharing data with the original) and using it breaks my code in many ways.
First, check that the image doesn't already have these artifacts before it is cloned.
Using Clone() is indeed only a shallow copy, but I believe that Clone(Rectangle, PixelFormat) is a deep clone:
public static Bitmap Clone(Bitmap bmp) {
var newbmp = bmp.Clone(new Rectangle(0, 0, bmp.Width, bmp.Height), bmp.PixelFormat);
return newbmp;
}
If that doesn't work, try copying the raw data:
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public static Bitmap Clone(Bitmap bmp) {
var oldData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
var newBmp = new Bitmap(bmp.Width, bmp.Height, oldData.PixelFormat);
var newData = newBmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
CopyMemory(newData.Scan0, oldData.Scan0, (uint)(Math.Abs(oldData.Stride) * oldData.Height));
newBmp.UnlockBits(newData);
bmp.UnlockBits(oldData);
return newBmp;
}

Bitmap Resize, only crops the image and no resizing

I have this code to resize a bitmap, but all it does is to crop it instead of resizing, what I am doing wrong?
public static System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
//a holder for the result
Bitmap result = new Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
and I call the function like this
bmPhoto = Imaging.ImageProcessing.ResizeImage(bmPhoto, scaledSize.Width, scaledSize.Height);
// Keeping Aspect Ratio
Image resizeImg(Image img, int width)
{
double targetHeight = Convert.ToDouble(width) / (img.Width / img.Height);
Bitmap bmp = new Bitmap(width, (int)targetHeight);
Graphics grp = Graphics.FromImage(bmp);
grp.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
return (Image)bmp;
}
// Without Keeping Aspect Ratio
Image resizeImg(Image img, int width, int height)
{
Bitmap bmp = new Bitmap(width, height);
Graphics grp = Graphics.FromImage(bmp);
grp.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
return (Image)bmp;
}
Try using a Rectangle object to specify the portion of the new image that you want to fill, like so:
graphics.DrawImage(image, new Rectangle(0, 0, result.Width, result.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, null);
As noted the Rectangle specifies that the image should be drawn between the top left and bottom right corner, and then you provide the coordinates of the original image that you want to scale into that area (0,0,image.Width,image.Height).

OpenTK Texture showing up full black

Allright so I've taken some code that works fine on other programs, I've copied it into a new program I'm working on and it doesn't seem to work, as far as I know I've got all the needed lines in there, but the areas where a texture is suppost to appear just display black squares.
I think I'm missing a line but I've got no idea what it is. I'm quite puzzled to say the least.
Here is my opengl setup:
GL.ClearColor(Color.Black);
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();
GL.Viewport(0, 0, glControl1.Width, glControl1.Height);
GL.Ortho(0, 800, 600, 0, -30, 30);
This is the method that loads the textures and returns the texture id, I store that in my variable. (the file.writeallbytes is just a test, I could open that no problem in photoshop)
private int loadTexture(String path)
{
GL.Enable(EnableCap.Texture2D);
byte[] ddsBytes = Media.GetFile("\\"+path.Replace("\\\\","\\")).Skip(20).ToArray();
File.WriteAllBytes("Derp.dds", ddsBytes);
int texId = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, texId);
Bitmap image = null;
DDSReader.DDSImage ddsImg = new DDSReader.DDSImage(ddsBytes);
image = ddsImg.BitmapImage;
ddsImg = null;
BitmapData bmpData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmpData.Width, bmpData.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0);
image.UnlockBits(bmpData);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)All.Modulate);
return texId;
}
And my drawing method looks like this:
public void Draw()
{
if (TexID == -1)
{
GL.Color4(Color[0], Color[1], Color[2], (byte)100);
}
else
{
GL.Color3((byte)255,(byte)255,(byte)255);
GL.Enable(EnableCap.Texture2D);
GL.BindTexture(TextureTarget.Texture2D, TexID);
}
GL.Begin(BeginMode.TriangleStrip);
GL.TexCoord2(0, 0);
GL.Vertex2(X, Y + Height);
GL.TexCoord2(0, 1);
GL.Vertex2(X, Y);
GL.TexCoord2(1, 0);
GL.Vertex2(X + Width, Y + Height);
GL.TexCoord2(1, 1);
GL.Vertex2(X +Width, Y);
GL.End();
GL.Disable(EnableCap.Texture2D);
}
EDIT: The texture does show up, but the texture is full black, even when I set another color value (which usually changes the hue of the texture)

Categories