C# 快速 RGB 轉灰階

Three Ways to Grayscale

1. GetPixel/SetPixel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static Bitmap RGBtoGray(Bitmap srcbmp)
{
Bitmap bitmap = new Bitmap(bmpSrc);
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
int gray = (
bitmap.GetPixel(x, y).R +
bitmap.GetPixel(x, y).G +
bitmap.GetPixel(x, y).B) / 3;
Color color = Color.FromArgb(gray, gray, gray);
bitmap.SetPixel(x, y, color);
}
}
return bitmap;
}

2. unsafe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static Bitmap RGBtoGray(Bitmap srcbmp)
{
Bitmap bitmap = new Bitmap(bmpSrc);
BitmapData bmpData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite,
bitmap.PixelFormat);

unsafe
{
int stride = bmpData.Stride;
int offset = stride - bitmap.Width * 3;

IntPtr intPtr = bmpData.Scan0;
byte* p = (byte*)(void*)intPtr;
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
byte gray = (byte)((
p[y * (bitmap.Width * 3 + offset) + (x * 3) + 0] +
p[y * (bitmap.Width * 3 + offset) + (x * 3) + 1] +
p[y * (bitmap.Width * 3 + offset) + (x * 3) + 2]) / 3);
for (int i = 0; i < 3; i++)
{
p[y * (bitmap.Width * 3 + offset) + (x * 3) + i] = gray;
}
}
}
}

bitmap.UnlockBits(bmpData);
return bitmap;
}

3. Graphics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static Bitmap RGBtoGray(Bitmap bmpSrc)
{
ColorMatrix cm = new ColorMatrix(new float[][]
{
new float[] { 0.30f, 0.30f, 0.30f, 0.00f, 0.00f } ,
new float[] { 0.59f, 0.59f, 0.59f, 0.00f, 0.00f } ,
new float[] { 0.11f, 0.11f, 0.11f, 0.00f, 0.00f } ,
new float[] { 0.00f, 0.00f, 0.00f, 1.00f, 0.00f } ,
new float[] { 0.00f, 0.00f, 0.00f, 0.00f, 1.00f }
});
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(cm);

Bitmap bitmap = new Bitmap(bmpSrc.Width, bmpSrc.Height, bmpSrc.PixelFormat);
Graphics g = Graphics.FromImage(bitmap);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
g.DrawImage(bmpSrc, rect, 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, ia);
return bitmap;
}

Performance Analysis and Evaluation

  • 硬體環境

    • CPU: AMD Ryzen 7 1700
    • RAM: Kingston DDR4-2400 16G x2
    • MB : MSI B350 GAMING PRO CARBON
    • VGA: NVIDIA Geforce GTX 750 2GB-GDDR5
    • SSD: Crucial MX300 525G
  • 測試圖片

  • 測試方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    static void Main(string[] args)
    {
    Stopwatch sw = new Stopwatch();
    List<long> records = new List<long>() { 0, 0, 0 };

    Bitmap bitmap = new Bitmap("input.bmp");
    Console.WriteLine("========== Bitmap Info ==========");
    Console.WriteLine(
    $"Filename\t{ "input.bmp"} " + Environment.NewLine +
    $"Palette \t{ Image.GetPixelFormatSize(bitmap.PixelFormat) }" + Environment.NewLine +
    $"Width \t{ bitmap.Height }" + Environment.NewLine +
    $"Height \t{ bitmap.Width }" + Environment.NewLine);

    for (int i = 0; i < 10; i++)
    {
    Console.WriteLine($"========== Count { i } ==========");

    sw = Stopwatch.StartNew();
    RGBtoGray1(new Bitmap("input.bmp"));
    sw.Stop();
    records[0] += sw.ElapsedMilliseconds;
    Console.WriteLine(string.Format("Method 1 cost {0,5} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw = Stopwatch.StartNew();
    RGBtoGray2(new Bitmap("input.bmp"));
    sw.Stop();
    records[1] += sw.ElapsedMilliseconds;
    Console.WriteLine(string.Format("Method 2 cost {0,5} milliseconds", sw.ElapsedMilliseconds));

    sw = Stopwatch.StartNew();
    RGBtoGray3(new Bitmap("input.bmp"));
    sw.Stop();
    records[2] += sw.ElapsedMilliseconds;
    Console.WriteLine(string.Format("Method 3 cost {0,5} milliseconds", sw.ElapsedMilliseconds));

    Console.WriteLine();
    }

    Console.WriteLine("========== Average ==========");
    Console.WriteLine(string.Format("Method 1: {0, 7} milliseconds", (float)records[0] / 10));
    Console.WriteLine(string.Format("Method 2: {0, 7} milliseconds", (float)records[1] / 10));
    Console.WriteLine(string.Format("Method 3: {0, 7} milliseconds", (float)records[2] / 10));

    Console.ReadKey();
    }
  • 實測數據