在此之前,我会注意到我会接受C#或VB .NET解决方案.
我有这段旧代码,我正在尝试重构它,以避免使用GetPixel
/SetPixel
方法的坏习惯和性能低效:
<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
因此,在阅读了使用LockBits
方法的投票最多的解决方案**here**之后,我试图根据需要修改代码,使用Color
作为参数,而不是字节序列(因为它们本质上是相同的):
<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
扩展方法有两个问题:第一个是如果pixelformat不是原始示例中的Format24bppRgb
,则所有操作都会出错:在循环中抛出了一个“IndexOutOfRange”异常。我想这是因为我阅读了3个字节(RGB)而不是4个字节(ARGB),但是我不确定如何使它适应任何可以传递给函数的源像素格式。
第二个问题是,如果我按照最初的C#示例使用Format24bppRgb
,颜色将变为黑色。
注意,我不确定我链接到的C#问题中给出的原始解决方案是否是错误的,因为根据他们的评论,似乎在某种程度上是错误的。
这是我尝试使用它的方式:
' 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
2条答案
按热度按时间brgchamk1#
我想这是因为我阅读的是3字节(RGB)而不是4字节(ARGB)
是的,这就是问题的关键。如果你想处理原始图像内容,你必须依赖
PixelFormat
。你必须区分索引格式(8bpp或更少),BitmapData
中的像素不是颜色,而是调色板的索引。dgenwo3n2#
您发布的代码中有三个不同的问题:
1.颜色组件顺序错误。
Bitmap
类以little-endian格式将像素值存储为整数。这意味着组件的字节顺序实际上是BGR(或32 bpp的BGRA)。1.在VB .NET中,你不能直接比较
Color
值。我对VB .NET的了解不够,不知道为什么会这样,但我认为这是与VB .NET如何处理值类型相关的正常语言行为。要正确比较Color
值,你需要调用ToArgb()
,它返回一个Integer
值,可以直接比较。1.您的
For
循环使用了错误的结束值。如果仅从数组长度中减去1
,则循环可能会遇到行尾的填充,但发现字节太少,无法成功地将2
添加到循环索引并仍保留在数组中。下面是一个对我来说很好用的扩展方法:
就这一点而言:
我不知道如何适应任何源像素格式,我可以传递给函数。
不幸的是,API不会直接返回位图的每像素位数或每像素字节数,您可以泛化代码以考虑每像素字节数,但仍必须至少将
PixelFormat
值Map到每像素字节数。