如何在C#中为位图图像应用模糊效果?

7

如何在不使用库的情况下在C#中对图像应用模糊效果?


这不是一个好主意,因为WriteableBitmap需要4个参数而不是2个。我已经尝试过这个例子了。 - Anton Lieskovsky
1
作为一个快速的hack,你可以将全尺寸位图渲染成一小部分大小,然后将其重新渲染回全尺寸位图。这只需要使用Graphics.FromImageGraphics.DrawImage即可。 - adv12
可能是在图像上添加模糊效果的重复问题。 - rene
2个回答

12

更新的代码(现在更快,需要使用UNSAFE关键字)

static void Main(string[] args)
{
    Bitmap bitmap = new Bitmap("C:\\Users\\erik\\test.png");
    bitmap = Blur(bitmap, 10);
    bitmap.Save("C:\\Users\\erik\\test2.png");
}

private static Bitmap Blur(Bitmap image, Int32 blurSize)
{
    return Blur(image, new Rectangle(0, 0, image.Width, image.Height), blurSize);
}

private unsafe static Bitmap Blur(Bitmap image, Rectangle rectangle, Int32 blurSize)
{
    Bitmap blurred = new Bitmap(image.Width, image.Height);

    // make an exact copy of the bitmap provided
    using (Graphics graphics = Graphics.FromImage(blurred))
        graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
            new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);

    // Lock the bitmap's bits
    BitmapData blurredData = blurred.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, blurred.PixelFormat);

    // Get bits per pixel for current PixelFormat
    int bitsPerPixel = Image.GetPixelFormatSize(blurred.PixelFormat);

    // Get pointer to first line
    byte* scan0 = (byte*)blurredData.Scan0.ToPointer();

    // look at every pixel in the blur rectangle
    for (int xx = rectangle.X; xx < rectangle.X + rectangle.Width; xx++)
    {
        for (int yy = rectangle.Y; yy < rectangle.Y + rectangle.Height; yy++)
        {
            int avgR = 0, avgG = 0, avgB = 0;
            int blurPixelCount = 0;

            // average the color of the red, green and blue for each pixel in the
            // blur size while making sure you don't go outside the image bounds
            for (int x = xx; (x < xx + blurSize && x < image.Width); x++)
            {
                for (int y = yy; (y < yy + blurSize && y < image.Height); y++)
                {
                    // Get pointer to RGB
                    byte* data = scan0 + y * blurredData.Stride + x * bitsPerPixel / 8;

                    avgB += data[0]; // Blue
                    avgG += data[1]; // Green
                    avgR += data[2]; // Red

                    blurPixelCount++;
                }
            }

            avgR = avgR / blurPixelCount;
            avgG = avgG / blurPixelCount;
            avgB = avgB / blurPixelCount;

            // now that we know the average for the blur size, set each pixel to that color
            for (int x = xx; x < xx + blurSize && x < image.Width && x < rectangle.Width; x++)
            {
                for (int y = yy; y < yy + blurSize && y < image.Height && y < rectangle.Height; y++)
                {
                    // Get pointer to RGB
                    byte* data = scan0 + y * blurredData.Stride + x * bitsPerPixel / 8;

                    // Change values
                    data[0] = (byte)avgB;
                    data[1] = (byte)avgG;
                    data[2] = (byte)avgR;
                }
            }
        }
    }

    // Unlock the bits
    blurred.UnlockBits(blurredData);

    return blurred;
}

处理模糊值为 10256x256 图像,共花费 2.356 秒

原始代码(来自Github - 稍有修改)

static void Main(string[] args)
{
    Bitmap bitmap = new Bitmap("C:\\Users\\erik\\test.png");

    bitmap = Blur(bitmap, 10);

    bitmap.Save("C:\\Users\\erik\\test2.png");
}

private static Bitmap Blur(Bitmap image, Int32 blurSize)
{
    return Blur(image, new Rectangle(0, 0, image.Width, image.Height), blurSize);
}

private static Bitmap Blur(Bitmap image, Rectangle rectangle, Int32 blurSize)
{
    Bitmap blurred = new Bitmap(image.Width, image.Height);

    // make an exact copy of the bitmap provided
    using (Graphics graphics = Graphics.FromImage(blurred))
        graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
            new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);

    // look at every pixel in the blur rectangle
    for (int xx = rectangle.X; xx < rectangle.X + rectangle.Width; xx++)
    {
        for (int yy = rectangle.Y; yy < rectangle.Y + rectangle.Height; yy++)
        {
            int avgR = 0, avgG = 0, avgB = 0;
            int blurPixelCount = 0;

            // average the color of the red, green and blue for each pixel in the
            // blur size while making sure you don't go outside the image bounds
            for (int x = xx; (x < xx + blurSize && x < image.Width); x++)
            {
                for (int y = yy; (y < yy + blurSize && y < image.Height); y++)
                {
                    Color pixel = blurred.GetPixel(x, y);

                    avgR += pixel.R;
                    avgG += pixel.G;
                    avgB += pixel.B;

                    blurPixelCount++;
                }
            }

            avgR = avgR / blurPixelCount;
            avgG = avgG / blurPixelCount;
            avgB = avgB / blurPixelCount;

            // now that we know the average for the blur size, set each pixel to that color
            for (int x = xx; x < xx + blurSize && x < image.Width && x < rectangle.Width; x++)
                for (int y = yy; y < yy + blurSize && y < image.Height && y < rectangle.Height; y++)
                    blurred.SetPixel(x, y, Color.FromArgb(avgR, avgG, avgB));
        }
    }

    return blurred;
}

处理一张 256x256 像素、模糊值为 10 的图片耗时 7.594 秒

原始图片

Original Image

模糊图片(模糊级别 10)

Blurred Image

图片来源: https://www.pexels.com/search/landscape/


1
事实上,我刚刚测试了一下,发现当数值大于10时,它就无法使用了。速度慢,并且会产生一些奇怪的垂直条纹伪影。对于较高的数值,高斯模糊(也许像在一些库中找到的AForge那样?)可能更适合。 - AFract
我同意。但是OP没有具体的要求。我相信这应该足够了。 - erikvimz
1
嘿嘿,我刚刚弄清楚为什么会这样。算法确实有一个漏洞!应该是Color pixel = image.GetPixel(x, y);而不是Color pixel = blurred.GetPixel(x, y); :). 一旦修复了,效果就更好了!我刚刚在原始的Github页面上发布了一条评论。 - AFract
@AFract,你的修改对我没有任何影响。我有一些空闲时间,所以我重新审视了算法,并实现了使用LockBitsUnlockBits,现在它运行得更快了。 - erikvimz
如果你看不到区别,显然你没有用我上面建议的图像进行测试。尝试使用模糊大小高达10的图像:从5开始,结果图像的差异就很明显了。我的评论是针对原始代码的,你使用位锁的版本与此无关,我还没有测试它。 - AFract
显示剩余10条评论

1
如果您正在使用XAML,请从Microsoft 检查它。同时研究这段代码,您的问题可以得到解决。

没有 XAML - 我需要它作为脚本。 - Anton Lieskovsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接