Replace color of a image, using Lockbits

后端 未结 2 1341
情话喂你
情话喂你 2021-01-22 07:29

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

2条回答
  •  不知归路
    2021-01-22 08:11

    There are three different problems in the code you posted:

    1. You have the color component order wrong. The 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).
    2. In VB.NET, you can't directly compare 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.
    3. Your 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.

提交回复
热议问题