Groups | Blog | Home
all groups > dotnet drawing api > october 2006 >

dotnet drawing api : Question on Animation



pamela fluente
10/31/2006 6:18:01 AM

In a fast animation done by repeatedly displaying
a bitmap on a picture box ...

what is better:

1. to recreate, each time, the bitmap and graphics object
(disposing the previous ones)
or
2. to "clean up" the existing bitmap and redraw on it ?

And, in case what is the faster way to clean the bitmap up. (I am
wondering whether the
filling done by a clear is more time consuming than just recreating
the bitmap.)

Further, I guess that if the picture box gets resized I have no choice
but to recreate the bitmap. Right? or is there a better way ?

-P
pamela fluente
10/31/2006 10:06:28 AM

Joergen Bech ha scritto:

Hi Joergen ! :)

[quoted text, click to view]

The new bitmap has always a trasparent background (as far I can
"see"), which is
just what I need. Infact I always draw on a trasparent bitmap.
When I show the bitmap in the picture box the user sees images and
text
on "white" pages, because the background of the control where is the
picture box where I load
the bitmap is white. So I never need actually any "clear" in the sense
of "fill with a color".

If there was a fast clear which turned all to a transparent bitmap it
would be perfectly
fine for me. Is there ? I could not find it around.

At the present I recreate bitmap and graphics each time. What I was
wondering
if this is correct or it would be better to "erase" (turn to
transparent) the bitmap so that
I can write on it again.

[quoted text, click to view]

This seems a nice strategy for the case of resize.

Then I would split in 2 cases:
- resize needed (recreate the bitmap)
- resize not needed (cleanup the bitmap or recreate ? )

I am not sure I would be able to use the fact that only a small portion
of a page may need to be drawn because my minimum entity for
computation is a "page". I split all the objects of the document in
pieces that lay on pages. Then I am able to draw a sigle page anytime,
based on the overall computations. I am not able to consider less than
one page: that's the minimum entity I draw. I do not have data on
portion of pages. I only memorize objects for the whole page. Every
time an object is moved a repagination occurs to compute all the object
portion position. Mere scrolling of zooming does not cause any page
computation.

[quoted text, click to view]

I expect that the user will rarely cause resize. Because usually work
keeping the editor
always magnified. Those rare times I could just recreate all.

[quoted text, click to view]

My buffer is as large as the vievable portion of the screen where the
user does his editing.
could be 1280 x 1024 or smaller.

[quoted text, click to view]
pamela fluente
10/31/2006 1:29:21 PM

Joergen Bech ha scritto:

[quoted text, click to view]

Thanks! Very interesting, and instructive Joergen.

I did not mean you to make the test :) I meant just checking if I was
missing something well know among graphics experts. But anyway, thank
you very much for caring.

Your test is very interesting (to me) and has some unexpected points.

I am noticing the fill rectangle, which to me would not be a natural
choice, but I can see it's faster than clear, for big bitmaps. Although
for small size it turns out that clear() is again faster !

On my pc I get:
2780
1047 (fill) 1287 (clear)

and for size 128px (break even point):

64
61 (fill) 46 (clear)

for size 10 as you noticed it's more convenient to recreate the bitma
and to use clear (I would add)

20
39 (fill) 28 (clear)


[quoted text, click to view]

That's true. Infact the computation /measurement and redrawing of
grids and other complicate objects are terribly expensive. Anyway your
test is important to me, because I think that forst of all a 3 times
faster graphics generation is not bad, but above all I am concerned
about memory usage, corruption and fragmentation. Creating and
disposing continuosly a 1024x1280 bitmap should not to be a relief for
the ps memory. I think that just wiping it out would make the program
much more stable. I could recreate it just on resize.

[quoted text, click to view]

I just try to establsih once for all the best strategy, so that I do
not have to care about it anymore and concentrate on other things :)

Very helpful Joergen. Many thanks and if you have more remarks please
just let me know, I love them ...


-P

[quoted text, click to view]
pamela fluente
10/31/2006 3:28:27 PM

ThunderMusic ha scritto:

[quoted text, click to view]

Hello ThunderMusic ,

thanks for the contribution :)

well, I think it is certainly possible and actually easy to
implement your idea.
In my specific case, having observed that the break even point is
low (128px)
and since we are talking about an editor, where the user needs a
large screen to
work, it would not seem (at least at a first superficial
examination) useful to take
the time. Further, the really time-consuming operations are
actually other,
such as those related to string (measurement, splitting, computation
of object position etc.).

In any case if one really needs some really lightning speed, I guess
there are other tools
other than the gdi+. For my purposes, which are not writing complex
3D games, I
experienced that the gdi+ is sufficient. In my application I have
even implemented
(for charting) a 3D engine with an arcball controller and the
animation of even
complex poliedra with thousands of faces is smooth and acceptable.
And, of course,
if Microsoft "hardware-accelerate" it I would ne even more happy
:)) ... probably all of us ...

-P











[quoted text, click to view]
pamela fluente
10/31/2006 4:27:11 PM
Hi

I have a surprising (?) news.

I have just changed my animation scheme replacing the bitmap
regeneration with the neater bitmap cleaning, as we have been
discussing above.

To my surprise I observed that

Graphics.FillRectangle(Brushes.Transparent, 0, 0,
Me.Bitmap.Width, Me.Bitmap.Height)

does not clear anything! The new frames get overlayed. If instead I
use:

Graphics.Clear(Color.Transparent)

it works as expected. So it seems the 2 commands behave differently.
Actually FillRectangle with a transparent color does not seem to do
anything. No wonder Clear is slower ! :)

How come is this so ?

-P
ThunderMusic
10/31/2006 4:53:23 PM
I post the idea just so you can evaluate if you can use it in your case...
your test for FillRectangle vs Clear gave me an idea... Would it be
possible for you to profile what's best fot the user's computer and use the
methods accordingly? I mean, you say, FillRectangle is faster in some case
and clear is faster in some other cases. So at the start of your
application, why don't you run a small test to know which method should be
used and set a flag somewhere in the application that says "Use
FillRectangle" or "Use Clear".... this applies to FillRectangle and Clear,
but could be applied to other things too, like "Do I wipe or recreate?"

Just an idea...

I hope it helps

ThunderMusic

[quoted text, click to view]

pamela fluente
10/31/2006 5:11:07 PM
Hello Michael C, thanks!

I am not sure we are talking about the same thing. I do not feel could
do what I am doing by handling the on paint event. I am not designing a
custom control. I do not need to repaint continuosly "on paint". I
repaint when the user does something like moving an object, scrolling,
zooming, and so on. I am working on an editor (imagine something like
Word) with a lot of images, text, tables, etc. : I need "persistent"
graphics.

It is also possible I am missing completely your point. In such a case
I need more explanations to understand what you are really proposing.

The picture box is used to hold the second buffer. It's convenient and
easily refreshed by just :
Me.PictureBox1.Image = Me.PictureBox1.Image

this seems quite fast to me ...



My scheme is

A. Initially (editor start) create the bitmap and graphic
object assign the bitmap to
a picture box

B. during editing:
-clear the current graphics
- create a new image drawing on the graphics containing
the current view of the
page (the graphics is never filled with color: I
always draw on a transparent surface)
- "refresh" the view by the above command (
Me.PictureBox1.Image = Me.PictureBox1.Image). This has exactly the same
effect of double buffering, with the advantage that I do not have to
handle the additional buffer (note: I never user the refresh command of
picture box nor any invalidate: that's a suicidal)

C. On exit dispose all

This seems to work quite well and smoothly so far. Let me know if you
see improvements.

-P

Michael C ha scritto:

[quoted text, click to view]
Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/31/2006 6:31:15 PM
On 31 Oct 2006 06:18:01 -0800, "pamela fluente"
[quoted text, click to view]

Think about it: A new bitmap would automatically be filled with 0's
(black) so if you need a different background color, you would be
filling it anyway. So by recreating it you would have the additional
overhead of - well - recreating it. Won't save you anything.

[quoted text, click to view]

Yes, in this case it should be recreated. Of course, if we are talking
about a buffer (which I presume is the case), you could just recreate
it whenever the new size is larger (on either axis) than the existing
buffer, then use a subsection of the buffer if the size gets smaller.

There is, however, a price to pay: Getting a 10x10 pixel image from
a 1000x1000 pixel buffer can be more expensive than if the buffer
had been just the 10x10 pixels, due to the way the pixels are stored
in memory. One way to see this is to measure the time it takes to
loop through a bitmap byte array (LockBits) horizontally x vertically
vs. vertically x horizontally. If the inner loop goes vertically, it
takes significantly longer than when processing the bytes horizontally
first.

Another price to pay is higher memory consumption.

For buffers that do not vary widely in size over their lifetime, I
would use the "recreate when larger" approach.

For buffers that vary widely in size over their lifetime, you might
consider expanding by a percentage (not necessarily double)
when larger and contract by another percentage when smaller.
This scheme ensures that there is some wiggle room for small
size adjustments and the buffer is only recreated when large
size changes occur.

So the questions are:

- How much do the buffers vary in size?

- How much memory am I willing to assign to those buffers?

- What is the cost of recreating the buffers?

- Is it better to pay the price of recreating the buffers if it means
that the application runs at a consistent pace without "stuttering"
when big changes take place (consistent = steady, but slower pace).
In games, this would be the same as keeping the framerate at a
steady low rate at all times in order to accommodate a few scenes
with lots of activity).

Measure. Measure. Measure.

/Joergen Bech


Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/31/2006 8:11:23 PM

[quoted text, click to view]

The test below indicates that FillRectangle on an existing 1000x1000
bitmap is three times faster than recreating the bitmap and doing
nothing with it.

Change the loops to 50.000 and the bitmap size to 10x10, the
story is entirely different: FillRectangle takes twice as long as
recreating the rectangle.

So this quick, unscientific test indicates that small bitmaps favor
recreation and large bitmaps are better off being wiped with
FillRectangle.

But most importantly: Both are pretty fast and whatever else
you are doing with those bitmaps is likely to render this choice
irrelevant.

I suggest just getting everything to work. If you find that you need
to speed things up, measure each step and figure out where the time
goes. In the case of my own print engine, I found that most of the
time was spent in the MeasureString function, then the DrawString
method, and way down the list came the rectangles and other
drawing primitives.

Methinks you are optimizing prematurely if you are worried about
recreate vs. fill.

/Joergen Bech

---snip---

Option Explicit On
Option Strict On

Public Class Form1

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim swa As New Stopwatch

'=== Test 1 ===

swa.Start()

For i As Integer = 1 To 500
Dim bm As New Bitmap(1000, 1000,
Imaging.PixelFormat.Format32bppPArgb)
bm.Dispose()
Next

swa.Stop()
Debug.WriteLine(swa.ElapsedMilliseconds.ToString)


'=== Test 2 ===

Dim bm2 As New Bitmap(1000, 1000,
Imaging.PixelFormat.Format32bppPArgb)

swa.Reset()
swa.Start()

For i As Integer = 1 To 500
Dim g As Graphics = Graphics.FromImage(bm2)
g.FillRectangle(Brushes.Transparent, 0, 0, bm2.Width,
bm2.Height)
g.Dispose()
Next

swa.Stop()
Debug.WriteLine(swa.ElapsedMilliseconds.ToString)

bm2.Dispose()

End Sub

End Class


Michael C
11/1/2006 12:00:00 AM
[quoted text, click to view]

Hang on a sec. I think this conversation is going down the wrong path
because you asked the wrong question to start with and everyone has been
trying to answer that question. (It's always important to make sure you're
solving the right problem :-). I don't think you need a bitmap at all, just
turn on double buffering and draw straight to the screen. You could draw
straight to the form, to a panel or create your own usercontrol. Probably
the last thing you'd use is a picturebox. :-) Do all your drawing in OnPaint
of whatever you are painting to. If you need to trigger frames for an
animation then call this.refresh (this being the control being painted to).

Michael

Michael C
11/1/2006 12:00:00 AM
[quoted text, click to view]

Not really, it makes perfect sense. If you are drawing a partially
transparent color to a surface then you want that color to be mixed with the
surface color, not to replace it. The ratio in which it mixes is the
transparancy, if it is 255 then all of the color will be written, if it's
128 then 50%, if 0 then there will be no change.

[quoted text, click to view]

Makes sense.

[quoted text, click to view]

That doesn't make sense. Fill rectangle should be slower because it needs to
combine 2 colors, clear just needs to do a replace.

Michael

Joergen Bech <jbech<NOSPAM> NO[at]SPAM
11/1/2006 12:00:00 AM
On Wed, 1 Nov 2006 11:59:28 +1100, "Michael C" <nospam@nospam.com>
[quoted text, click to view]

Now I'm embarrassed, but that's ok: If I was afraid to make
mistakes, I would never post anything :) Thanks for pointing out
that oversight. Sometimes (all the time) the most important thing
is to get the discussion rolling so we all can learn from it.

Replacing FillRectangle with Clear, I get

50.000 x 10x10:
- Recreate = 2663
- Clear = 4087 (Fill = 5822)

500 x 1000x1000:
- Recreate = 7002
- Clear = 5109 (Fill = 2473)

At least the results are consistent with my first "findings",
relatively speaking.

[quoted text, click to view]

Under the covers, FillRectangle (eventually) calls

GdipFillRectangleI(ByVal graphics As HandleRef, ByVal brush As
HandleRef, ByVal x As Integer, ByVal y As Integer, ByVal width As
Integer, ByVal height As Integer)

and Clear calls

GdipGraphicsClear(ByVal graphics As HandleRef, ByVal argb As
Integer)

Now it makes perfect sense that the *overhead* of calling
FillRectangle the way I did it in the first test affects the results
a bit. Another oversight on my part.

If we do not pass the constant Brushes.Transparent (my worst mistake)
but use a variable instead, things look slightly better:

---snip---
'=== Test 2 ===

Dim bm2 As New Bitmap(10, 10,
Imaging.PixelFormat.Format32bppPArgb)

swa.Reset()
swa.Start()

Dim b As New SolidBrush(Color.Transparent)
Dim c As Color = Color.Transparent
Dim w As Integer = bm2.Width
Dim h As Integer = bm2.Height

For i As Integer = 1 To 50000
Dim g As Graphics = Graphics.FromImage(bm2)
g.FillRectangle(b, 0, 0, w, h)
'g.Clear(c)
g.Dispose()
Next

b.Dispose()

swa.Stop()
Debug.WriteLine(swa.ElapsedMilliseconds.ToString)

bm2.Dispose()

---snip---

The results are now

50.000 x 10x10:
- Recreate = 2664
- Clear = 4125 (Fill = 4195)

So Clear was not affected by substituting a variable. I presume this
is because Color is a value type and the compiler just substituted
it with the argb value in both cases, but in the case of the brush,
4195 vs 5822 before is quite a speed improvement.

As for Fill being faster than Clear on large bitmaps. Who knows?
GDI+ is supposedly not hardware-accelerated, but perhaps some
pieces here and there are? Anyone who can shed some light on
this?

/Joergen Bech


pamela fluente
11/1/2006 2:31:03 AM

Joergen Bech ha scritto:

[quoted text, click to view]

you should not.
Sometime the difeerence between being wrong or being right is very
subtle :))

Take a look here: :)

size 1500px

g.CompositingMode = Drawing2D.CompositingMode.SourceOver
g.FillRectangle(Brushes.Transparent, 0, 0, bm2.Width,
bm2.Height)
g.CompositingMode = Drawing2D.CompositingMode.SourceCopy

'g.Clear(Color.Transparent)

Millisecs:
6077 dispose
2291 clean (fill with Drawing2D.CompositingMode.SourceCopy)


'g.CompositingMode = Drawing2D.CompositingMode.SourceOver
'g.FillRectangle(Brushes.Transparent, 0, 0, bm2.Width,
bm2.Height)
'g.CompositingMode = Drawing2D.CompositingMode.SourceCopy

g.Clear(Color.Transparent)

Millisecs:
6112 dispose
2930 clean (clear)



the 2 above are "functionally" equivalent.

-P

[quoted text, click to view]
pamela fluente
11/1/2006 2:37:32 AM
oops I meant ...

g.CompositingMode = Drawing2D.CompositingMode.SourceCopy
g.FillRectangle(Brushes.Transparent, 0, 0, bm2.Width,
bm2.Height)
g.CompositingMode = Drawing2D.CompositingMode.SourceOver

:))


-P
[quoted text, click to view]
Joergen Bech <jbech<NOSPAM> NO[at]SPAM
11/1/2006 11:51:17 AM

ok. So the conclusion is that Fill with SourceCopy works as
required and is still faster than Clear, except for very small
bitmaps where the overhead takes up a greater portion of
the time, but in real terms this is irrelevant.

Case closed, I suppose. Always Fill (not clear or recreate).

/Joergen Bech



On 1 Nov 2006 02:37:32 -0800, "pamela fluente"
[quoted text, click to view]
Bob Powell [MVP]
11/1/2006 5:55:17 PM
Evidence suggests that re-using and clearing a bitmap is the best option.

This is the result of a test run I wrote to create and destroy a number of
bitmaps as opposed to creating one bitmap and clearing it.

Creating 100 bitmaps...
Total time taken=0 minutes, 0 seconds, 812 milliseconds
Creating a bitmap once and wiping it 100 times...
Total time taken=0 minutes, 0 seconds, 390 milliseconds

In all cases the Graphics object was disposed of between draw cycles to
ensure maximum authenticity with the real case.

Interestingly, keeping the Graphics object around between draw cycles only
removed 20 milliseconds from the total so the creation and disposal of a
Graphics object attached to a bitmap is reasonably negligable.

The code is after my signature for your perusal...

--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.
-----------------------------------------------------------------

const int loops=100;

StringBuilder sb=new StringBuilder();

sb.Append(string.Format("Creating {0} bitmaps...\r\n",loops));

DateTime dt=DateTime.Now;

for (int i = 0; i < loops; i++)

{

Bitmap bm = new Bitmap(1024, 768, PixelFormat.Format32bppPArgb);

Graphics g = Graphics.FromImage(bm);

g.FillRectangle(Brushes.Black, 10, 10, 1004, 748);

g.Dispose();

bm.Dispose();

}

TimeSpan ts = DateTime.Now - dt;

sb.Append(string.Format("Total time taken={0} minutes, {1} seconds, {2}
milliseconds\r\n", ts.Minutes, ts.Seconds - 60 * ts.Minutes,
ts.Milliseconds - ((60000 * ts.Minutes) + (1000 * ts.Seconds))));

sb.Append(string.Format("Creating a bitmap once and wiping it {0}
times...\r\n", loops));

dt = DateTime.Now;

Bitmap bm1 = new Bitmap(1024, 678, PixelFormat.Format32bppPArgb);

for (int i = 0; i < loops; i++)

{

Graphics g1 = Graphics.FromImage(bm1);

g1.FillRectangle(Brushes.Black, 10, 10, 1004, 748);

g1.Clear(Color.White);

g1.Dispose();

}

ts = DateTime.Now - dt;

sb.Append(string.Format("Total time taken={0} minutes, {1} seconds, {2}
milliseconds\r\n", ts.Minutes, ts.Seconds - 60 * ts.Minutes,
ts.Milliseconds - ((60000 * ts.Minutes) + (1000 * ts.Seconds))));

this.textBox1.Text = sb.ToString();



[quoted text, click to view]

pamela fluente
11/2/2006 7:15:13 AM

Bob Powell [MVP] ha scritto:

[quoted text, click to view]

Thanks. Bob. That's actually interesting and good to know.

Ciao :) ,

P

[quoted text, click to view]
Joergen Bech <jbech<NOSPAM> NO[at]SPAM
11/2/2006 8:49:18 PM
On 2 Nov 2006 07:15:13 -0800, "pamela fluente"
[quoted text, click to view]

On my machine, I can create and dispose a Graphics object
20,000 times per second, i.e.

For i As Integer = 1 To 20000
g = Graphics.FromImage(bm)
g.Dispose()
Next i

Importantly, the size of the bitmap does not matter, nor does
it seem to affect the number handle count of the process.
Little effect on memory, even if the Dispose line in the sample
above is removed.

Multiple Graphics objects can reference the same image at
the same time.

Nothing to think about here. Just keep the code clean.

/JB


Bob Powell [MVP]
11/17/2006 6:34:14 PM
Me.Picturebox1.Invalidate is probably faster.

--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.



[quoted text, click to view]

AddThis Social Bookmark Button