在圖形處理和繪製任務中,選擇合適的圖形庫對於應用效能至關重要。本文將對比四大流行圖形庫——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
}