Before nothing, I\'ll note that I accept a C# or VB.Net solution.
I have this old code which I\'m trying to refactor to avoid the bad habbits and performance inefficienc
There are three different problems in the code you posted:
Bitmap
class stores pixel values as integers, in little-endian format. This means that the byte order of the components is actually BGR (or BGRA for 32bpp).Color
values. I don't know enough about VB.NET to know why that is, but I assume it's a normal language behavior related to how VB.NET treats value types. To correctly compare Color
values, you need to call ToArgb()
, which returns an Integer
value, which can be compared directly.For
loop uses the wrong ending value. If you only subtract 1
from the length of the array, then it is possible for the loop to run into the padding at the end of a row, but find too few bytes to successfully add 2
to the loop index and still remain within the array.Here's a version of your extension method that works fine for me:
Public Function ChangeColor(ByVal image As Image, ByVal oldColor As Color, ByVal newColor As Color)
Dim newImage As Bitmap = New Bitmap(image.Width, image.Height, image.PixelFormat)
Using g As Graphics = Graphics.FromImage(newImage)
g.DrawImage(image, Point.Empty)
End Using
' Lock the bitmap's bits.
Dim rect As New Rectangle(0, 0, newImage.Width, newImage.Height)
Dim bmpData As BitmapData = newImage.LockBits(rect, ImageLockMode.ReadWrite, newImage.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 * newImage.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 - 3 Step 3
Dim testColor As Color = Color.FromArgb(rgbValues(i + 2), rgbValues(i + 1), rgbValues(i))
If (testColor.ToArgb() = oldColor.ToArgb()) Then
rgbValues(i) = newColor.B
rgbValues(i + 1) = newColor.G
rgbValues(i + 2) = newColor.R
End If
Next i
' Copy the RGB values back to the bitmap.
Marshal.Copy(rgbValues, 0, ptr, numBytes)
' Unlock the bits.
newImage.UnlockBits(bmpData)
Return newImage
End Function
As far as this goes:
I'm not sure how to adapt it for any source pixelformat that I can pass to the function.
Unfortunately, the API does not directly return the bits-per-pixel or bytes-per-pixel for the bitmap. You can generalize your code to take into account the number of bytes per pixel, but you'll still have to at least map the PixelFormat
value to that bytes per pixel value.