2015-11-06 120 views
2

在没有任何事情之前,我会注意到我接受了C#或VB.Net解决方案。用Lockbits替换图像的颜色

我有这样的老代码,我试图重构避免使用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 

因此,使用阅读的是投票的解决方案hereLockBits的做法,我想代码适应我的需要,使用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 

我有两个扩展方法的问题,首先是,如果像素格式不是Format24bppRgb作为原始示例,然后全部出错,在循环中引发IndexOutOfRange异常,我想这是因为我正在读取3个字节( RGB)而不是4(ARGB),但我不知道如何适应它可以传递给函数的任何源像素格式。

Secondlly的是,如果我使用Format24bppRgb为原来的C#示例,颜色变为黑色。

请注意,我不知道,如果在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

我想这是因为我读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); 
} 
+0

所以我添加了1,4,8,24和32 bpp像素格式的示例。当然,您也可以计算15/16/64和其他格式的颜色。我在这里使用了不安全的方法,但是您可以使用'Marshal.Copy'通过使用托管数组来复制数据。 – taffer

1

有你发布的代码三种不同的问题:

  1. 你有颜色分量顺序不对。 Bitmap类以小尾数格式将像素值存储为整数。这意味着组件的字节顺序实际上是BGR(或者32bpp的BGRA)。
  2. 在VB.NET中,不能直接比较Color的值。我对VB.NET知之甚少,不知道为什么,但我认为这是一种与VB.NET如何处理值类型有关的正常语言行为。要正确比较Color值,您需要拨打ToArgb(),该值返回Integer的值,可以直接比较该值。
  3. 您的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值映射到每像素值的字节数。

+1

谢谢你的回答!在这种情况下,我将评估一些像素格式,然后为未知像素格式引发NotImplemented异常。您能否提一下,或者提供一个url(来自维基百科?MSDN或其他来源)在哪里知道每个pixelpixel像素的每个像素?现在我有:'PixelFormat.Format24bppRgb = 3,PixelFormat.Format32bppArgb = 4,PixelFormat.Format32bppRgb = 4' – ElektroStudios

+0

似乎在WPF中我们可以获得每像素的位数:https://msdn.microsoft.com/ EN-US /库/ system.windows.media.pixelformat.bitsperpixel%28V = vs.110%29.aspx – ElektroStudios