Optimizing Repeated PictureBox.Paints

  • Thread starter Thread starter The Confessor
  • Start date Start date
T

The Confessor

I'm trying to develop a graphically-simple, gameplay-complex RPG, and I
decided to create a graphical Proof of Concept just to confirm that
graphics would be... well, simple.

Predictably, they've proven otherwise.

This is my PictureBoxMap_Paint() Sub, which a tiles a PictureBox with 19*
17 40*40-pixel images, two rows and two columns of which are painted
beyond the current bounds of the picturebox to facilitate scrolling. The
If/Else statements handle the bounds of the map... At the moment they
simply 'roll over' and display the graphics on the other side.

Dim A, B As Integer
For A = -9 To 9
For B = -8 To 8
Dim X, Y As Integer
If A + CurrentCoords.East < 0 Then
X = Cell.GetUpperBound(0) + (A + CurrentCoords.East) + 1
ElseIf A + CurrentCoords.East > Cell.GetUpperBound(0) Then
X = (A + CurrentCoords.East) - Cell.GetUpperBound(0) - 1
Else
X = A + CurrentCoords.East
End If
If B + CurrentCoords.North < 0 Then
Y = Cell.GetUpperBound(1) + (B + CurrentCoords.North) +
ElseIf B + CurrentCoords.North > Cell.GetUpperBound(1) Then
Y = (B + CurrentCoords.North) - Cell.GetUpperBound(1) - 1
Else
Y = B + CurrentCoords.North
End If
e.Graphics.DrawImage(CellImages(Cell(X, Y,
CurrentCoords.Up).Image), ((A + 9) * 40) - 40 + XOffSet, (520 - ((B + 6)
* 40) + YOffSet))
Next
Next

The XOffSet and YOffSet are used to produce the scrolling effect, as seen
in this code from my Form_KeyDown procedure.

If e.KeyCode = System.Windows.Forms.Keys.Left Then
For XOffSet = 0 To 40 Step 4
PictureBox_Map.Refresh()
Next
XOffSet = 0
If CurrentCoords.East > 0 Then
CurrentCoords.East = CurrentCoords.East - 1
Else
CurrentCoords.East = Cell.GetUpperBound(0)
End If
PictureBox_Map.Refresh()
End If

Unfortunately, this produces a very slow scroll on my Pentium III 933MHZ;
I've had to add the equivalent of frame-skipping using 'Step 4' just to
make it comparable to the speed in, say, Final Fantasy.

My theory for the reason behind this slowness was the reassembly of the
map with each PictureBox_Map.Refresh(), and I therefore searched for a
way to offset a preassembled graphic, reassembling only when necessary.

Unfortunately, I could not then force PictureBox_Map to display this
preassembled graphic in such a way that it could be offset as necessary
to produce scrolling.

An IRC acquaintance who otherwise has been very helpful suggested that
calling PictureBox_Map.Refresh might be producing some unintended
overhead, and that calling the PictureBox_Paint() routine directly from
KeyDown might be better.

Unfortunately, I could not call it in such a way that my entry for the
PaintEventArgs argument would not completely break the PictureBox_Paint
routine.

Does anybody have any suggests as to where, exactly, most of my overhead
is coming from and how I might reduce or eliminate it?

Are repliers have my sincere gratitude.
 
The said:
I'm trying to develop a graphically-simple, gameplay-complex RPG, and I
decided to create a graphical Proof of Concept just to confirm that
graphics would be... well, simple.
[snip]

I won't go into details, because I think your performance problems can
be best addressed by conceptual changes rather than particular code
changes.

The way to handle a picturebox that displays a complex graphics is not
to build the graphics in the Paint event, but rather to maintain a
Bitmap, which holds the actual image, and in the Paint event just
DrawImage the bitmap onto the Graphics provided. When the image is
required to change, draw the changes onto the Bitmap, then Invalidate
the picturebox to force it to paint. Here is a short example:

' VS2003/2005
' create a new Windows Forms app
' drop a picturebox and two buttons on a form
' add this at the top of the code file
Imports System.Drawing

'add this in the form, before the End Class line
Private myImage As Bitmap
Private myImageGraphics As Graphics
'probably more correct to create the Graphics
'whenever we want to draw
'but this is quicker

Private Sub Form1_Load(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Load
myImage = New Bitmap(PictureBox1.Width, PictureBox1.Height,
Imaging.PixelFormat.Format32bppArgb)
myImageGraphics = Graphics.FromImage(myImage)

myImageGraphics.Clear(Color.White)
End Sub

Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
'all we do in here is draw myImage
e.Graphics.DrawImage(myImage, e.ClipRectangle, e.ClipRectangle,
GraphicsUnit.Pixel)
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
'create a complex image
Dim x As Integer, y As Integer
Dim b As Brush, c As Color
Dim r As New Random

For x = 0 To myImage.Width Step 10
For y = 0 To myImage.Height Step 8
c = Color.FromArgb(r.Next(0, 255), r.Next(0, 255),
r.Next(0, 255))
b = New SolidBrush(c)
Try
myImageGraphics.FillRectangle(b, x, y, 10, 8)
Finally
b.Dispose()
End Try
Next
Next

'and force painting
PictureBox1.Invalidate()
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
'make some change to the image
Dim oldImage As Bitmap = myImage
myImage = New Bitmap(PictureBox1.Width, PictureBox1.Height,
Imaging.PixelFormat.Format32bppArgb)
myImageGraphics = Graphics.FromImage(myImage)

myImageGraphics.ResetTransform()
myImageGraphics.TranslateTransform(-myImage.Width \ 2,
-myImage.Height \ 2, Drawing2D.MatrixOrder.Append)
myImageGraphics.RotateTransform(10,
Drawing2D.MatrixOrder.Append)
myImageGraphics.TranslateTransform(myImage.Width \ 2,
myImage.Height \ 2, Drawing2D.MatrixOrder.Append)
myImageGraphics.DrawImage(oldImage, 0, 0)

PictureBox1.Invalidate()
End Sub



Your situation is going to involve creating the main image with an
extra strip of cells, as currently, and some error-prone transformation
stuff working out exactly which portion of the image to draw in the
Paint event, so have fun with that :) The key point is that we only do
the 'hard' work (that nested loop that creates the image) when we have
to, ie when we scrol to a region we haven't seen before. While
examining a portion of the image we *have* created, all we do is just
DrawImage.
 
Back
Top