Link to home
Start Free TrialLog in
Avatar of HATCHET
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(intCounterX, 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(intCounterX, 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(intCounterX, intCounterY, clrNew)
      Next intCounterX
   Next intCounterY
   
   picDisplay.Image.Dispose()
   picDisplay.Image = objBITMAP
   picDisplay.Refresh()
End Sub
Avatar of anthony_glenwright
anthony_glenwright
Flag of Australia image

Avatar of HATCHET
HATCHET

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.PaintEventArgs) 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(picDisplay.Image, 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?
ASKER CERTIFIED SOLUTION
Avatar of anthony_glenwright
anthony_glenwright
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of HATCHET

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
Sorry, you've just about reached the limit of my knowledge of graphics in .NET...

Avatar of HATCHET

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(intCounterX, 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(intCounterX, 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