Drawing on an Image with transparency? (to erase a section)

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
First of all I am just an amateur with no formal training, so please don't flame me for my errors - I am really trying to get this right.

I have 2 questions

1. How can I erase a circle in an image to make a transparent section
2. How can I reload the edited image to the picture box (so that it can be saved later)

I have a form where a user loads an image to create a connect 4 game board. I draw on it the circles to cut the holes in it, and want to then be able to save it as a game board. Saving it is not the problem. Drawing the image is not a problem. The part I need help with is reloading the drawing with the graphic edits and transparency.
I know I must be missing something really simple but its frustrating the heck out of me!

I am aware that some dim statements are not in the proper places ;-/

The form contains 3 controls - 2 buttons (Load and create) and a picture box. Here is my NOT working properly code.

VB.NET:
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Drawing.Imaging

Public Class FrmCreateBoard
 
Private Sub FrmCreateBoard_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        PictureBox1.BackColor = Color.Transparent
        PictureBox1.Size = New Size(794, 680)
End Sub

Private Sub butLoadImage_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butLoadImage.Click
        Me.OpenFileDialog1.Filter = "JPEG files (*.jpg)|*.jpg|GIF files (*.gif)|*.gif|All files (*.*)|*.*"
        If OpenFileDialog1.ShowDialog() = DialogResult.OK Then
            PictureBox1.Image = Image.FromFile(OpenFileDialog1.FileName)

            Dim original As Image = PictureBox1.Image
            Dim resized As Image = ResizeImage(original, New Size(1024, 768))
            Dim memStream As MemoryStream = New MemoryStream()
            'resized.Save(memStream, ImageFormat.Jpeg)
            PictureBox1.Image = resized
        End If
End Sub

Public Shared Function ResizeImage(ByVal image As Image, ByVal size As Size, Optional ByVal preserveAspectRatio As Boolean = True) As Image
        Dim newWidth As Integer
        Dim newHeight As Integer
        If preserveAspectRatio Then
            Dim originalWidth As Integer = image.Width
            Dim originalHeight As Integer = image.Height
            Dim percentWidth As Single = CSng(size.Width) / CSng(originalWidth)
            Dim percentHeight As Single = CSng(size.Height) / CSng(originalHeight)
            Dim percent As Single = If(percentHeight < percentWidth,
        percentHeight, percentWidth)
            newWidth = CInt(originalWidth * percent)
            newHeight = CInt(originalHeight * percent)
        Else
            newWidth = size.Width
            newHeight = size.Height
        End If
        Dim newImage As Image = New Bitmap(newWidth, newHeight)
        Using graphicsHandle As Graphics = Graphics.FromImage(newImage)
            graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic
            graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight)
        End Using
        Return newImage
End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' draw circle with mock bevel by drawing transparent circle on a larger solid circle
        Dim xpos As Integer = 60
        Dim ypos As Integer = 60
        For ydirection = 1 To 6
            For xdirection = 1 To 7
                DrawCircle(New Point(xpos, ypos), 44, Pens.RosyBrown, Brushes.RosyBrown)   '(X,Y,radius)
                DrawCircle(New Point(xpos, ypos), 40, Pens.Transparent, Brushes.Transparent)   '(X,Y,radius)
                xpos = xpos + 110
            Next
            xpos = 60 ' start again at start of line (picturebox)
            ypos = ypos + 110
        Next
End Sub

Private Sub DrawCircle(ByVal cp As Point, ByVal radius As Integer, ByVal p As Pen, ByVal b As Brush)
        ' load the background
        Dim Board As Image = PictureBox1.Image
        'create bitmap
        Dim NewImage As Bitmap
        NewImage = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        'create graphics object from picture
        Dim gr As Graphics = Graphics.FromImage(NewImage)
        ' draw the board backround (to draw on top of)
        gr.DrawImage(Board, New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height))
        ' now draw the circles on the board
        gr = PictureBox1.CreateGraphics
        Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
        gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
        gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
        ' Now need to recreate the picture in the picturebox from the graphics component and reload it
        PictureBox1.Image = NewImage
        ' cleanup
        gr.Dispose()
 End Sub

End Class

My first question


When I reload the image in the picture box with:

PictureBox1.Image = NewImage

I expected to see my new image with the drawing on it, but I get the original image with no drawing on it? This is where I have done something wrong. How do I fix this?

My second question.

My second circle doesn't erase anything, its just transparent. The intention is to cut a hole in the image so when its saved as a "*.png" it will be full of transparent areas.
Any ideas why its not doing what it wanted?

Help very much appreciated.
 
Last edited:

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
This always happens!

I eventually post after days of scratching my head then I figure some of it out. I have figured out the redrawing of the image, I was already doing this when I resized the image! - LOL. Now i need to know how to convert a circle in the image to make a hole - ie transparent section.

Here is the fixed code for redrawing the image. What i was not doing was using a "Using" section

VB.NET:
    Private Sub DrawCircle(ByVal cp As Point, ByVal radius As Integer, ByVal p As Pen, ByVal b As Brush)
        ' load the background
        Dim Board As Image = PictureBox1.Image
        'create bitmap
        'Dim NewImage As Bitmap
        Dim NewImage As Image = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        'create graphics object from picture
        Using gr As Graphics = Graphics.FromImage(NewImage)
            gr.InterpolationMode = InterpolationMode.HighQualityBicubic
            ' draw the board backround (to draw on top of)
            gr.DrawImage(Board, New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height))
            ' now draw the circles on the board
            Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
            gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            ' Now need to recreate the picture in the picturebox from the graphics component and reload it
        End Using
        PictureBox1.Image = NewImage
        
    End Sub

Any help with making a circle transparent would be fantastic. (holes in image to save it as png)

Many thanks
 
Last edited:

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
Figured out a solution to the transparency, although I had to use a solid colour and convert it to transparent. Im not sure if this is ideal but it works for some images. What I don't want is for it to make any areas transparent that I have not drawn. IE if the user loads an image with the colour pen in it that I am making transparent, so there must be a better solution ????

First I set my inner circle to white as the color transparent didn't work as a color ???

VB.NET:
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' draw circle with mock bevel by drawing transparent circle on a larger solid circle
        Dim xpos As Integer = 60
        Dim ypos As Integer = 60
        For ydirection = 1 To 6
            For xdirection = 1 To 7
                DrawCircle(New Point(xpos, ypos), 44, Pens.RosyBrown, Brushes.RosyBrown)   '(X,Y,radius)
                DrawCircle(New Point(xpos, ypos), 40, Pens.White, Brushes.White)   '(X,Y,radius)                    <------------------- set the brush to white
                xpos = xpos + 110
            Next
            xpos = 60 ' start again at start of line (picturebox)
            ypos = ypos + 110
        Next
    End Sub

Then in the drawing routine I converted all the white to transparent

VB.NET:
    Private Sub DrawCircle(ByVal cp As Point, ByVal radius As Integer, ByVal p As Pen, ByVal b As Brush)
        ' load the background
        Dim Board As Image = PictureBox1.Image
        'create bitmap
        'Dim NewImage As Bitmap
        'NewImage = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        Dim NewImage As Image = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        'create graphics object from picture
        'Dim gr As Graphics = Graphics.FromImage(NewImage)
        Using gr As Graphics = Graphics.FromImage(NewImage)
            gr.InterpolationMode = InterpolationMode.HighQualityBicubic
            ' draw the board backround (to draw on top of)
            gr.DrawImage(Board, New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height))
            ' now draw the circles on the board
            'gr = PictureBox1.CreateGraphics
            Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
            gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            ' Now need to recreate the picture in the picturebox from the graphics component and reload it
        End Using
        PictureBox1.Image = NewImage
        ' cleanup
        Dim g As New System.Drawing.Bitmap(PictureBox1.Image)
        g.MakeTransparent(System.Drawing.Color.White)
        PictureBox1.Image = g
    End Sub

This was the code I added:

VB.NET:
  Dim g As New System.Drawing.Bitmap(PictureBox1.Image)
        g.MakeTransparent(System.Drawing.Color.White)
        PictureBox1.Image = g

I could not use gr.MakeTransparent because transparency is not in the graphics object but is in the bitmap object.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,589
Location
Norway
Programming Experience
10+
You can do this by using methods Graphics.SetClip with a GraphicsPath (.AddEllipse), then Graphics.Clear(Color.Transparent). Graphics.ResetClip afterwards.

It is not necessary to create a new image each time, you can use the PictureBox.Image directly, as long as the first loaded image supports transparency. You just need to call PictureBox.Refresh after modifying the image for the control to repaint it.

Also take care to dispose any graphics related object once you're done using it.
 

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
Thanks John for the reply

This is exactly what i am looking for. Maybe you can help a little further :shame: I have looked up a done as you suggested but it clips a rectangle, not an ellipse ??
I know I must be miss-understanding the Graphics path method.


Called by DrawCircle(New Point(xpos, ypos), 40, Pens.Transparent, Brushes.Transparent)
There are two methods in my code, one for solid circle and one for transparent.

VB.NET:
            Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            Dim myPath As New GraphicsPath
            If p.Color = Color.Transparent Then
                gr.SetClip(rect)
                myPath.addellipse(rect)
                gr.DrawPath(Pens.Transparent, myPath)
                gr.Clear(Color.Transparent)
                gr.ResetClip()
            Else
                gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
                gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            End If
            gr.Dispose()

or this - incorporating the combine mode gr.SetClip(rect, combineMode.Replace) MSDN says this works (http://msdn.microsoft.com/en-us/library/ms142117.aspx), but its not doing it on my machine :frustrated:
VB.NET:
 Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            Dim myPath As New GraphicsPath ' create a graphics path
            If p.Color = Color.Transparent Then
                myPath.AddEllipse(rect) ' add an ellipse to the graphics path (rect)
                gr.DrawPath(Pens.Transparent, myPath) ' draw the path to the image
                gr.SetClip(rect, combineMode.Replace) ' set a clip region
                gr.Clear(Color.Transparent) ' clear the clip region
                gr.ResetClip() ' reset the clip so it can be re-used
            Else
                gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
                gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            End If

Here I set my rect (area) as a circle, but it clips a square (rectangle)

BoardClip.jpg

I have figured out by painting the clip region that the clip region is NOT an ellipse, it IS a rectangle
 
Last edited:

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
Figured the solution.

It was indeed the clip region, the FIX is to clip the region myPath

gr.SetClip(myPath, CombineMode.Replace) ' set a clip region to the ellipse

VB.NET:
Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            Dim myPath As New GraphicsPath ' create a graphics path
            If p.Color = Color.Transparent Then
                myPath.AddEllipse(rect) ' add an ellipse to the graphics path (rect)
                gr.DrawPath(Pens.Transparent, myPath) ' draw the path to the image
                gr.SetClip(myPath, combineMode.Replace) ' set a clip region   <------------------------ Clip the path region (Not the rect) !!
                gr.Clear(Color.Transparent) ' clear the clip region
                gr.ResetClip() ' reset the clip so it can be re-used
            Else
                gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
                gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            End If

board2.png

Thanks JohnH, you put me on the correct path. I have posted my solution for others to see because examples of this have been so hard to find.

Many thanks - I really do appreciate your help and i am going to enjoy the new toy you have shown me was there (GraphicsPath) ;-)
 

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
Here is the full code to the createboard form. Below if a view of the part made game.

Form consists of a picturebox and 3 buttons

VB.NET:
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Drawing.Imaging

Public Class FrmCreateBoard

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' draw circle with mock bevel by drawing transparent circle on a larger solid circle
        Dim xpos As Integer = 60
        Dim ypos As Integer = 60
        For ydirection = 1 To 6
            For xdirection = 1 To 7
                DrawCircle(New Point(xpos, ypos), 44, Pens.RosyBrown, Brushes.RosyBrown)   '(X,Y,radius)
                DrawCircle(New Point(xpos, ypos), 40, Pens.Transparent, Brushes.Transparent)   '(X,Y,radius)
                xpos = xpos + 110
            Next
            xpos = 60 ' start again at start of line (picturebox)
            ypos = ypos + 110
        Next
    End Sub

    Private Sub DrawCircle(ByVal cp As Point, ByVal radius As Integer, ByVal p As Pen, ByVal b As Brush)
        ' load the background
        Dim Board As Image = PictureBox1.Image
        'create bitmap
        Dim NewImage As Image = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        'create graphics object from picture
        Using gr As Graphics = Graphics.FromImage(NewImage)
            gr.InterpolationMode = InterpolationMode.HighQualityBicubic
            ' draw the board backround (to draw on top of)
            gr.DrawImage(Board, New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height))
            ' now draw the circles on the board
            Dim rect As Rectangle = New Rectangle(cp.X - radius, cp.Y - radius, 2 * radius, 2 * radius)
            Dim myPath As New GraphicsPath ' create a graphics path
            If p.Color = Color.Transparent Then
                myPath.AddEllipse(rect) ' add an ellipse to the graphics path (rect)
                gr.DrawPath(Pens.Transparent, myPath) ' draw the path to the image
                gr.SetClip(myPath, CombineMode.Replace) ' set a clip region to the ellipse
                gr.Clear(Color.Transparent) ' clear the clip region
                gr.ResetClip() ' reset the clip so it can be re-used
            Else
                gr.DrawEllipse(p, rect) ' p = pen colour, rect = object to draw on
                gr.FillEllipse(b, rect) ' b = brush colour, rect = object to draw on
            End If
            ' Now need to recreate the picture in the picturebox from the graphics component and reload it
            gr.Dispose()
        End Using
        PictureBox1.Image = NewImage

    End Sub

    Public Shared Function ResizeImage(ByVal image As Image, ByVal size As Size, Optional ByVal preserveAspectRatio As Boolean = True) As Image
        Dim newWidth As Integer
        Dim newHeight As Integer
        If preserveAspectRatio Then
            Dim originalWidth As Integer = image.Width
            Dim originalHeight As Integer = image.Height
            Dim percentWidth As Single = CSng(size.Width) / CSng(originalWidth)
            Dim percentHeight As Single = CSng(size.Height) / CSng(originalHeight)
            Dim percent As Single = If(percentHeight < percentWidth,
        percentHeight, percentWidth)
            newWidth = CInt(originalWidth * percent)
            newHeight = CInt(originalHeight * percent)
        Else
            newWidth = size.Width
            newHeight = size.Height
        End If
        Dim newImage As Image = New Bitmap(newWidth, newHeight)
        Using graphicsHandle As Graphics = Graphics.FromImage(newImage)
            graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic
            graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight)
        End Using
        Return newImage
    End Function

    Private Sub butLoadImage_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butLoadImage.Click
        Me.OpenFileDialog1.Filter = "JPEG files (*.jpg)|*.jpg|GIF files (*.gif)|*.gif|All files (*.*)|*.*"
        If OpenFileDialog1.ShowDialog() = DialogResult.OK Then
            PictureBox1.Image = Image.FromFile(OpenFileDialog1.FileName)

            'Dim original As Image = Image.FromFile("C:\path\to\some.jpg")
            Dim original As Image = PictureBox1.Image
            Dim resized As Image = ResizeImage(original, New Size(1024, 768))
            Dim memStream As MemoryStream = New MemoryStream()
            'resized.Save(memStream, ImageFormat.Jpeg)
            PictureBox1.Image = resized
        End If
    End Sub

    Private Sub FrmCreateBoard_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        PictureBox1.BackColor = Color.Transparent
        PictureBox1.Size = New Size(794, 680)
    End Sub

    Private Sub SaveBoard_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SaveBoard.Click
        PictureBox1.Image.Save(Application.StartupPath + "\images\test.png", System.Drawing.Imaging.ImageFormat.Png)  <---- replace with your image path to save or a save dialog
    End Sub
End Class

preview.png
 
Last edited:
Top Bottom