在没有任何事情之前,我会注意到我接受了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 后,我尝试根据自己的需要调整代码,使用{{ 1}}作为参数而不是字节序列(因为本质上是相同的):
Color
我对扩展方法有两个问题,首先是如果pixelformat不是<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
作为原始示例那么一切都出错了,循环中抛出Format24bppRgb
异常,我想这是因为我读的是3个字节(RGB)而不是4个(ARGB),但是我不知道如何调整它以适应我可以传递给函数的任何源像素格式。
其次是如果我使用IndexOutOfRange
作为原始C#示例,则颜色会变为黑色。
请注意,我不确定我链接的C#问题中给出的原始解决方案是否错误,因为根据他们的评论似乎在某种程度上是错误的。
这就是我尝试使用它的方式:
Format24bppRgb
答案 0 :(得分:2)
我想这是因为我读了3个字节(RGB)而不是4个(ARGB)
是的,这就是重点。如果您想操纵原始图像内容,则必须依赖PixelFormat
。并且您必须区分索引格式(8bpp或更少),其中BitmapData
中的像素不是颜色,而是调色板的索引。
public void ChangeColor(Bitmap bitmap, Color from, Color to)
{
if (Image.GetPixelFormatSize(bitmap.PixelFormat) > 8)
{
ChangeColorHiColoredBitmap(bitmap, from, to);
return;
}
int indexFrom = Array.IndexOf(bitmap.Palette.Entries, from);
if (indexFrom < 0)
return; // nothing to change
// we could replace the color in the palette but we want to see an example for manipulating the pixels
int indexTo = Array.IndexOf(bitmap.Palette.Entries, to);
if (indexTo < 0)
return; // destination color not found - you can search for the nearest color if you want
ChangeColorIndexedBitmap(bitmap, indexFrom, indexTo);
}
private unsafe void ChangeColorHiColoredBitmap(Bitmap bitmap, Color from, Color to)
{
int rawFrom = from.ToArgb();
int rawTo = to.ToArgb();
BitmapData data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadWrite, bitmap.PixelFormat);
byte* line = (byte*)data.Scan0;
for (int y = 0; y < data.Height; y++)
{
for (int x = 0; x < data.Width; x++)
{
switch (data.PixelFormat)
{
case PixelFormat.Format24bppRgb:
byte* pos = line + x * 3;
int c24 = Color.FromArgb(pos[0], pos[1], pos[2]).ToArgb();
if (c24 == rawFrom)
{
pos[0] = (byte)(rawTo & 0xFF);
pos[1] = (byte)((rawTo >> 8) & 0xFF);
pos[2] = (byte)((rawTo >> 16) & 0xFF);
}
break;
case PixelFormat.Format32bppRgb:
case PixelFormat.Format32bppArgb:
int c32 = *((int*)line + x);
if (c32 == rawFrom)
*((int*)line + x) = rawTo;
break;
default:
throw new NotSupportedException(); // of course, you can do the same for other pixelformats, too
}
}
line += data.Stride;
}
bitmap.UnlockBits(data);
}
private unsafe void ChangeColorIndexedBitmap(Bitmap bitmap, int from, int to)
{
int bpp = Image.GetPixelFormatSize(bitmap.PixelFormat);
if (from < 0 || to < 0 || from >= (1 << bpp) || to >= (1 << bpp))
throw new ArgumentOutOfRangeException();
if (from == to)
return;
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, bitmap.Size),
ImageLockMode.ReadWrite,
bitmap.PixelFormat);
byte* line = (byte*)data.Scan0;
// scanning through the lines
for (int y = 0; y < data.Height; y++)
{
// scanning through the pixels within the line
for (int x = 0; x < data.Width; x++)
{
switch (bpp)
{
case 8:
if (line[x] == from)
line[x] = (byte)to;
break;
case 4:
// First pixel is the high nibble. From and To indices are 0..16
byte nibbles = line[x / 2];
if ((x & 1) == 0 ? nibbles >> 4 == from : (nibbles & 0x0F) == from)
{
if ((x & 1) == 0)
{
nibbles &= 0x0F;
nibbles |= (byte)(to << 4);
}
else
{
nibbles &= 0xF0;
nibbles |= (byte)to;
}
line[x / 2] = nibbles;
}
break;
case 1:
// First pixel is MSB. From and To are 0 or 1.
int pos = x / 8;
byte mask = (byte)(128 >> (x & 7));
if (to == 0)
line[pos] &= (byte)~mask;
else
line[pos] |= mask;
break;
}
}
line += data.Stride;
}
bitmap.UnlockBits(data);
}
答案 1 :(得分:1)
您发布的代码中存在三个不同的问题:
Bitmap
类以little-endian格式将像素值存储为整数。这意味着组件的字节顺序实际上是BGR(或32bpp的BGRA)。Color
值。我对VB.NET知之甚少知之甚少,但我认为这是与VB.NET如何处理值类型相关的正常语言行为。要正确比较Color
值,您需要调用ToArgb()
,它会返回Integer
值,可以直接比较。For
循环使用错误的结束值。如果你只从数组的长度中减去1
,那么循环可能会在行的末尾遇到填充,但找到的字节太少,无法成功地将2
添加到循环索引仍然保留在数组中。以下是您的扩展方法的一个版本,对我来说很合适:
<Extension>
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
就此而言:
我不确定如何使其适应我可以传递给函数的任何源像素格式。
不幸的是,API不会直接返回位图的每像素位数或每像素字节数。您可以概括代码以考虑每个像素的字节数,但您仍然必须至少将PixelFormat
值映射到每个像素值的字节数。