C# 使用锁定位替换图像的颜色

C# 使用锁定位替换图像的颜色,c#,.net,vb.net,image-processing,bitmap,C#,.net,Vb.net,Image Processing,Bitmap,在此之前,我将注意到我接受C#或VB.Net解决方案 我有一段旧代码,我正试图对其进行重构,以避免使用GetPixel/SetPixel方法带来的不良习惯和性能低下: <Extension> Public Function ChangeColor(ByVal sender As Image, ByVal oldColor As Color, ByVal newColor

在此之前,我将注意到我接受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
扩展方法有两个问题,首先是如果pixelformat不是原始示例中的
Format24bppRgb
,那么所有问题都会出现,循环中会抛出一个
IndexOutforage
异常,我想这是因为我读取的是3个字节(RGB),而不是4个字节(ARGB),但我不确定如何将其适应于任何可以传递到函数的源像素格式

其次,如果我使用
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 
我想这是因为我读取的是3字节(RGB),而不是4字节(ARGB)

是的,这就是重点。如果要处理原始图像内容,必须使用
PixelFormat
。您必须区分索引格式(8bpp或更小),其中
位图数据中的像素不是颜色而是调色板的索引

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);
}
public void ChangeColor(位图、颜色从、颜色到)
{
if(Image.GetPixelFormatSize(bitmap.PixelFormat)>8)
{
ChangeColorHiColoredBitmap(位图、从、到);
返回;
}
int indexFrom=Array.IndexOf(bitmap.palete.Entries,from);
if(indexFrom<0)
return;//无需更改
//我们可以替换调色板中的颜色,但我们希望看到一个操作像素的示例
int indexTo=Array.IndexOf(bitmap.palete.Entries,to);
如果(指数<0)
return;//未找到目标颜色-如果需要,可以搜索最近的颜色
ChangeColorIndexedBitmap(位图、indexFrom、indexTo);
}
私有无效ChangeColorHiColoredBitmap(位图位图、颜色从、颜色到)
{
int rawFrom=from.ToArgb();
int rawTo=to.ToArgb();
BitmapData data=bitmap.LockBits(新矩形(Point.Empty,bitmap.Size),ImageLockMode.ReadWrite,bitmap.PixelFormat);
字节*行=(字节*)data.Scan0;
对于(int y=0;y>8)和0xFF);
pos[2]=(字节)((rawTo>>16)和0xFF);
}
打破
案例PixelFormat.Format32bppRgb:
案例PixelFormat.Format32bppArgb:
int c32=*((int*)行+x);
如果(c32==rawFrom)
*((int*)行+x)=rawTo;
打破
违约:
抛出new NotSupportedException();//当然,您也可以对其他像素格式执行相同的操作
}
}
line+=数据。步幅;
}
位图。解锁位(数据);
}
私有不安全void ChangeColorIndexedBitmap(位图位图、int-from、int-to)
{
int bpp=Image.GetPixelFormatSize(位图.PixelFormat);
如果(从<0 | |到<0 | | |从>=(1=(1>4==from:(小字节&0x0F)==from)
{
如果((x&1)==0)
{
半字节&=0x0F;
半字节|=(字节)(到>(x&7));
如果(to==0)
行[pos]&=(字节)~掩码;
其他的
行[pos]|=掩码;
打破
}
}
line+=数据。步幅;
}
位图。解锁位(数据);
}

您发布的代码中有三个不同的问题:

  • 颜色组件顺序错误。
    位图
    类将像素值存储为整数,采用小端格式。这意味着组件的字节顺序实际上是BGR(或BGRA表示32bpp)
  • 在VB.NET中,您不能直接比较
    Color
    值。我对VB.NET了解不够,不知道为什么会这样,但我认为这是一种与VB.NET如何处理值类型相关的正常语言行为。要正确比较
    Color
    值,需要调用
    ToArgb()
    ,返回一个可直接比较的
    整数值
  • 您的
    For
    循环使用了错误的结束值。如果仅从数组长度中减去
    1
    ,则循环可能会在行尾处插入填充,但找到的字节太少,无法成功地将
    2
    添加到循环索引中,并且仍保留在数组中
  • 下面是一个版本的扩展方法,对我来说很好:

    
    公共功能更改颜色(ByVal图像作为图像,ByVal oldColor作为颜色,ByVal newColor作为颜色)
    Dim newImage As Bitmap=新位图(image.Width、image.Height、image.PixelFormat)
    使用g作为Graphics=Graphics.FromImage(newImage)
    g、 DrawImage(图像,点。空)
    终端使用
    '锁定位图的位。
    将矩形变暗为新矩形(0,0,newImage.Width,newImage.Height)
    将bmpData调整为位图数据=newImage.LockBits(rect、ImageLockMode.ReadWrite、newImage.PixelFormat)
    '获取第一行的地址。
    尺寸ptr为IntPtr=bmpData.Scan0
    '声明一个数组以保存位图的字节。
    整数形式的Dim numBytes=(bmpData.Stride*newImage.Height)
    Dim RGB值为Byte()=新字节(numBytes-1){
    
    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);
    }