HATCHET
asked on
VB.NET - Make Bitmap Grayscale (Bitmap.SetPixel??)
I'm trying to take a System.Drawing.Bitmap object in VB.NET and make it grayscale.
1) Can anyone please give me an easier, more efficient way of making an image grayscale? I'm an expert in VB 5 and 6 and doing this is EASY and fast in VB5/6, but I'm just getting into VB.NET and it's very confusing how you're supposed to do things. It's irritating how Microsoft keeps you away from any real useful functionality that they deem "low-level". It seems they want to keep you at a high level with all the objects, etc... but the objects don't do everything I want, and their use is often cryptic.
2) I tried to do a pixel by pixel color conversion like this... but the following line of code causes the error "An unhandled exception of type 'System.ArgumentException' occurred in system.drawing.dll. Additional information: Invalid parameter used.":
objBITMAP.SetPixel(intCoun terX, intCounterY, clrNew)
What's wrong with my code? It seems straight forward.
__________________________ __________ __________
Private Sub mnuEffectsGrayscale_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuEffectsGrayscale.Click
Dim objBITMAP As System.Drawing.Bitmap
Dim intCounterX As Integer
Dim intCounterY As Integer
Dim clrCurrent As System.Drawing.Color = New System.Drawing.Color()
Dim clrNew As System.Drawing.Color = New System.Drawing.Color()
Dim intCurrent As Integer
Dim intNew As Integer
If picDisplay.Image Is Nothing Then Exit Sub
objBITMAP = picDisplay.Image.Clone
For intCounterY = 0 To objBITMAP.Height - 1
For intCounterX = 0 To objBITMAP.Width - 1
clrCurrent = objBITMAP.GetPixel(intCoun terX, intCounterY)
intCurrent = RGB(clrCurrent.R, clrCurrent.G, clrCurrent.B)
intNew = 0.33 * (intCurrent Mod 256) + _
0.59 * ((intCurrent \ 256) Mod 256) + _
0.11 * ((intCurrent \ 65536) Mod 256)
If intNew < 0 Then intNew = 0
If intNew > 255 Then intNew = 255
clrNew.FromArgb(intNew, intNew, intNew)
objBITMAP.SetPixel(intCoun terX, intCounterY, clrNew)
Next intCounterX
Next intCounterY
picDisplay.Image.Dispose()
picDisplay.Image = objBITMAP
picDisplay.Refresh()
End Sub
1) Can anyone please give me an easier, more efficient way of making an image grayscale? I'm an expert in VB 5 and 6 and doing this is EASY and fast in VB5/6, but I'm just getting into VB.NET and it's very confusing how you're supposed to do things. It's irritating how Microsoft keeps you away from any real useful functionality that they deem "low-level". It seems they want to keep you at a high level with all the objects, etc... but the objects don't do everything I want, and their use is often cryptic.
2) I tried to do a pixel by pixel color conversion like this... but the following line of code causes the error "An unhandled exception of type 'System.ArgumentException'
objBITMAP.SetPixel(intCoun
What's wrong with my code? It seems straight forward.
__________________________
Private Sub mnuEffectsGrayscale_Click(
Dim objBITMAP As System.Drawing.Bitmap
Dim intCounterX As Integer
Dim intCounterY As Integer
Dim clrCurrent As System.Drawing.Color = New System.Drawing.Color()
Dim clrNew As System.Drawing.Color = New System.Drawing.Color()
Dim intCurrent As Integer
Dim intNew As Integer
If picDisplay.Image Is Nothing Then Exit Sub
objBITMAP = picDisplay.Image.Clone
For intCounterY = 0 To objBITMAP.Height - 1
For intCounterX = 0 To objBITMAP.Width - 1
clrCurrent = objBITMAP.GetPixel(intCoun
intCurrent = RGB(clrCurrent.R, clrCurrent.G, clrCurrent.B)
intNew = 0.33 * (intCurrent Mod 256) + _
0.59 * ((intCurrent \ 256) Mod 256) + _
0.11 * ((intCurrent \ 65536) Mod 256)
If intNew < 0 Then intNew = 0
If intNew > 255 Then intNew = 255
clrNew.FromArgb(intNew, intNew, intNew)
objBITMAP.SetPixel(intCoun
Next intCounterX
Next intCounterY
picDisplay.Image.Dispose()
picDisplay.Image = objBITMAP
picDisplay.Refresh()
End Sub
ASKER
Thank you Anthony for the tip off on that URL. I went there and tried to get a "Graphics" object from a PictureBox and the only successful way I could do that was to put my code in the "Paint" event of the PictureBox which passes a reference to the "Graphics" object of it. I can easily invoke the "Paint" event by calling the "Invalidate" method of the PictureBox, but I feel it's a HACK. Do you know of any other way to get a reference to the "Graphics" object of the PictureBox?
Here's the code I wrote based on the sample at the URL you specified:
__________________________ __________ __________ __________ __
Private Sub picDisplay_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.Paint EventArgs) Handles picDisplay.Paint
Dim ia As ImageAttributes = New ImageAttributes()
Dim cm As ColorMatrix = New ColorMatrix()
' 1/3 on the top 3 rows and 3 columns
cm.Matrix00 = 1 / 3
cm.Matrix01 = 1 / 3
cm.Matrix02 = 1 / 3
cm.Matrix10 = 1 / 3
cm.Matrix11 = 1 / 3
cm.Matrix12 = 1 / 3
cm.Matrix20 = 1 / 3
cm.Matrix21 = 1 / 3
cm.Matrix22 = 1 / 3
ia.SetColorMatrix(cm)
With picDisplay.Image
e.Graphics.DrawImage(picDi splay.Imag e, New Rectangle(0, 0, .Width, .Height), 0, 0, .Width, .Height, GraphicsUnit.Pixel, ia)
picDisplay.Refresh()
End With
End Sub
__________________________ __________ __________ __________ __
The problem with this code is it renders the image as grayscale on the PictureBox, but doesn't make that rendering PERMINENT!! It's just like if you set "AutoRedraw" to FALSE on a VB5 or VB6 PictureBox and do a BitBlt() to it's "hDC". It renders correctly, but if a "Paint" event is invoked or forced by covering the PictureBox, then showing it again... the newly rendered image goes away (it's not correctly repainted). In VB5 and VB6, the fix for this was simple:
"Set PictureBox.Picture = PictureBox.Image"
How can you make what you "draw" to the surface of a PictureBox in VB.NET "permiment" so it will automatically redraw?
Here's the code I wrote based on the sample at the URL you specified:
__________________________
Private Sub picDisplay_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.Paint
Dim ia As ImageAttributes = New ImageAttributes()
Dim cm As ColorMatrix = New ColorMatrix()
' 1/3 on the top 3 rows and 3 columns
cm.Matrix00 = 1 / 3
cm.Matrix01 = 1 / 3
cm.Matrix02 = 1 / 3
cm.Matrix10 = 1 / 3
cm.Matrix11 = 1 / 3
cm.Matrix12 = 1 / 3
cm.Matrix20 = 1 / 3
cm.Matrix21 = 1 / 3
cm.Matrix22 = 1 / 3
ia.SetColorMatrix(cm)
With picDisplay.Image
e.Graphics.DrawImage(picDi
picDisplay.Refresh()
End With
End Sub
__________________________
The problem with this code is it renders the image as grayscale on the PictureBox, but doesn't make that rendering PERMINENT!! It's just like if you set "AutoRedraw" to FALSE on a VB5 or VB6 PictureBox and do a BitBlt() to it's "hDC". It renders correctly, but if a "Paint" event is invoked or forced by covering the PictureBox, then showing it again... the newly rendered image goes away (it's not correctly repainted). In VB5 and VB6, the fix for this was simple:
"Set PictureBox.Picture = PictureBox.Image"
How can you make what you "draw" to the surface of a PictureBox in VB.NET "permiment" so it will automatically redraw?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I had previously tried the "CreateGraphics" method but didn't have any luck with it. I tried it again with the following code and was suprised that it actually worked:
Private objGraphics As Graphics = Nothing
objGraphics = picDisplay.CreateGraphics
However, I've got the same problem when using the "objGraphics" variable, or the Graphics object passed tothe "Paint" event. As soon as the "picDisplay" PictureBox is repainted or refreshed... the modifications I made are again gone.
As I said before, it's just like in VB 5 or 6 when you use the BitBlt() Win32 API to draw an image onto a PictureBox that has its "AutoRedraw" property turned off. There's got to be a way to tell the VB.NET PictureBox to "AutoRedraw" or something??
Kevin
Private objGraphics As Graphics = Nothing
objGraphics = picDisplay.CreateGraphics
However, I've got the same problem when using the "objGraphics" variable, or the Graphics object passed tothe "Paint" event. As soon as the "picDisplay" PictureBox is repainted or refreshed... the modifications I made are again gone.
As I said before, it's just like in VB 5 or 6 when you use the BitBlt() Win32 API to draw an image onto a PictureBox that has its "AutoRedraw" property turned off. There's got to be a way to tell the VB.NET PictureBox to "AutoRedraw" or something??
Kevin
Sorry, you've just about reached the limit of my knowledge of graphics in .NET...
ASKER
Thank you anthony_glenwright for all your help on this. I'm going to go ahead and award you the points. I'm sure I'll figure out the auto-redraw problem.
Also, I did find a WORK AROUND for the "Invalid parameter used" error I was getting. You have to create a second System.Drawing.Bitmap object from the first... then you can work with it. Like this:
__________________________ __________ __________ __________ __
Dim intCounterX As Integer
Dim intCounterY As Integer
Dim clrCurrent As System.Drawing.Color
Dim intCurrent As Integer
Dim intNew As Integer
Dim objBITMAP As System.Drawing.Bitmap
Try
If picDisplay.Image Is Nothing Then Exit Sub
' Create a Bitmap object from the current image
objBITMAP = New Bitmap(picDisplay.Image)
For intCounterY = 0 To objBITMAP.Height - 1
For intCounterX = 0 To objBITMAP.Width - 1
clrCurrent = objBITMAP.GetPixel(intCoun terX, intCounterY)
intCurrent = RGB(clrCurrent.R, clrCurrent.G, clrCurrent.B)
intNew = 0.33 * (intCurrent Mod 256) + _
0.59 * ((intCurrent \ 256) Mod 256) + _
0.11 * ((intCurrent \ 65536) Mod 256)
If intNew < 0 Then intNew = 0
If intNew > 255 Then intNew = 255
objBITMAP.SetPixel(intCoun terX, intCounterY, Color.FromArgb(intNew, intNew, intNew))
Next intCounterX
Next intCounterY
Catch objErr As Exception
MsgBox("The following error occured:" & Chr(13) & Chr(13) & _
"Error Source = " & objErr.Source & Chr(13) & _
"Error Description = " & objErr.Message, MsgBoxStyle.OKOnly Or MsgBoxStyle.Exclamation, " ")
Finally
picDisplay.Image.Dispose()
picDisplay.Image = Nothing
picDisplay.Image = objBITMAP
picDisplay.Refresh()
End Try
__________________________ __________ __________ __________ __
Kevin Wilson
TheVBZone.com
VBZ@one.net
Also, I did find a WORK AROUND for the "Invalid parameter used" error I was getting. You have to create a second System.Drawing.Bitmap object from the first... then you can work with it. Like this:
__________________________
Dim intCounterX As Integer
Dim intCounterY As Integer
Dim clrCurrent As System.Drawing.Color
Dim intCurrent As Integer
Dim intNew As Integer
Dim objBITMAP As System.Drawing.Bitmap
Try
If picDisplay.Image Is Nothing Then Exit Sub
' Create a Bitmap object from the current image
objBITMAP = New Bitmap(picDisplay.Image)
For intCounterY = 0 To objBITMAP.Height - 1
For intCounterX = 0 To objBITMAP.Width - 1
clrCurrent = objBITMAP.GetPixel(intCoun
intCurrent = RGB(clrCurrent.R, clrCurrent.G, clrCurrent.B)
intNew = 0.33 * (intCurrent Mod 256) + _
0.59 * ((intCurrent \ 256) Mod 256) + _
0.11 * ((intCurrent \ 65536) Mod 256)
If intNew < 0 Then intNew = 0
If intNew > 255 Then intNew = 255
objBITMAP.SetPixel(intCoun
Next intCounterX
Next intCounterY
Catch objErr As Exception
MsgBox("The following error occured:" & Chr(13) & Chr(13) & _
"Error Source = " & objErr.Source & Chr(13) & _
"Error Description = " & objErr.Message, MsgBoxStyle.OKOnly Or MsgBoxStyle.Exclamation, " ")
Finally
picDisplay.Image.Dispose()
picDisplay.Image = Nothing
picDisplay.Image = objBITMAP
picDisplay.Refresh()
End Try
__________________________
Kevin Wilson
TheVBZone.com
VBZ@one.net
http://www.dotnet247.com/247reference/msgs/18/93994.aspx