Home All Groups Group Topic Archive Search About

Optimizing Repeated PictureBox.Paints

Author
7 Mar 2006 3:29 PM
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.

Author
9 Mar 2006 11:57 AM
Larry Lard
The Confessor wrote:
> 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.

--
Larry Lard
Replies to group please