Before nothing, I'll note that I'll accept C# or VB.NET solution.
I have this old code which I'm trying to refactor to avoid the bad habits and performance inefficiency of using GetPixel
/SetPixel
methods:
<Extension>
Public Function ChangeColor(ByVal sender As Image,
ByVal oldColor As Color,
ByVal newColor As Color) As Image
Dim bmp As New Bitmap(sender.Width, sender.Height, sender.PixelFormat)
Dim x As Integer = 0
Dim y As Integer = 0
While (x < bmp.Width)
y = 0
While y < bmp.Height
If DirectCast(sender, Bitmap).GetPixel(x, y) = oldColor Then
bmp.SetPixel(x, y, newColor)
End If
Math.Max(Threading.Interlocked.Increment(y), y - 1)
End While
Math.Max(Threading.Interlocked.Increment(x), x - 1)
End While
Return bmp
End Function
So, after reading the most voted solution here using LockBits
approach, I'm trying to adapt the code to my needs, to use a Color
as parameter instead of a sequence of bytes (because in essence they are the same):
<Extension>
Public Function ChangeColor(ByVal sender As Image,
ByVal oldColor As Color,
ByVal newColor As Color) As Image
Dim bmp As Bitmap = DirectCast(sender.Clone, Bitmap)
' Lock the bitmap's bits.
Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
Dim bmpData As BitmapData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat)
' Get the address of the first line.
Dim ptr As IntPtr = bmpData.Scan0
' Declare an array to hold the bytes of the bitmap.
Dim numBytes As Integer = (bmpData.Stride * bmp.Height)
Dim rgbValues As Byte() = New Byte(numBytes - 1) {}
' Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, numBytes)
' Manipulate the bitmap.
For i As Integer = 0 To rgbValues.Length - 1 Step 3
If (Color.FromArgb(rgbValues(i), rgbValues(i + 1), rgbValues(i + 2)) = oldColor) Then
rgbValues(i) = newColor.R
rgbValues(i + 1) = newColor.G
rgbValues(i + 2) = newColor.B
End If
Next i
' Copy the RGB values back to the bitmap.
Marshal.Copy(rgbValues, 0, ptr, numBytes)
' Unlock the bits.
bmp.UnlockBits(bmpData)
Return bmp
End Function
I have two problems with the extension method: the first is that if pixelformat is not Format24bppRgb
as the original example then all goes wrong: an "IndexOutOfRange" exception is thrown in the loop. I suppose this is because I'm reading 3 bytes (RGB) instead of 4 (ARGB), but I'm not sure how to adapt it for any source pixelformat that I can pass to the function.
The second is that if I use Format24bppRgb
as per the original C# example, the color changes to black.
Note that I'm not sure if the original solution given in the C# question that I linked to is wrong, because as per their comments it seems it is wrong in some way.
This is the way I'm trying to use it:
' This function creates a bitmap of a solid color.
Dim srcImg As Bitmap = ImageUtil.CreateSolidcolorBitmap(New Size(256, 256), Color.Red)
Dim modImg As Image = srcImg.ChangeColor(Color.Red, Color.Blue)
PictureBox1.BackgroundImage = srcImg
PictureBox2.BackgroundImage = modImg