DrawRectangle on Picturebox not working

rrjwilson

Member
Joined
Mar 2, 2010
Messages
13
Programming Experience
1-3
I've coded a project to use a webcam for motion tracking (like many others). I use avicap32.dll and receive images happily at 640x480 i then grab a frame for reference then grab another frame and difference them to produce a black and white picturebox (picture4) I then use this picture to create the image for another picturebox (picture5) but this one draws rectangles over any 5x5 white areas. Or at least that is what is supposed to happen. I just cannot draw the rectangles for some reason even though I'm doing as thousand of pages say. Below is the routine in question all others work happily but for some reason this one does not.
Any ideas?
VB.NET:
Private Sub Detection(ByVal old As PictureBox, ByVal knew As PictureBox)
        Dim pic As New Bitmap(old.Image)
        Dim i As Integer
        Dim bgr As Color
        Dim p As New Pen(Color.Red, 1)
        Dim d As Graphics
        d = knew.CreateGraphics
        'knew.Refresh()

        d.DrawImage(pic, 0, 0, knew.Width, knew.Height)
        ' Detect blob size
        For xcnt = 0 To old.Image.Width - 1
            For ycnt = 0 To old.Image.Height - 1
                bgr = pic.GetPixel(xcnt, ycnt)
                i = 0
                If bgr = Color.White Then
                    For a = -2 To 2
                        For b = -2 To 2
                            If pic.GetPixel(xcnt + a, ycnt + b) = Color.White Then
                                area(i) = "true"
                                i += 1
                            End If
                        Next
                    Next
                    If area(0) = area(1) = area(2) = area(3) = area(4) = area(5) _
                    = area(6) = area(7) = area(8) = area(9) = area(10) _
                    = area(11) = area(12) = area(13) = area(14) = area(15) _
                    = area(16) = area(17) = area(18) = area(19) = area(20) _
                    = area(21) = area(22) = area(23) = area(24) = area(25) Then
                        d.DrawRectangle(p, xcnt - 2, ycnt - 2, 5, ycnt + 2)
                    End If
                    ReDim area(25)
                End If
            Next
        Next
    End Sub
 
First up, I assume that your second method parameter should be "new", which is the opposite of "old", rather than "knew", which is the past tense of "know".

As for the issue, you should pretty much NEVER be calling CreateGraphics. If you want to draw on a control then you handle that control's Paint event, which provides you with a Graphics object. If you want to change what's drawn at any time then you change the data that describes the drawing and then call Refresh or Invalidate and Update on the control.
 
knew is just what i called it argument i could have called it Boba_Fett

All I want to do is draw a rectangle over the discovered point. By adding PaintEventArgs (ByVal e as PaintEventArgs) i get to use the e.Graphics.DrawRectangle but am not passing any paint events because this is just a call. Also with the e.Graphics I cannot call refresh or update. I have looked at your dynamic GDI examples and all of them are passed by a control not just created.

I use the exact same code but with setPixel to do the difference image and that works perfectly. Code is below but I am still stumped.
VB.NET:
    Private Sub Compare(ByVal old As PictureBox, ByVal knew As PictureBox, ByVal out As PictureBox)
        Dim R, G, B As Integer
        Dim R1, G1, B1 As Integer
        Dim R2, G2, B2 As Integer
        Dim OldPic As New Bitmap(old.Image)
        Dim NewPic As New Bitmap(knew.Image)
        Dim OutputPic As New Bitmap(knew.Image)
        Dim d As Graphics
        d = out.CreateGraphics

        ' Subtract previous details
        For xcnt = 0 To old.Image.Width - 1
            For ycnt = 0 To old.Image.Height - 1
                R1 = OldPic.GetPixel(xcnt, ycnt).R
                R2 = NewPic.GetPixel(xcnt, ycnt).R
                B1 = OldPic.GetPixel(xcnt, ycnt).B
                B2 = NewPic.GetPixel(xcnt, ycnt).B
                G1 = OldPic.GetPixel(xcnt, ycnt).G
                G2 = NewPic.GetPixel(xcnt, ycnt).G
                R = R1 - R2
                G = G1 - G2
                B = B1 - B2
                If R < 0 Then R = R * -1
                If G < 0 Then G = G * -1
                If B < 0 Then B = B * -1
                If (R + G + B) < tbrThreshold.Value Then
                    OutputPic.SetPixel(xcnt, ycnt, Color.Black)
                Else
                    OutputPic.SetPixel(xcnt, ycnt, Color.White)
                End If
            Next
        Next

        d.DrawImage(OutputPic, 0, 0, knew.Image.Width, knew.Image.Height)
        d.Dispose()
        out.Image = OutputPic

    End Sub
 
Draw on the image that is displayed in picturebox (once, permanent), or use the controls Paint event (every event, dynamic), but as explained do not use CreateGraphics for drawing.
i get to use the e.Graphics.DrawRectangle but am not passing any paint events because this is just a call. Also with the e.Graphics I cannot call refresh or update
The only call you should do in relation to dynamic painting is to Invalidate/Update or Refresh the control in order for its Paint event handler to be invoked.
 
I'm trying to get my head around using your code for my purpose as I have NO events to pass to the paint handler currently as the whole graphic is being calculated.
After following other posts as well as this one I have still not got the result I had hoped. I am now using the Graphics.FromImage(bmp) to produce a graphic the has the difference map of the two pictures but without calling .CreateGrahics the graphics do not show and there is no Bitmap.FromGraphic. If I could Bitmap.FromGraphic or Ctype(Graphics, image) I would be having no problems (The first code does not work because the image called from the picturebox is blank because it is a graphic).
The blob detection part I can use John's invalidate/update approach easily because that is exactly what I'm doing (small data change) but the Compare routine is nothing like it. I know this code functions as it should mathematically (proven by .CreateGraphics producing exactly what it should) but it produces a graphic not an image which is less than helpful when I try to take that image (lovely and blank as it is) and do white blob detection on it. Could I place the more sensible .FromImage code within the .paint event of the control and then just call that? would that produce an image (adjusted to be a difference map).
I have seen the Lockbit mentioned in other places as well as bitblt for image manipulation but that seems very long winded (for something so "normal").
Any easy graphics to image conversions? or will I have to go the road of Lockbit or bitblt? Would prefer not to use the save code below.

VB.NET:
OutputPic.Save("c:\test1.bmp", Imaging.ImageFormat.Bmp)
        out.Image = Bitmap.FromFile("c:\test1.bmp")


VB.NET:
    Private Sub Compare(ByVal old As PictureBox, ByVal knew As PictureBox, ByVal out As PictureBox)
        Dim R, G, B As Integer
        Dim R1, G1, B1 As Integer
        Dim R2, G2, B2 As Integer
        Dim OldPic As New Bitmap(old.Image)
        Dim NewPic As New Bitmap(knew.Image)
        Dim OutputPic As New Bitmap(knew.Image.Width, knew.Image.Height)
        Dim d As Graphics = Graphics.FromImage(OutputPic)

        ' Subtract previous details
        For xcnt = 0 To old.Image.Width - 1
            For ycnt = 0 To old.Image.Height - 1
                R1 = OldPic.GetPixel(xcnt, ycnt).R
                R2 = NewPic.GetPixel(xcnt, ycnt).R
                B1 = OldPic.GetPixel(xcnt, ycnt).B
                B2 = NewPic.GetPixel(xcnt, ycnt).B
                G1 = OldPic.GetPixel(xcnt, ycnt).G
                G2 = NewPic.GetPixel(xcnt, ycnt).G
                R = R1 - R2
                G = G1 - G2
                B = B1 - B2
                If R < 0 Then R = R * -1
                If G < 0 Then G = G * -1
                If B < 0 Then B = B * -1
                If (R + G + B) < tbrThreshold.Value Then
                    OutputPic.SetPixel(xcnt, ycnt, Color.Black)
                Else
                    OutputPic.SetPixel(xcnt, ycnt, Color.White)
                End If
            Next
        Next

        'd.DrawImage(OutputPic, 0, 0, knew.Image.Width, knew.Image.Height)
        out.Invalidate()
        out.Update()
        d.Dispose()
        OldPic.Dispose()
        NewPic.Dispose()
        OutputPic.Dispose()

    End Sub
 
Last edited:
You need to first understand how GDI+ works in order to use it. Controls on screen are not just drawn once. They are drawn repeatedly and at various times, some predictable and some not so. Every time a control needs to be drawn it's OnPaint method is called, which draws the control and raises the Paint event. By handling the Paint event you are able to perform custom drawing. You do not pass anything to a Paint event handler, any more than you pass something to the Click event handler of a Button. The event is raised and the event handler executed. In the event handler you get whatever data you need to do whatever you need to do.

In the case of a Paint event handler, you will need to have stored data elsewhere that describes your drawing. In the Paint event handler, you retrieve that data and use it to draw. An example of that is the collection of Line objects in one of those posts of mine. Those Line objects are not actual lines. They are data that describes lines. You use that data to draw actual lines in the Paint event handler.

If you ever need to change what's draw on a control then you need to draw. The only place you draw on a control is in the Paint event handler, so you need to raise the Paint event. You modify the stored data that describes the drawing and then you call either Refresh or Invalidate and Update to raise a Paint event. When the event is raised your event handler is executed and the drawing gets done.

Note that you always do all the drawing in the Paint event but possibly only part of that drawing will get painted to the screen. That is the slowest step so it needs to be kept to a minimum. When you call Invalidate you can specify an area of the control. When a control is painted, only the the invalidated areas are done. As such, you should only invalidate areas that could have changed. Calling Invalidate with no arguments invalidates the entire control. It's when you call Update that the Paint event is raised, so you can call Invalidate multiple times beforehand if appropriate. Calling Refresh is equivalent to calling Invalidate with no arguments and then calling Update. In fact, that is exactly what it does internally.

You can also use GDI+ to draw on Image objects. Unlike for controls, this is only done once and it is permanent. For a control you use the Graphics object provided by the Paint event handler to draw. For an Image, you call Graphics.FromImage to create one, which you must remember to dispose when you're done. Again, I have already provided an example of this. My first link in my last post shows how to draw temporarily on a PictureBox and then make that permanent on the Image displayed in the PictureBox.
 
Ok so let me get this straight before I basically rewrite everything.
To change a bitmap with the graphics I must call the Bitmap.FromImage (like I am doing) but I must call the .Paint event to change the pixel using .setPixel as doing it in the normal routine does nothing? Pseudo example below to show shorthand iteration.
The x marks the tracked center also needs to call the .Paint event but because it is on the control not the image when I refresh it will be removed and redrawn in its new location? Do the same call but use an inflated area for the x? or just refresh as it could be anywhere?

Pseudo Example
Sub Compare(pic1,pic2,pic3)
pic1.getpixel(x,y)
pic2.getpixel(x,y)
comp = color - color
if comp > threshold then
target.col = white​
endif
target.pic = pic3
target.x = x
target.y = y
pic3.invalidate()
pic3.update()​
end sub
 
To change a bitmap with the graphics I must call the Bitmap.FromImage (like I am doing) but I must call the .Paint event to change every pixel individually using .setPixel as otherwise it will not work?
Can so there is no way for me to create a graphic and convert it to an image? excluding lockbit and bitblt.
 
Can so there is no way for me to create a graphic and convert it to an image?
It seems you have that backwards. You can compare a Graphics object to a pencil, you can't make a pencil an image, but you can use the pencil to draw an image. Just choose your canvas. If you want to draw to an image then create/get an image/bitmap object first, then assign the Graphics to that and start drawing. If you want to draw to screen use the appropriate controls Paint event.
 
Yes John using the correct canvas by using the Bitmap.FromImage allows me to draw upon the image but the crucial part I was missing was a simple .update() call to the pbox holding the image.

Its not about the paint event its just calling for an update (no code required in the .paint)
.Setpixel (x,y,RGB)
.drawimage(pfft,0,0,w,h)
.update()

back to trying to find a nice way to find the biggest white part :D
Thanks guys.
 
If you make changes to the image currently displayed by a control that would require Control.Refresh for it to redraw itself, that is true.
 
Sorry I just realised I did not post the solution. This code takes images from the first two arguments of the call and creates a black and white difference image which it displays in the third argument. I currently have it called on a timer_tick which allows me to control FPS or the comparison 10FPS is easily achievable. The parts in bold are the important parts to note. The Graphics.FromImage means you are drawing ON the image not just over it like a normal graphic. SetPixel does not require code in the paint event just needs you to draw the pixels you have changed, put the new pic into the picturebox and picturebox.update. Nothing else is needed.

VB.NET:
Private Sub Compare(ByVal old As PictureBox, ByVal knew As PictureBox, ByVal out As PictureBox)
        Dim R, G, B As Integer
        Dim R1, G1, B1 As Integer
        Dim R2, G2, B2 As Integer
        Dim OldPic As New Bitmap(old.Image)
        Dim NewPic As New Bitmap(knew.Image)
        Dim OutputPic As New Bitmap(knew.Image.Width, knew.Image.Height)
        [B]Dim d As Graphics = Graphics.FromImage(OutputPic)
[/B]
        ' Subtract previous details
        For xcnt = 0 To old.Image.Width - 1
            For ycnt = 0 To old.Image.Height - 1
                R1 = OldPic.GetPixel(xcnt, ycnt).R
                R2 = NewPic.GetPixel(xcnt, ycnt).R
                B1 = OldPic.GetPixel(xcnt, ycnt).B
                B2 = NewPic.GetPixel(xcnt, ycnt).B
                G1 = OldPic.GetPixel(xcnt, ycnt).G
                G2 = NewPic.GetPixel(xcnt, ycnt).G
                R = R1 - R2
                G = G1 - G2
                B = B1 - B2
                If R < 0 Then R = R * -1
                If G < 0 Then G = G * -1
                If B < 0 Then B = B * -1
                If (R + G + B) < tbrThreshold.Value Then
                    OutputPic.SetPixel(xcnt, ycnt, Color.Black)
                Else
                    OutputPic.SetPixel(xcnt, ycnt, Color.White)
                End If
            Next
        Next

        [B]d.DrawImage(OutputPic, 0, 0, knew.Image.Width, knew.Image.Height)
        out.Image = OutputPic
        out.Update()[/B]

        d.Dispose()
        OldPic.Dispose()
        NewPic.Dispose()
        OutputPic.Dispose()

    End Sub
 
Back
Top