QuarkCanvas.WPF
1.1.4
dotnet add package QuarkCanvas.WPF --version 1.1.4
NuGet\Install-Package QuarkCanvas.WPF -Version 1.1.4
<PackageReference Include="QuarkCanvas.WPF" Version="1.1.4" />
<PackageVersion Include="QuarkCanvas.WPF" Version="1.1.4" />
<PackageReference Include="QuarkCanvas.WPF" />
paket add QuarkCanvas.WPF --version 1.1.4
#r "nuget: QuarkCanvas.WPF, 1.1.4"
#:package QuarkCanvas.WPF@1.1.4
#addin nuget:?package=QuarkCanvas.WPF&version=1.1.4
#tool nuget:?package=QuarkCanvas.WPF&version=1.1.4
QuarkCanvas
QuarkCanvas.WPF 是一个基于 WPF + SkiaSharp 的图像 / ROI / Shape 显示控件库,QuarkDemo 提供了对应的交互示例。
当前版本重点能力:
- 图像显示:支持
byte[]、SKBitmap、BitmapSource、ImageSource、WriteableBitmap、文件路径、Stream、RGB 平面指针。 - ROI / Shape 显示:支持点、线、圆、椭圆、矩形、Polygon、文本、圆弧、扇形等。
- 交互绘制:支持异步进入绘制模式,返回
RoiDisplayBase。 - 视图控制:支持
FitToView、SetPart、导出当前画布、独立弹窗显示。 - Domain 掩膜:图像显示入口支持在赋值前按 run-length 区域将域外像素置黑。
快速开始
<qc:Canvas x:Name="canvas" />
using SharpBoxesCore.DataStruct.Structure;
canvas.DisplayImage(@"D:\Images\sample.png");
canvas.Display(new Circle(200, 200, 60), "circle_1");
canvas.Display(new Point(200, 200), "center");
canvas.FitToView();
Domain 掩膜图像显示
以下图像 API 全部支持相同的 Domain 掩膜参数:
bool isPaintDomain = false,
List<double> rows = null,
List<double> startCols = null,
List<double> endCols = null
当 isPaintDomain == true 且 rows/startCols/endCols 均不为空、长度相等时,会在赋值前先把域外像素置黑,再交给现有显示 / 缩放 / 降采样逻辑。
支持像素格式:
Gray8Bgr24Rgb24Pbgra32Bgra32Bgr32
从文件加载后再套 Domain 掩膜
var bitmap = new BitmapImage(new Uri(@"D:\Images\sample.png"));
var writableBitmap = new WriteableBitmap(bitmap);
var rows = new List<double> { 100, 101, 102 };
var startCols = new List<double> { 150, 148, 146 };
var endCols = new List<double> { 320, 322, 324 };
canvas.ClearWindow();
canvas.DisplayDirectWritableBitmap(
writableBitmap,
isPaintDomain: true,
rows: rows,
startCols: startCols,
endCols: endCols);
canvas.FitToView();
内存说明
DisplayImage(...)会生成用于显示的位图对象,原图与显示图短时间内可能同时存在。DisplayWritableBitmap(...)会生成掩膜后的中间位图,再转成SKBitmap显示。DisplayDirectWritableBitmap(...)直接挂接WriteableBitmap,更适合后续频繁更新。- 有两份内存同时存在是正常峰值,不等于内存泄漏;旧对象不再被引用后会由 GC/Dispose 回收。
Canvas 公开 API
基础属性
CanvasId:当前画布实例唯一标识。CurrentTag1/CurrentTag2:预留标签对象,可绑定业务上下文。CurrentCanvasPoint:当前鼠标对应的画布坐标。CurrentImagePoint:当前鼠标对应的图像坐标。HoveredRoi:当前悬停 ROI。SelectedRoi:当前选中 ROI。
显示单个 Shape / ROI
Display(IShapeStructure shape, string name = "")AttachCircle(string name, Circle circle)AttachEllipse(string name, Ellipse ellipse)AttachRing(string name, RingRoiStructure ring)AttachLine(string name, Line line)AttachArc(string name, float startX, float startY, float middleX, float middleY, float endX, float endY)AttachCircleSector(string name, float radius, float centerX, float centerY, float startDegree, float endDegree)AttachEllipse2DSector(string name, float radiusX, float radiusY, float centerX, float centerY, float rotationDegree, float startDegree, float endDegree)AttachPolygon(string name, Polygon polygon)AttachRect1(string name, Rectangle1D rect1D)AttachRect2(string name, Rectangle2D rect2D)AttachObject(IDisplayable obj)DetachObject(string name)
canvas.Display(new Point(120, 80), "pt_1");
canvas.Display(new Line(10, 10, 200, 100), "line_1");
canvas.Display(new Circle(200, 200, 60), "circle_1");
canvas.Display(new RingRoiStructure(260, 220, 90, 45), "ring_1");
canvas.AttachArc("arc_1", 100, 100, 180, 60, 240, 140);
canvas.AttachRing("ring_attach_1", new RingRoiStructure(420, 240, 100, 50));
canvas.AttachCircleSector("sector_1", 80, 300, 200, 20, 140);
canvas.AttachEllipse2DSector("ellipse_sector_1", 120, 60, 500, 300, 30, 0, 110);
批量显示
Displays<T>(List<T> shapes, string name = "") where T : IShapeStructureDisplays(List<Polygon> polygons, string name = "")
var shapes = new List<IShapeStructure>
{
new Point(100, 100),
new Circle(200, 200, 40),
new Rectangle1D(300, 120, 160, 90),
new QuarkCanvas.WPF.Displays.Others.Text("Hello", new Point(400, 80))
};
canvas.Displays(shapes, "mixed_shapes");
var polygons = new List<Polygon>
{
new Polygon(new List<Point>
{
new Point(10, 10),
new Point(100, 10),
new Point(100, 80),
new Point(10, 80)
}, true)
};
canvas.Displays(polygons, "polygon_batch");
图像显示
DisplayImage(byte[] bytes, ...)DisplayImage(SKBitmap bitmap, ...)DisplayImage(IntPtr rPtr, IntPtr gPtr, IntPtr bPtr, int width, int height, int stride, byte alpha = 255, ...)DisplayImage(BitmapSource bitmap, ...)DisplayImage(ImageSource imageSource, ...)DisplayWritableBitmap(WriteableBitmap writableBitmap, ...)DisplayDirectWritableBitmap(WriteableBitmap writableBitmap, ...)UpdateDirectWritableBitmap()DisplayImage(string imageFilePath, ...)DisplayImage(Stream stream, ...)
canvas.DisplayImage(File.ReadAllBytes(@"D:\Images\sample.png"));
canvas.DisplayImage(@"D:\Images\sample.png");
using var stream = File.OpenRead(@"D:\Images\sample.png");
canvas.DisplayImage(stream);
canvas.FitToView();
文字 / 标注辅助显示
DisplayCross(Point center, float angle, float size, string name = "")DisplayButtonText(string name, string text, int size, int x, int y, int width, int height, Color? textColor = null, Color? backGroundColor = null, float cornerRadius = 4, float padding = 8)DisplayMarkRectText(string name, float left, float top, float right, float bottom, string text, float fontSize = 16f, Color? textColor = null, Color? backgroundColor = null, Color? strokeColor = null, float strokeWidth = 2f, bool isBold = true, TextPosition textPosition = TextPosition.TopOutside)
canvas.DisplayCross(new Point(320, 240), angle: 45, size: 60, name: "cross_1");
canvas.DisplayMarkRectText(
name: "mark_1",
left: 100,
top: 120,
right: 260,
bottom: 220,
text: "NG",
fontSize: 20);
绘制模式 API
DrawCircleAsync()DrawEllipseAsync()DrawRingAsync()DrawLineAsync()DrawArcAsync()DrawCircleSectorAsync()DrawEllipse2DSectorAsync()DrawPolygonAsync()DrawRectangle2DAsync()DrawRectangleAsync()ConfirmDrawMod()CancelDrawMod()
var roi = await canvas.DrawPolygonAsync();
if (roi != null)
{
var data = roi.GetImageData();
}
var ringRoi = await canvas.DrawRingAsync();
if (ringRoi != null)
{
var ringData = ringRoi.GetImageData();
}
画布控制与导出
ClearCheckerboardBackground()ClearShapes()ClearWindow()Invalidate()FitToView()FlushOff()FlushOn()DumpWindow()ExportCanvasFitToSize(int outputWidth = 0, int outputHeight = 0)
canvas.DisplayImage(@"D:\Images\sample.png");
canvas.FitToView();
var export = canvas.ExportCanvasFitToSize(1920, 1080);
var currentView = await canvas.DumpWindow();
ROI 查询 API
GetAllRoiDatas()GetAllRois()GetRoi(string name)GetRoiData(string name)
var allRois = canvas.GetAllRois();
var allShapeDatas = canvas.GetAllRoiDatas();
var roi = canvas.GetRoi("circle_1");
var roiData = canvas.GetRoiData("circle_1");
样式与视口 API
SetAnchorSize(int anchorSize)SetCheckerboardBackground(float cellSize = 20f, SKColor? color1 = null, SKColor? color2 = null)SetFillColor(Color color)SetFillOpacity(int opacity)SetPart(float row1, float column1, float row2, float column2)SetPart(Rectangle1D roiRect)SetPart(int row1, int column1, int row2, int column2)SetStrokeColor(Color color, int lineWidth)SetStrokeOpacity(int opacity)
canvas.SetStrokeColor(Colors.Lime, 2);
canvas.SetFillColor(Colors.Red);
canvas.SetFillOpacity(80);
canvas.SetAnchorSize(12);
canvas.SetPart(100, 200, 500, 900);
Canvas 公开属性
EnableMoveContent:是否允许拖拽平移内容。EnableWheelZoom:是否允许滚轮缩放。MaxImageRenderSize:大图显示时的采样阈值。OriginalViewportSwitchThreshold:切回原始分辨率显示的缩放阈值。MenuBarFontSize:顶部菜单栏字号。MenuBarVisible:顶部菜单栏显隐。ShowFixedCrosshair:是否显示固定十字准星。ShowMouseCrosshair:是否显示跟随鼠标的十字准星。StatusBarFontSize:底部状态栏字号。StatusBarVisible:底部状态栏显隐。
canvas.EnableMoveContent = true;
canvas.EnableWheelZoom = true;
canvas.MaxImageRenderSize = 4096;
canvas.OriginalViewportSwitchThreshold = 1.25;
canvas.MenuBarVisible = false;
canvas.StatusBarVisible = true;
canvas.ShowMouseCrosshair = true;
Shape 数据类型介绍
Canvas.Display(...) 与 Canvas.Displays<T>(...) 依赖 SharpBoxesCore.DataStruct.Structure.IShapeStructure。
SharpBoxesCore 常用 Shape
Point
表示单个点,常用于定位点、顶点集合。
var point = new Point(100, 120);
canvas.Display(point, "point_1");
Line
由起点和终点构成,适合边、测量线、方向线。
var line = new Line(10, 20, 200, 80);
canvas.Display(line, "line_1");
Circle
圆心 + 半径。
var circle = new Circle(300, 220, 50);
canvas.Display(circle, "circle_1");
Ellipse
中心、长短半轴、旋转角。
var ellipse = new Ellipse(400, 260, 120, 60, 30);
canvas.Display(ellipse, "ellipse_1");
Polygon
点集 + IsClosed,适用于轮廓、区域边界、不规则 ROI。
var polygon = new Polygon(
new List<Point>
{
new Point(100, 100),
new Point(180, 120),
new Point(160, 200),
new Point(90, 180)
},
true);
canvas.Display(polygon, "polygon_1");
Rectangle1D
轴对齐矩形,常用为 X / Y / Width / Height,也可直接用于 SetPart(Rectangle1D)。
var rect1 = new Rectangle1D(120, 80, 240, 160);
canvas.Display(rect1, "rect1_1");
canvas.SetPart(rect1);
Rectangle2D
旋转矩形表达。
var rect2 = new Rectangle2D(
new Point(300, 300),
100,
100,
30);
canvas.Display(rect2, "rect2_1");
QuarkCanvas 扩展 Shape
QuarkCanvas.WPF.Displays.Others.Text
文本 Shape,主要属性:
ContentPositionColorFontSizeBackgroundColorBorderRadiusAngle
var text = new QuarkCanvas.WPF.Displays.Others.Text(
"缺陷 A",
new Point(200, 60))
{
Color = Colors.Yellow,
BackgroundColor = Colors.DarkRed,
FontSize = 18,
BorderRadius = 6,
Angle = 0
};
canvas.Display(text, "text_1");
ArcRoiStructure
三点圆弧结构,主要属性:StartX、StartY、MiddleX、MiddleY、EndX、EndY。
CircleSectorRoiStructure
圆扇形结构,主要属性:Radius、CenterX、CenterY、StartDegree、EndDegree。
Ellipse2DSectorRoiStructure
椭圆扇形结构,主要属性:RadiusX、RadiusY、CenterX、CenterY、RotationDegree、StartDegree、EndDegree。
DirectWriteableBitmapDisplay
适合自己维护 WriteableBitmap 像素内容,并希望后续频繁刷新的场景。
公开成员:
DirectWriteableBitmapDisplay()DirectWriteableBitmapDisplay(WriteableBitmap writableBitmap, Guid canvasOwner, bool isPaintDomain = false, List<double> rows = null, List<double> startCols = null, List<double> endCols = null)AttachWritableBitmap(WriteableBitmap writableBitmap)ConfigurePaintDomain(bool isPaintDomain, List<double> rows = null, List<double> startCols = null, List<double> endCols = null)Invalidate()Dispose()
var display = new DirectWriteableBitmapDisplay(
writableBitmap,
canvas.CanvasId,
isPaintDomain: true,
rows: rows,
startCols: startCols,
endCols: endCols);
canvas.AttachObject(display);
canvas.Invalidate();
WindowHelper
OpenWindow(int width, int height, int x, int y, out Canvas canvas, out Window window)OpenWindowFitImage(BitmapSource bitmapSource, int x, int y, out Canvas canvas, out Window window)
WindowHelper.OpenWindow(1000, 800, 100, 100, out var tempCanvas, out var win);
tempCanvas.DisplayImage(@"D:\Images\sample.png");
tempCanvas.FitToView();
var bitmap = new BitmapImage(new Uri(@"D:\Images\sample.png"));
WindowHelper.OpenWindowFitImage(bitmap, 80, 80, out var tempCanvas, out var win);
使用建议
- 单次加载静态图像:优先
DisplayImage(...)。 - 频繁刷新像素:优先
DisplayDirectWritableBitmap(...)。 - 单个或少量 ROI:用
Display(...)/AttachXxx(...)。 - 大量 Shape:优先
Displays<T>(...)或Displays(List<Polygon>)批量合并。 - 局部查看:用
SetPart(...)。 - 整体查看:用
FitToView()。
依赖
- .NET Framework 4.8+ 或 .NET 6.0+
- SkiaSharp
- SkiaSharp.Views.WPF
- SharpBoxesCore
License
MIT License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0-windows10.0.19041 is compatible. net7.0-windows was computed. net8.0-windows was computed. net8.0-windows10.0.19041 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
| .NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- SharpBoxesCore (>= 1.1.4.8)
- SkiaSharp (>= 3.119.2)
- SkiaSharp.Views.WPF (>= 3.119.2)
-
net6.0-windows10.0.19041
- SharpBoxesCore (>= 1.1.4.8)
- SkiaSharp (>= 3.119.2)
- SkiaSharp.Views.WPF (>= 3.119.2)
-
net8.0-windows10.0.19041
- SharpBoxesCore (>= 1.1.4.8)
- SkiaSharp (>= 3.119.2)
- SkiaSharp.Views.WPF (>= 3.119.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.