The Mighty BitBlt

M

Martijn Mulder

/*
BitBlt.cs

C# code using P/Invoke


I have good reasons to use P/Invoke to get access to the Win32 API
function BitBlt(). But I have trouble understanding the workings of it.

Below is a small, compilable and runnable program that shows the
problem. In words, it may seem complicated, but once you see it, it's
obvious.

First, I create a Bitmap with a green background and a red circle in the
middle. This Bitmap I alter slightly and copy it to the screen, four
times. Twice with Graphics.Drawimage(), twice with BitBlt() (so fast!).

The first attempt in the left-top corner with DrawImage() poses no problem.

The second attempt next to it with BitBlt() shows an Empty Black Square.

Then I copy part of the screen to the in-memory Bitmap. When I copy the
in-memory Bitmap back to the screen with BitBlt(), only the part copied
from the screen shows. The rest is black. This you see in the
left-bottom corner.

Finally, on the right-bottom corner, I use DrawImage() to show the
now-altered Bitmap on screen. The Bitmap is there, but only part of it
is BitBltable.


The question is:

How can I arrange things so that I can directly BitBlt an in-memory
Bitmap to the screen?

*/


namespace MyNamespace
{


using System.Drawing;
using System.Windows.Forms;


class MyForm:Form
{


[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern long BitBlt
(
System.IntPtr a,
int b,
int c,
int d,
int e,
System.IntPtr f,
int g,
int h,
int i
);


MyForm()
{
Text="The Mighty BitBlt";
}


override protected void OnPaint(PaintEventArgs a)
{


//Create Bitmap with Red Circle on Green Background
Bitmap bitmap=new Bitmap(100,100,a.Graphics);
Graphics graphics=Graphics.FromImage(bitmap);
graphics.Clear(Color.Green);
graphics.FillEllipse(Brushes.Red,10,10,80,80);


//Left-Top: Draw the newly made Bitmap with DrawImage()
//Result: Green Square with Red Circle Inside
a.Graphics.DrawImage(bitmap,0,0);


//Get Handle to Screen-Graphics and to Bitmap-Graphics
System.IntPtr hdc_screen=a.Graphics.GetHdc();
System.IntPtr hdc_bitmap=graphics.GetHdc();


//Right-Top: Copy Bitmap to Screen with BitBlt()
//Result: Black Empty Square
BitBlt(hdc_screen,100,0,100,100,hdc_bitmap,0,0,0xCC0020);


//Copy Part of Screen to Bitmap with BitBlt()
BitBlt(hdc_bitmap,50,50,50,50,hdc_screen,0,0,0xCC0020);


//Left-Bottom: Copy Bitmap to Screen with BitBlt()
//Result: Three Quarters Black, Quarter Circle Showing
BitBlt(hdc_screen,0,100,100,100,hdc_bitmap,0,0,0xCC0020);


//Get Rid of Handles and Temporary Graphics Object
a.Graphics.ReleaseHdc(hdc_screen);
graphics.ReleaseHdc(hdc_bitmap);
graphics.Dispose();


//Right-Bottom: Copy Bitmap to Screen with DrawImage()
//Result: Red PacMan with Quarter Pie in Corner
a.Graphics.DrawImage(bitmap,100,100);

}


[System.STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}
}
 
P

Peter Duniho

Martijn said:
/*
BitBlt.cs

C# code using P/Invoke

I have good reasons to use P/Invoke to get access to the Win32 API
function BitBlt(). But I have trouble understanding the workings of it.

I don't think the problem is with your use of BitBlt via p/invoke. If
you move the BitBlt code to a managed C++ DLL, still passing it the HDC
you created from the Graphics instance you got from the Bitmap, the same
results occur.
[...]
The question is:

How can I arrange things so that I can directly BitBlt an in-memory
Bitmap to the screen?

I don't have a specific answer to that, unfortunately.

I can tell you that if instead of creating an HDC from the Graphics
gotten from the Bitmap, you get an HBITMAP from the Bitmap, and then
create a new DC (CreateCompatibleDC) and select that HBITMAP into the
DC, then the calls to BitBlt do what you expect.

One minor "gotcha" here is that the HBITMAP you get is not a reference
to the original Bitmap instance. So the line of code that modifies the
bitmap only operates on the HBITMAP; the original Bitmap remains unchanged.

It seems obvious to me that there's something about the DC you get from
the Graphics instance that is interfering with the BitBlt from working
correctly. But it's not the use of BitBlt that's the problem.

Oddly, the DC obviously is connected to the Bitmap instance, since the
Bitmap is modified in the BitBlt that copies the 50x50 section to the
lower-right corner of the Bitmap. There's something about the DC that's
wrong and which is preventing the BitBlt from copying the bits that are
clearly there to the screen as desired.

You may want to look into the specific drawing mode set in the DC or
other state in the DC (maybe pixel depth?). I might play around with it
later, but it's been awhile since I've done native Win32 drawing stuff
so the likelihood of me coming up with the answer isn't as high as of
you figuring it out yourself. :)

Maybe someone else reading this will have some insight.

Pete
 
M

Michael Rubinstein

Martjin, what are the good reasons to BitBlt from memory? I am not
challenging, just asking. I use a different approach for drawing - it is in
essence double buffering, only implement in my code, as opposed to selecting
'DoubleBuffered' in the form editor. I create two identical buffers:


BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;

BufferedGraphics memBuffer1;

BufferedGraphics memBuffer2;

memBuffer1 = currentContext.Allocate(this.CreateGraphics(),
this.ClientRectangle);

memBuffer2 = currentContext.Allocate(this.CreateGraphics(),
this.ClientRectangle);

memBuffer2 stores the original image. memBuffer1 is used for drawing. Within
the Paint procedure I first copy the contents of memBuffer1 to memBuffer2.

System.Drawing.Graphics g = memBuffer1.Graphics;

this.memBuffer2.Render(g);

Then draw bitmaps in memBuffer1.

g.DrawImage(myBitmap,x,y);

I also draw shapes, text. Since it is done in memory I can do whatever I
like. I rotoate some bitmaps. When I am done I simply copy the contents of
memBuffer1 to the form calling:

memBuffer1.Render();

I believe it is a well-known technique. In my Win32 programs I used BitBlt
quite a lot. In .NET I found other ways more efficient for my purposes. In
your case I see no reason for BitBlt() not working with BufferedGraphics.

Michael







Martijn Mulder said:
/*
BitBlt.cs

C# code using P/Invoke


I have good reasons to use P/Invoke to get access to the Win32 API
function BitBlt(). But I have trouble understanding the workings of it.

Below is a small, compilable and runnable program that shows the problem.
In words, it may seem complicated, but once you see it, it's obvious.

First, I create a Bitmap with a green background and a red circle in the
middle. This Bitmap I alter slightly and copy it to the screen, four
times. Twice with Graphics.Drawimage(), twice with BitBlt() (so fast!).

The first attempt in the left-top corner with DrawImage() poses no
problem.

The second attempt next to it with BitBlt() shows an Empty Black Square.

Then I copy part of the screen to the in-memory Bitmap. When I copy the
in-memory Bitmap back to the screen with BitBlt(), only the part copied
from the screen shows. The rest is black. This you see in the left-bottom
corner.

Finally, on the right-bottom corner, I use DrawImage() to show the
now-altered Bitmap on screen. The Bitmap is there, but only part of it is
BitBltable.


The question is:

How can I arrange things so that I can directly BitBlt an in-memory Bitmap
to the screen?

*/


namespace MyNamespace
{


using System.Drawing;
using System.Windows.Forms;


class MyForm:Form
{


[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern long BitBlt
(
System.IntPtr a,
int b,
int c,
int d,
int e,
System.IntPtr f,
int g,
int h,
int i
);


MyForm()
{
Text="The Mighty BitBlt";
}


override protected void OnPaint(PaintEventArgs a)
{


//Create Bitmap with Red Circle on Green Background
Bitmap bitmap=new Bitmap(100,100,a.Graphics);
Graphics graphics=Graphics.FromImage(bitmap);
graphics.Clear(Color.Green);
graphics.FillEllipse(Brushes.Red,10,10,80,80);


//Left-Top: Draw the newly made Bitmap with DrawImage()
//Result: Green Square with Red Circle Inside
a.Graphics.DrawImage(bitmap,0,0);


//Get Handle to Screen-Graphics and to Bitmap-Graphics
System.IntPtr hdc_screen=a.Graphics.GetHdc();
System.IntPtr hdc_bitmap=graphics.GetHdc();


//Right-Top: Copy Bitmap to Screen with BitBlt()
//Result: Black Empty Square
BitBlt(hdc_screen,100,0,100,100,hdc_bitmap,0,0,0xCC0020);


//Copy Part of Screen to Bitmap with BitBlt()
BitBlt(hdc_bitmap,50,50,50,50,hdc_screen,0,0,0xCC0020);


//Left-Bottom: Copy Bitmap to Screen with BitBlt()
//Result: Three Quarters Black, Quarter Circle Showing
BitBlt(hdc_screen,0,100,100,100,hdc_bitmap,0,0,0xCC0020);


//Get Rid of Handles and Temporary Graphics Object
a.Graphics.ReleaseHdc(hdc_screen);
graphics.ReleaseHdc(hdc_bitmap);
graphics.Dispose();


//Right-Bottom: Copy Bitmap to Screen with DrawImage()
//Result: Red PacMan with Quarter Pie in Corner
a.Graphics.DrawImage(bitmap,100,100);

}


[System.STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}
}
 
M

Martijn Mulder

Michael Rubinstein schreef:
Martjin, what are the good reasons to BitBlt from memory? I am not
challenging, just asking. I use a different approach for drawing - it is in
essence double buffering, only implement in my code, as opposed to selecting
'DoubleBuffered' in the form editor. I create two identical buffers:

<SNIP code that I will study shortly>

Michael,

The decision to use Win32 API code was based on a dramatic difference
when I compared Graphics.DrawImage() with BitBlt(). See the code below,
you can compile and run it as-is.

But now, after some sleep, and being notified of the existence of class
BufferedGraphicsContent (never heard of before. Really) it seems there
are ways to achieve what I want within the .NET framework. And that's
way to go, of course.

Here is some code to time test DrawImage() vs. BitBlt():



/*
BitBlt.cs
*/


namespace MyNamespace
{


using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;


class MyForm:Form
{


[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern long BitBlt
(
System.IntPtr a,
int b,
int c,
int d,
int e,
System.IntPtr f,
int g,
int h,
int i
);


const int A_BIG_NUMBER=50000;


Bitmap bitmap;
Stopwatch stopwatch;


float first;
float second;


public MyForm()
{
bitmap=new Bitmap(55,55);
Graphics a=Graphics.FromImage(bitmap);
a.Clear(BackColor);
a.FillEllipse(Brushes.Red,21,21,34,34);
a.Dispose();
}


void FirstAlternative(Graphics a)
{
for(int i=0;i<A_BIG_NUMBER;i++)
{
a.DrawImage(bitmap,0,0);
}
}


void SecondAlternative(Graphics a)
{
System.IntPtr intptr=a.GetHdc();
for(int i=0;i<A_BIG_NUMBER;i++)
{
BitBlt(intptr,55,0,55,55,intptr,0,0,13369376);
}
a.ReleaseHdc(intptr);
}


override protected void OnPaint(PaintEventArgs a)
{
a.Graphics.DrawString
(
"Wait...",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,90)
);

stopwatch=Stopwatch.StartNew();
FirstAlternative(a.Graphics);
stopwatch.Stop();
first=stopwatch.ElapsedMilliseconds/1000f;

stopwatch=Stopwatch.StartNew();
SecondAlternative(a.Graphics);
stopwatch.Stop();
second=stopwatch.ElapsedMilliseconds/1000f;

a.Graphics.DrawString
(
"Finished",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,120)
);

a.Graphics.DrawString
(
"First Alternative\nusing DrawImage() took "+first+" seconds",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,150)
);

a.Graphics.DrawString
(
"Second Alternative\nusing BitBlt() took "+second+" seconds",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,200)
);

}


[System.STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}
}
 
M

Michael Rubinstein

Hi Martjin. When drawing is done in a memory buffer speed is rarely an
issue. The benefits become 'visible' (not literally) when you have to
perform multiple drawings operations, like transparently drawing multiple
images on top of a bigger image plus adding some shapes and text. I have not
done comparative tests, like the one you show, however I have programs
written in Win32, later re-written in .Net, and handling the same graphics.
..Net programs using buffered graphics perform noticeable better on the same
machine. Programs I am referring to are assets (vehicles, people) tracking.
The canvas I am drawing at is a geographical map.

Michael

Martijn Mulder said:
Michael Rubinstein schreef:
Martjin, what are the good reasons to BitBlt from memory? I am not
challenging, just asking. I use a different approach for drawing - it is
in essence double buffering, only implement in my code, as opposed to
selecting 'DoubleBuffered' in the form editor. I create two identical
buffers:

<SNIP code that I will study shortly>

Michael,

The decision to use Win32 API code was based on a dramatic difference when
I compared Graphics.DrawImage() with BitBlt(). See the code below, you can
compile and run it as-is.

But now, after some sleep, and being notified of the existence of class
BufferedGraphicsContent (never heard of before. Really) it seems there are
ways to achieve what I want within the .NET framework. And that's way to
go, of course.

Here is some code to time test DrawImage() vs. BitBlt():



/*
BitBlt.cs
*/


namespace MyNamespace
{


using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;


class MyForm:Form
{


[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern long BitBlt
(
System.IntPtr a,
int b,
int c,
int d,
int e,
System.IntPtr f,
int g,
int h,
int i
);


const int A_BIG_NUMBER=50000;


Bitmap bitmap;
Stopwatch stopwatch;


float first;
float second;


public MyForm()
{
bitmap=new Bitmap(55,55);
Graphics a=Graphics.FromImage(bitmap);
a.Clear(BackColor);
a.FillEllipse(Brushes.Red,21,21,34,34);
a.Dispose();
}


void FirstAlternative(Graphics a)
{
for(int i=0;i<A_BIG_NUMBER;i++)
{
a.DrawImage(bitmap,0,0);
}
}


void SecondAlternative(Graphics a)
{
System.IntPtr intptr=a.GetHdc();
for(int i=0;i<A_BIG_NUMBER;i++)
{
BitBlt(intptr,55,0,55,55,intptr,0,0,13369376);
}
a.ReleaseHdc(intptr);
}


override protected void OnPaint(PaintEventArgs a)
{
a.Graphics.DrawString
(
"Wait...",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,90)
);

stopwatch=Stopwatch.StartNew();
FirstAlternative(a.Graphics);
stopwatch.Stop();
first=stopwatch.ElapsedMilliseconds/1000f;

stopwatch=Stopwatch.StartNew();
SecondAlternative(a.Graphics);
stopwatch.Stop();
second=stopwatch.ElapsedMilliseconds/1000f;

a.Graphics.DrawString
(
"Finished",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,120)
);

a.Graphics.DrawString
(
"First Alternative\nusing DrawImage() took "+first+" seconds",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,150)
);

a.Graphics.DrawString
(
"Second Alternative\nusing BitBlt() took "+second+" seconds",
new Font("Times",12),
new SolidBrush(Color.Black),
new Point(5,200)
);

}


[System.STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}
}
 
F

Fred Griggs

Michael,
One very good reason for using BitBlit lies in its use of the ROP codes (other than SRCCOPY), which it appears are not available in BufferedGraphics -- unless (hopefully) I'm wrong.

Can anyone confirm?
fred
 
C

Calciu Sorin

Even with BufferedGraphics, BitBlt is faster. I am saying this because with BufferedGraphics you still use g.DrawImage(myBitmap,x,y); wich is slower than BitBlt. I have done tests!

If you want to use BitBlt then you must use this approach:

[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDest,Int32 nXDest,Int32 nYDest,Int32 nWidth,Int32 nHeight,IntPtr hdcSrc,Int32 nXSrc,Int32 nYSrc,UInt32 dwRop);
private const uint SRCCOPY = 0x00CC0020;

IntPtr dcSrc = IntPtr.Zero;
Graphics gSrc = null;
IntPtr hBitmap = IntPtr.Zero;
private Bitmap backImage = null;

public Bitmap BackImage
{
get { return backImage; }
set
{
backImage = value;

if (dcSrc != IntPtr.Zero)
{
gSrc.ReleaseHdc(dcSrc);
gSrc.Dispose();
DeleteObject(hBitmap);
dcSrc = IntPtr.Zero;
}

if (backImage != null)
{
hBitmap = backImage.GetHbitmap();

Graphics clientDC = this.CreateGraphics();
IntPtr hdc = clientDC.GetHdc();
IntPtr memdc = CreateCompatibleDC(hdc);

SelectObject(memdc, hBitmap);
gSrc = Graphics.FromHdc(memdc);
dcSrc = gSrc.GetHdc();

clientDC.ReleaseHdc(hdc);
clientDC.Dispose();
}

this.Invalidate();
}
}

protected override void OnPaint(PaintEventArgs e)
{
Rectangle r = e.ClipRectangle;
if (backImage != null && showBackground)
{
if (r.X > backImage.Width || r.Y > backImage.Height)
return;
if (r.X + r.Width > backImage.Width)
r.Width = backImage.Width - r.X;
if (r.Y + r.Height > backImage.Height)
r.Height = backImage.Height - r.Y;

//e.Graphics.DrawImage(backImage, r, r, GraphicsUnit.Pixel); // the slower GDI+ way

IntPtr dcDst = e.Graphics.GetHdc();
BitBlt(dcDst, r.X, r.Y, r.Width, r.Height, dcSrc, r.X, r.Y, SRCCOPY);// the faster GDI BitBlt way
e.Graphics.ReleaseHdc(dcDst);
}
}

My problem is how to i release the hBitmap wich is as big as backImage but i'm sure i will find soon.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top