圖形繪相簿效能對比:GDI+、OpenCV、ImageSharp 與 SkiaSharp

阿遇而已發表於2024-12-07

在圖形處理和繪製任務中,選擇合適的圖形庫對於應用效能至關重要。本文將對比四大流行圖形庫——GDI+、OpenCV、ImageSharp 和 SkiaSharp——在繪製任務中的效能表現。我們開展了一組相同條件下的效能測試,以全面瞭解它們在大規模圖形繪製中的效率和表現。

程式碼會貼在最後

測試環境

測試的膝上型電腦CPU為Ultra 7 155H 32G記憶體。每個庫均被用來在 20,000 x 20,000 畫素的空白畫布上隨機繪製大量簡單圖形。這些圖形包括矩形、圓形和三角形,每個圖形的大小固定為 50x50 畫素。我們主要關注每個庫完成任務所需的時間。

平臺介紹

1. GDI+

  • 概述: GDI+ 是 Windows 平臺上的原生圖形庫,適合 GUI 應用程式的圖形處理。
  • 應用場景: 適用於需要與 Windows 系統緊密整合的應用,如傳統桌面應用程式。

2. OpenCV

  • 概述: OpenCV 是一個開源計算機視覺庫,但也支援基本的圖形繪製功能。
  • 應用場景: 適合需要高階影像處理和計算機視覺功能的應用。

3. ImageSharp

  • 概述: ImageSharp 是一個跨平臺的 .NET 圖形處理庫,以其簡單的 API 和高效的處理能力而聞名。
  • 應用場景: 適合需要跨平臺影像處理的應用,特別是在影像操作複雜度較高的情況下。

4. SkiaSharp

  • 概述: SkiaSharp 是由 Google 開發的 Skia 引擎的 .NET 封裝,提供硬體加速的2D圖形處理能力。
  • 應用場景: 適合需要高效能渲染的應用,如遊戲和複雜 UI 應用。

效能對比

GDI Drawing
GDI+ Drawing Nums:160000
GDI+ Drawing finished in 6754 ms.
OpenCV Drawing
OpenCV Drawing Nums:160000
OpenCv Drawing finished in 4663 ms.
ImageSharp Drawing
ImageSharp Drawing Nums:160000
ImageSharp Drawing finished in 19922 ms.
SkiaSharp Drawing
SkiaSharp Drawing Nums:160000
SkiaSharp Drawing finished in 12115 ms.

總結

選擇合適的圖形庫應基於專案的特定需求和目標平臺。如需高效能,OpenCV 會是首選;在需要跨平臺相容性時,ImageSharp,SkiaSharp 是不錯的選擇,而 GDI+ 則適合與 Windows 應用緊密整合的場景。

透過對於這四個圖形庫的效能對比,我們可以更清晰地根據特定專案需求選擇合適的工具,以實現最佳效能和使用者體驗。

using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Numerics;
using OpenCvSharp;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using Color = System.Drawing.Color;
using CvPoint = OpenCvSharp.Point;
using GdiPointF = System.Drawing.PointF;
using GdiPen = System.Drawing.Pen;

using Pens = SixLabors.ImageSharp.Drawing.Processing.Pens;

namespace CSharpApiLearn.StaticMethods;

public class DrawingMethod
{
    public static void GDIDrawing()
    {
        Console.WriteLine("GDI Drawing");
        
        Stopwatch stopwatch = Stopwatch.StartNew();
        
        Bitmap bitmap = new Bitmap(20000, 20000);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            // 設定高質量繪製
            g.SmoothingMode = SmoothingMode.AntiAlias;

            // 圖形的基礎尺寸(這裡設定矩形邊長、圓形直徑、三角形邊長都為相同值,可根據需求調整)
            int shapeSize = 50;
            // 水平和垂直方向的間距(可以設定為0使圖形緊密相連,也可根據需求留一點間隔)
            int horizontalSpacing = 0;
            int verticalSpacing = 0;

            // 計算水平方向可容納的圖形數量(考慮間距)
            int horizontalCount = (bitmap.Width + horizontalSpacing) / (shapeSize + horizontalSpacing);
            // 計算垂直方向可容納的圖形數量(考慮間距)
            int verticalCount = (bitmap.Height + verticalSpacing) / (shapeSize + verticalSpacing);
            
            Console.WriteLine($"GDI+ Drawing Nums:{horizontalCount * verticalCount}");
            
            Random random = new Random();
            for (int row = 0; row < verticalCount; row++)
            {
                for (int col = 0; col < horizontalCount; col++)
                {
                    // 隨機確定圖形型別(0表示矩形,1表示圓形,2表示三角形)
                    int shapeType = random.Next(3);

                    // 根據行列計算圖形的位置
                    int x = col * (shapeSize + horizontalSpacing);
                    int y = row * (shapeSize + verticalSpacing);

                    if (shapeType == 0)
                    {
                        // 繪製矩形
                        DrawRectangle(g, x, y, shapeSize, shapeSize);
                    }
                    else if (shapeType == 1)
                    {
                        // 繪製圓形
                        DrawCircle(g, x + shapeSize / 2, y + shapeSize / 2, shapeSize / 2);
                    }
                    else
                    {
                        // 繪製三角形
                        DrawTriangle(g, x, y, shapeSize);
                    }
                }
            }
        }
        bitmap.Save(@"D:\GdiOutput.png", System.Drawing.Imaging.ImageFormat.Png);
        Console.WriteLine($"GDI+ Drawing finished in {stopwatch.ElapsedMilliseconds} ms.");
        bitmap.Dispose();
    }

    public static void OpenCvDrawing()
    {
        Console.WriteLine("OpenCV Drawing");
        
        Stopwatch stopwatch = Stopwatch.StartNew();
        
        Mat image = new Mat(20000, 20000, MatType.CV_8UC4, new Scalar(0, 0, 0, 0));

        // 圖形的基礎尺寸(這裡設定矩形邊長、圓形直徑、三角形邊長都為相同值,可根據需求調整)
        int shapeSize = 50;
        // 水平和垂直方向的間距(可以設定為0使圖形緊密相連,也可根據需求留一點間隔)
        int horizontalSpacing = 0;
        int verticalSpacing = 0;

        // 計算水平方向可容納的圖形數量(考慮間距)
        int horizontalCount = (image.Cols + horizontalSpacing) / (shapeSize + horizontalSpacing);
        // 計算垂直方向可容納的圖形數量(考慮間距)
        int verticalCount = (image.Rows + verticalSpacing) / (shapeSize + verticalSpacing);
        
        Console.WriteLine($"OpenCV Drawing Nums:{horizontalCount * verticalCount}");

        Random random = new Random();
        for (int row = 0; row < verticalCount; row++)
        {
            for (int col = 0; col < horizontalCount; col++)
            {
                // 隨機確定圖形型別(0表示矩形,1表示圓形,2表示三角形)
                int shapeType = random.Next(3);

                // 根據行列計算圖形的位置
                int x = col * (shapeSize + horizontalSpacing);
                int y = row * (shapeSize + verticalSpacing);

                if (shapeType == 0)
                {
                    // 繪製矩形
                    CvDrawRectangle(image, x, y, shapeSize, shapeSize);
                }
                else if (shapeType == 1)
                {
                    // 繪製圓形
                    CvDrawCircle(image, x + shapeSize / 2, y + shapeSize / 2, shapeSize / 2);
                }
                else
                {
                    // 繪製三角形
                    CvDrawTriangle(image, x, y, shapeSize);
                }
            }
        }

        // 將繪製好的影像儲存到D盤(這裡指定檔名為output.png,可根據需求修改)
        string filePath = @"D:\OpenCvOutput.png";
        Cv2.ImWrite(filePath, image);
        
        Console.WriteLine($"OpenCv Drawing finished in {stopwatch.ElapsedMilliseconds} ms.");
        image.Dispose();
    }

    public static void ImageSharpDrawing()
    {
        Console.WriteLine("ImageSharp Drawing");

            Stopwatch stopwatch = Stopwatch.StartNew();

            // Create a new image with specified dimensions
            using var image = new SixLabors.ImageSharp.Image<Rgba32>(20000, 20000);

            // Set up options for drawing (e.g., anti-aliasing)
            var drawingOptions = new DrawingOptions() { GraphicsOptions = { Antialias = true } };

            // Define the pen
            var pen = Pens.Solid(SixLabors.ImageSharp.Color.Black, 2);

            // Base shape dimension and spacing
            int shapeSize = 50;
            int horizontalSpacing = 0;
            int verticalSpacing = 0;

            // Calculate how many shapes fit in the image dimensions
            int horizontalCount = (image.Width + horizontalSpacing) / (shapeSize + horizontalSpacing);
            int verticalCount = (image.Height + verticalSpacing) / (shapeSize + verticalSpacing);
            
            Console.WriteLine($"ImageSharp Drawing Nums:{horizontalCount * verticalCount}");

            Random random = new Random();
            for (int row = 0; row < verticalCount; row++)
            {
                for (int col = 0; col < horizontalCount; col++)
                {
                    // Determine shape type randomly
                    int shapeType = random.Next(3);

                    // Determine positioning
                    int x = col * (shapeSize + horizontalSpacing);
                    int y = row * (shapeSize + verticalSpacing);

                    if (shapeType == 0)
                    {
                        // Rectangle
                        image.Mutate(ctx => ctx.Draw(drawingOptions, pen, new SixLabors.ImageSharp.Rectangle(x, y, shapeSize, shapeSize)));
                    }
                    else if (shapeType == 1)
                    {
                        // Circle
                        image.Mutate(ctx => ctx.Draw(drawingOptions, pen, new EllipsePolygon(new Vector2(x + shapeSize / 2, y + shapeSize / 2), shapeSize / 2)));
                    }
                    else
                    {
                        // Triangle
                        var triangle = new Polygon(new LinearLineSegment(
                            new SixLabors.ImageSharp.PointF(x, y),
                            new SixLabors.ImageSharp.PointF(x + shapeSize, y),
                            new SixLabors.ImageSharp.PointF(x + shapeSize / 2, y + (float)(shapeSize * Math.Sqrt(3) / 2))
                        ));
                        image.Mutate(ctx => ctx.Draw(drawingOptions, pen, triangle));
                    }
                }
            }

            // Save the image
            image.SaveAsPng(@"D:\ImageSharpOutput.png");

            Console.WriteLine($"ImageSharp Drawing finished in {stopwatch.ElapsedMilliseconds} ms.");
    }
    
    public static void SkiaSharpDrawing()
    {
        Console.WriteLine("SkiaSharp Drawing");

        Stopwatch stopwatch = Stopwatch.StartNew();

        const int width = 20000;
        const int height = 20000;
        using var surface = SKSurface.Create(new SKImageInfo(width, height));
        var canvas = surface.Canvas;

        // Clear the canvas
        canvas.Clear(SKColors.White);

        // Set up paint
        var paint = new SKPaint
        {
            Color = SKColors.Black,
            IsAntialias = true,
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 2
        };

        // Base shape dimension and spacing
        int shapeSize = 50;
        int horizontalSpacing = 0;
        int verticalSpacing = 0;

        // Calculate how many shapes fit in the image dimensions
        int horizontalCount = (width + horizontalSpacing) / (shapeSize + horizontalSpacing);
        int verticalCount = (height + verticalSpacing) / (shapeSize + verticalSpacing);
        
        Console.WriteLine($"SkiaSharp Drawing Nums:{horizontalCount * verticalCount}");

        Random random = new Random();
        for (int row = 0; row < verticalCount; row++)
        {
            for (int col = 0; col < horizontalCount; col++)
            {
                // Determine shape type randomly
                int shapeType = random.Next(3);

                // Determine positioning
                int x = col * (shapeSize + horizontalSpacing);
                int y = row * (shapeSize + verticalSpacing);

                if (shapeType == 0)
                {
                    // Rectangle
                    canvas.DrawRect(x, y, shapeSize, shapeSize, paint);
                }
                else if (shapeType == 1)
                {
                    // Circle
                    canvas.DrawCircle(x + shapeSize / 2, y + shapeSize / 2, shapeSize / 2, paint);
                }
                else
                {
                    // Triangle
                    var path = new SKPath();
                    path.MoveTo(x, y);
                    path.LineTo(x + shapeSize, y);
                    path.LineTo(x + shapeSize / 2, y + (int)(shapeSize * Math.Sqrt(3) / 2));
                    path.Close();
                    canvas.DrawPath(path, paint);
                }
            }
        }

        // Save the image to a file
        using var image = surface.Snapshot();
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        using var stream = System.IO.File.OpenWrite(@"D:\SkiaSharpOutput.png");
        data.SaveTo(stream);

        Console.WriteLine($"SkiaSharp Drawing finished in {stopwatch.ElapsedMilliseconds} ms.");
    }
    
    public static void DrawingBenchmark()
    {
        GDIDrawing();
        OpenCvDrawing();
        ImageSharpDrawing();
        SkiaSharpDrawing();
    }
    
    #region GDI+ Tools

    private static void DrawRectangle(Graphics g, int x, int y, int width, int height)
    {
        GdiPen pen = new GdiPen(Color.Black, 2);
        g.DrawRectangle(pen, x, y, width, height);
    }

    private static void DrawCircle(Graphics g, int x, int y, int radius)
    {
        GdiPen pen = new GdiPen(Color.Black, 2);
        g.DrawEllipse(pen, x - radius, y - radius, radius * 2, radius * 2);
    }

    private static void DrawTriangle(Graphics g, int x, int y, int sideLength)
    {
        GdiPointF[] points = new GdiPointF[3];
        points[0] = new GdiPointF(x, y);
        points[1] = new GdiPointF(x + sideLength, y);
        points[2] = new GdiPointF(x + sideLength / 2, y + (float)(sideLength * Math.Sqrt(3) / 2));

        GdiPen pen = new GdiPen(Color.Black, 2);
        g.DrawPolygon(pen, points);
    }

    #endregion

    #region OpenCV Tools

    public static void CvDrawRectangle(Mat image, int x, int y, int width, int height)
    {
        // 設定矩形顏色為黑色(在OpenCV中顏色順序是BGR,這裡用Scalar表示)
        Scalar color = new Scalar(0, 0, 0, 255);
        // 線寬設為2
        int thickness = 2;
        Cv2.Rectangle(image, new CvPoint(x, y), new CvPoint(x + width, y + height), color, thickness);
    }

    public static void CvDrawCircle(Mat image, int x, int y, int radius)
    {
        Scalar color = new Scalar(0, 0, 0, 255);
        int thickness = 2;
        Cv2.Circle(image, new CvPoint(x, y), radius, color, thickness);
    }

    public static void CvDrawTriangle(Mat image, int x, int y, int sideLength)
    {
        Scalar color = new Scalar(0, 0, 0, 255);
        int thickness = 2;

        CvPoint[] points = new CvPoint[4];
        points[0] = new CvPoint(x, y);
        points[1] = new CvPoint(x + sideLength, y);
        points[2] = new CvPoint(x + sideLength / 2, y + (int)(sideLength * Math.Sqrt(3) / 2));
        points[3] = new CvPoint(x, y);

        // 使用FillPoly來繪製填充的多邊形(這裡是三角形),如果不需要填充,可使用PolyLines並設定合適引數
        Cv2.Polylines(image, new CvPoint[][] { points }, false, color, thickness, LineTypes.Link8);
    }

    #endregion
}

相關文章