Solved

Undo for Picturebox Draw Control

Posted on 2006-10-22
8
2,161 Views
Last Modified: 2008-02-01
What I want to do is add a button "btnUndo" that will undo the last draw operation on this control.  I tried creating an array that will save the current image after each operation and the undo button will revert to it, but I had massive memmory problems with some of the larger images I have tried to edit.  Here is the code I have.

[code]

    Private bitC As New Bitmap(Gadler2.My.Resources.g)
    Private gC As Graphics = Graphics.FromImage(bitC)
    Private brC As New Pen(Color.Red)
    Private dwnC As Boolean
    Private lstC As Point
    Private firstrunC As Boolean = True
    Private PenColorC As String

    Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
        If btnBlue.Checked = True Then
            PenColorC = "Blue"
            dwnC = True
        End If

        If btnBlack.Checked = True Then
            PenColorC = "Black"
            dwnC = True
        End If

        If btnRed.Checked = True Then
            PenColorC = "Red"
            dwnC = True
        End If

        If btnGreen.Checked = True Then
            PenColorC = "Green"
            dwnC = True
        End If

        If btnEraser.Checked = True Then
            PenColorC = "White"
            dwnC = True
        End If
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
        If dwnC = True Then
            Dim sC As Integer
            Dim xyC As Point
            gC.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
            sC = 2
            brC.Color = Color.FromName(PenColorC)
            brC.Width = 2
            If PenColorC = "White" Then brC.Width = 8
            xyC.X = e.X
            xyC.Y = e.Y
            If firstrunC = True Then
                lstC = xyC
                firstrunC = False
            End If
            gC.DrawLine(brC, lstC, xyC)
            lstC = xyC

            PictureBox1.Image = bitC

        End If
    End Sub

    Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp
        dwnC = False
        firstrunC = True
    End Sub

[/code]

0
Comment
Question by:rbradberry
  • 4
  • 3
8 Comments
 
LVL 10

Expert Comment

by:Kinger247
ID: 17784889
Save the coordinates in the array not the image ..!
That way you redraw the previous coordinates.

0
 
LVL 3

Author Comment

by:rbradberry
ID: 17785731
but if i redraw the prvious coordinates in a different color then i am not placing the image back to the way it was, for instance if I cross over 3 different color lines then i would have to somehoe memorize what that pixel color was.  i don't know
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 17785907
Play with the code below...

It has an ArrayList "paths" that stores each "drawing operation" in it.  To remove the last operation, simply remove the Last GraphicsPath object in the list.  Removing the objects in reverse until it is empty undoes all operations.  Obviously this version isn't storing color by I think you get the idea.  To make it more complex you would make a custom class to store the GraphicsPath, Color, Width, etc, and store instances of that class in the ArrayList instead.  Then in the Paint event you extract the info about each drawing before painting that piece...

Public Class Form1

    Private paths As New ArrayList
    Private gp As System.Drawing.Drawing2D.GraphicsPath
    Private lastPoint As Point

    Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Left Then
            gp = New System.Drawing.Drawing2D.GraphicsPath
            paths.Add(gp)
            lastPoint = New Point(e.X, e.Y)
        End If
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
        If e.Button = Windows.Forms.MouseButtons.Left Then
            Dim curPoint As New Point(e.X, e.Y)
            gp.AddLine(lastPoint, curPoint)
            lastPoint = curPoint
            PictureBox1.Refresh()
        End If
    End Sub

    Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp
        If e.Button = Windows.Forms.MouseButtons.Left Then
            PictureBox1.Refresh()
        End If
    End Sub

    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        Dim gp As System.Drawing.Drawing2D.GraphicsPath
        Dim g As Graphics = e.Graphics
        For Each gp In paths
            g.DrawPath(Pens.Black, gp)
        Next
    End Sub

End Class
0
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
LVL 3

Author Comment

by:rbradberry
ID: 17792417
this seems like a viable solution, however, the lines bieng drawn are not altering the underlying image so when i save the image the changes aren't save, also when i change the pencolor it changes the color of all the lines, not jsut the last one drawn.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 17792774
"the lines bieng drawn are not altering the underlying image so when i save the image the changes aren't save"

Correct.  To save the image you first make a copy of the base image and get a graphics to it.  Then you paint the lines in the same manner that the Paint() event does and save the final image.

Something like:

    ' somewhere else you have your "base" image that you set the PictureBox.Image prop to:
    Dim yourOriginalImageInstanceHere As Image = Image.FromFile("c:\someImageFile.bmp")

    ' make a copy of the "base" and get a graphics to it
    Dim newBmp As New Bitmap(yourOriginalImageInstanceHere)
    Dim G As Graphics = Graphics.FromImage(newBmp)
    ' draw all the lines on the copy
    For Each gp As System.Drawing.Drawing2D.GraphicsPath In paths
        G.DrawPath(Pens.Black, gp)
    Next
    ' cleanup and save the new composite image
    G.Dispose()
    newBmp.Save("c:\someImage.bmp")

"when i change the pencolor it changes the color of all the lines, not jsut the last one drawn"

True.  I already said that the example I gave was a simple case and that it would need modification to handle multiple colors/widths.

Would you like to see a more sophisticated example?

By the way, what version VB are you working with here?  2003 or below? ...or 2005?
0
 
LVL 3

Author Comment

by:rbradberry
ID: 17793064
sure, i would love to see a better example.  i am working with VB 2005.  Thank you so much.
0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
ID: 17797409
Here is a more sophisticated example...

Add the code below to a blank form.  Double click the red square to change pen color.

Public Class TestForm

    Private Class Squiggle
        Public C As Color
        Public W As Integer
        Public GP As System.Drawing.Drawing2D.GraphicsPath

        Public Sub New(ByVal SquiggleColor As Color, ByVal SquiggleWidth As Integer)
            Me.C = SquiggleColor
            Me.W = SquiggleWidth
            GP = New System.Drawing.Drawing2D.GraphicsPath
        End Sub
    End Class

    Private Squiggles As New ArrayList
    Private curSquiggle As Squiggle = Nothing
    Private lastPoint As Point = Nothing

    Private WithEvents PB As New PictureBox
    Private WithEvents OpenImageBtn As New Button
    Private WithEvents CurColor As New PictureBox
    Private WithEvents CurWidth As New NumericUpDown
    Private WithEvents SaveImageBtn As New Button
    Private WithEvents UndoBtn As New Button
    Private WithEvents ClearBtn As New Button

    Private Sub TestForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim sc1 As New SplitContainer
        sc1.Dock = DockStyle.Fill
        sc1.Orientation = Orientation.Vertical
        sc1.FixedPanel = FixedPanel.Panel1
        sc1.SplitterDistance = 90
        sc1.IsSplitterFixed = True
        sc1.Panel1.Padding = New Padding(5)

        Dim fl1 As New FlowLayoutPanel
        fl1.Dock = DockStyle.Fill
        sc1.Panel1.Controls.Add(fl1)
        fl1.FlowDirection = FlowDirection.TopDown

        OpenImageBtn.Width = 75
        OpenImageBtn.Text = "Open Image"
        fl1.Controls.Add(OpenImageBtn)

        CurColor.Size = New Size(75, 75)
        CurColor.BorderStyle = BorderStyle.FixedSingle
        CurColor.BackColor = Color.Red
        fl1.Controls.Add(CurColor)

        CurWidth.Width = 75
        CurWidth.Minimum = 1
        CurWidth.Maximum = 7
        fl1.Controls.Add(CurWidth)

        SaveImageBtn.Width = 75
        SaveImageBtn.Text = "Save Image"
        fl1.Controls.Add(SaveImageBtn)

        UndoBtn.Width = 75
        UndoBtn.Text = "Undo"
        fl1.Controls.Add(UndoBtn)

        ClearBtn.Width = 75
        ClearBtn.Text = "Clear"
        fl1.Controls.Add(ClearBtn)

        PB.Dock = DockStyle.Fill
        sc1.Panel2.Controls.Add(PB)

        Me.Controls.Add(sc1)
    End Sub

    Private Sub OpenImageBtn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles OpenImageBtn.Click
        Dim ofd As New OpenFileDialog
        ofd.Title = "Open Image"
        ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*"
        If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
            Try
                ' open image without a file lock
                Dim img As Image = Image.FromFile(ofd.FileName)
                PB.Image = New Bitmap(img)
                img.Dispose()
            Catch ex As Exception
                MessageBox.Show(ofd.FileName & vbCrLf & vbCrLf & ex.Message, "Error Opening File", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End Try
        End If
    End Sub

    Private Sub CurColor_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles CurColor.DoubleClick
        Dim cd As New ColorDialog
        If cd.ShowDialog = Windows.Forms.DialogResult.OK Then
            CurColor.BackColor = cd.Color
        End If
    End Sub

    Private Sub SaveImageBtn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SaveImageBtn.Click
        Dim sfd As New SaveFileDialog
        sfd.Title = "Save Image"
        sfd.Filter = "Bitmaps (*.bmp)|*.bmp"
        If sfd.ShowDialog = Windows.Forms.DialogResult.OK Then
            Dim bmp As New Bitmap(PB.Image)
            Dim g As Graphics = Graphics.FromImage(bmp)
            PaintSquiggles(g)
            g.Dispose()
            bmp.Save(sfd.FileName, System.Drawing.Imaging.ImageFormat.Bmp)
        End If
    End Sub

    Private Sub UndoBtn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles UndoBtn.Click
        If Squiggles.Count > 0 Then
            Squiggles.RemoveAt(Squiggles.Count - 1)
            PB.Refresh()
        End If
    End Sub

    Private Sub ClearBtn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ClearBtn.Click
        If Squiggles.Count > 0 Then
            Squiggles.Clear()
            PB.Refresh()
        End If
    End Sub

    Private Sub PB_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PB.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Left Then
            curSquiggle = New Squiggle(CurColor.BackColor, CurWidth.Value)
            Squiggles.Add(curSquiggle)
            lastPoint = New Point(e.X, e.Y)
        End If
    End Sub

    Private Sub PB_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PB.MouseMove
        If e.Button = Windows.Forms.MouseButtons.Left Then
            If Not (curSquiggle Is Nothing) Then
                Dim curPoint As New Point(e.X, e.Y)
                curSquiggle.GP.AddLine(lastPoint, curPoint)
                lastPoint = curPoint
                PB.Refresh()
            End If
        End If
    End Sub

    Private Sub PB_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PB.MouseUp
        If e.Button = Windows.Forms.MouseButtons.Left Then
            If Not (curSquiggle Is Nothing) Then
                curSquiggle = Nothing
                lastPoint = Nothing
                PB.Refresh()
            End If
        End If
    End Sub

    Private Sub PB_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PB.Paint
        PaintSquiggles(e.Graphics)
    End Sub

    Private Sub PaintSquiggles(ByVal g As Graphics)
        Dim P As Pen
        For Each squig As Squiggle In Squiggles
            P = New Pen(squig.C, squig.W)
            g.DrawPath(P, squig.GP)
            P.Dispose()
        Next
    End Sub

End Class
0
 
LVL 3

Author Comment

by:rbradberry
ID: 17800880
Thank you so much, this works perfectly.  You seriously helped me through a rediculous deadline.

-Russ
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

This article explains how to create and use a custom WaterMark textbox class.  The custom WaterMark textbox class allows you to set the WaterMark Background Color and WaterMark text at design time.   IMAGE OF WATERMARKS STEPS Create VB …
This tutorial demonstrates one way to create an application that runs without any Forms but still has a GUI presence via an Icon in the System Tray. The magic lies in Inheriting from the ApplicationContext Class and passing that to Application.Ru…
Finds all prime numbers in a range requested and places them in a public primes() array. I've demostrated a template size of 30 (2 * 3 * 5) but larger templates can be built such 210  (2 * 3 * 5 * 7) or 2310  (2 * 3 * 5 * 7 * 11). The larger templa…

790 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question