图形绘图库性能对比:GDI+、OpenCV、ImageSharp 与 SkiaSharp

在图形处理和绘制任务中,选择合适的图形库对于应用性能至关重要。本文将对比四大流行图形库——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
}
posted @   阿遇而已  阅读(583)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示