EmguExtensions 0.1.8
dotnet add package EmguExtensions --version 0.1.8
NuGet\Install-Package EmguExtensions -Version 0.1.8
<PackageReference Include="EmguExtensions" Version="0.1.8" />
<PackageVersion Include="EmguExtensions" Version="0.1.8" />
<PackageReference Include="EmguExtensions" />
paket add EmguExtensions --version 0.1.8
#r "nuget: EmguExtensions, 0.1.8"
#:package EmguExtensions@0.1.8
#addin nuget:?package=EmguExtensions&version=0.1.8
#tool nuget:?package=EmguExtensions&version=0.1.8
EmguExtensions
A high-performance .NET library that extends Emgu.CV (OpenCV wrapper) with zero-copy Mat
accessors, ROI utilities, async stream copy helpers, pluggable Mat compression, structured contour hierarchies, and
drawing helpers.
Features
- Zero-copy Mat access -
Span<T>,ReadOnlySpan<T>,Memory<T>,ReadOnlyMemory<T>,Memory2D<T>, andReadOnlyMemory2D<T>accessors for fast pixel manipulation and async-friendly buffer usage - ROI utilities -
Roi,SafeRoi,ConstrainRoi, andRoiFromCenterfor bounded region-of-interest cropping with configurable empty-ROI behavior - Image transforms – Rotate with adjusted bounds, letterbox creation, crop-by-bounds, and shrink-to-fit resizing that reports whether the image changed
- Stream copy helpers -
CopyTo(Stream)andCopyToAsync(Stream, CancellationToken)copy continuous matrices in one writing and non-continuous matrices row by row without including row padding - Mat compression - Pluggable compressors (PNG, Deflate, GZip, ZLib, Brotli, Zstd) with
CMatfor memory-efficient compressed image storage.CMat.Compressis thread-safe — concurrent calls are serialized automatically - Contour hierarchies - Structured
EmguContour,EmguContours, andEmguContourFamilywrappers for OpenCV contour trees using hierarchy links - Drawing helpers - Polygon geometry, multi-line text rendering with alignment, SVG path generation, and predefined colors
- Disposal infrastructure -
DisposableObject,LeaveOpenDisposableObject, andGCSafeHandlefor thread-safe resource management
Requirements
Installation
NuGet Package Manager
Install-Package EmguExtensions
.NET CLI
dotnet add package EmguExtensions
Quick Start
Zero-Copy Mat Access
using EmguExtensions;
using Emgu.CV;
using Emgu.CV.CvEnum;
using CommunityToolkit.HighPerformance;
// Create or load a Mat
using var mat = new Mat(100, 100, DepthType.Cv8U, 1);
// Zero-copy span access for fast pixel reads/writes
var span = mat.GetSpan<byte>();
span[0] = 255; // Set first pixel
// 2D span access
var span2D = mat.GetSpan2D<byte>();
span2D[10, 20] = 128; // Row 10, Col 20
// Memory access is useful when an API needs Memory<T> or ReadOnlyMemory<T>.
ReadOnlyMemory<byte> bytes = mat.GetReadOnlyMemoryOfBytes();
Memory<byte> writableBytes = mat.GetMemoryOfBytes();
writableBytes.Span[1] = 64;
GetSpan<T>, GetMemory<T>, and their read-only counterparts require a continuous Mat. Use the 2D accessors for ROI
views or other non-continuous matrices:
using var source = new Mat(100, 100, DepthType.Cv8U, 1);
using var roi = new Mat(source, new Rectangle(10, 10, 40, 40)); // Usually non-continuous
var memory2D = roi.GetMemory2D<byte>();
memory2D.Span[0, 0] = 255;
ReadOnlyMemory2D<byte> readOnlyRoi = source.GetReadOnlyMemory2D<byte>(
new Rectangle(10, 10, 40, 40));
byte first = readOnlyRoi.Span[0, 0];
Safe ROI Cropping
using var source = CvInvoke.Imread("image.png");
// SafeRoi clamps to mat bounds and updates the rectangle with the effective ROI.
var roiRect = new Rectangle(-10, -10, 200, 200);
using var roi = source.SafeRoi(ref roiRect);
// Padding can be applied while still keeping the ROI inside the source bounds.
var paddedRect = new Rectangle(40, 40, 120, 80);
using var paddedRoi = source.SafeRoi(ref paddedRect, padding: 16);
// ConstrainRoi only updates the rectangle and reports whether it changed.
var candidate = new Rectangle(0, 0, 500, 500);
bool changed = source.ConstrainRoi(ref candidate);
// Empty ROI behavior is explicit when a requested ROI becomes empty.
var emptyRect = Rectangle.Empty;
using var fullSource = source.SafeRoi(ref emptyRect, EmptyRoiBehavior.CaptureSource);
// Other options:
// EmptyRoiBehavior.Default -> use OpenCV's default behavior for empty ROI
// EmptyRoiBehavior.CaptureSource -> use the full source image
// EmptyRoiBehavior.ThrowException -> throw InvalidOperationException
Image Sizing and Transforms
// Create a fixed-size letterboxed image. targetWidth and targetHeight must be positive.
using var boxed = source.CreateLetterBox(640, 480, out float scale, out int padX, out int padY);
// Shrink only when the image exceeds the target bounds. Returns true when resized.
bool wasShrunk = source.ShrinkToFitPreserveAspect(1024, 768);
// Output overloads populate dst even when no resize/rotation is needed.
using var resized = new Mat();
bool resizedWasShrunk = source.ShrinkToFitPreserveAspect(resized, 1024, 768);
using var rotated = new Mat();
source.RotateAdjustBounds(rotated, angle: 30);
Copy Mat Data to Streams
using var output = File.Create("frame.raw");
// Synchronous copy.
mat.CopyTo(output);
// Async copy with cancellation support.
await using var asyncOutput = File.Create("frame-async.raw");
await mat.CopyToAsync(asyncOutput, cancellationToken);
For continuous matrices the whole data block is written in one operation. For non-continuous matrices, such as partial-width ROI views, rows are written individually, so row padding is not included.
Mat Compression with CMat
using EmguExtensions;
// Compress a Mat for memory-efficient storage
var cmat = new CMat();
cmat.Compress(mat); // Auto-selects best storage (compressed or raw)
// Decompress back to Mat — caller owns and must dispose the returned Mat
using var restored = cmat.Decompress();
// Use a specific compressor and level
cmat.Compressor = MatCompressorBrotli.Instance;
cmat.CompressionLevel = CompressionLevel.SmallestSize;
cmat.Compress(mat);
// Compress(Mat) and Compress(MatRoi) are thread-safe — safe to call from multiple threads
Parallel.ForEach(frames, (frame, _, i) =>
{
using var mat = frame.ToMat();
compressedFrames[i].Compress(mat);
});
// Switch compressor and re-encode existing data in one step
cmat.ChangeCompressor(MatCompressorDeflate.Instance, reEncodeWithNewCompressor: true);
// Equality is hash-based (XxHash3) — O(1) for mismatches, fast for collections
bool same = cmat1 == cmat2;
// Direct compressor APIs also support async compression/decompression.
byte[] compressed = await MatCompressorBrotli.Instance.CompressAsync(mat, cancellationToken);
using var decompressed = mat.New();
await MatCompressorBrotli.Instance.DecompressAsync(compressed, decompressed, cancellationToken);
Contour Hierarchy
using EmguExtensions;
// Find contours with full hierarchy
using var contours = new EmguContours(binaryImage, RetrType.Tree);
// Iterate contour families (tree roots)
foreach (var family in contours.Families)
{
Console.WriteLine($"Area: {family.Self.Area}, Children: {family.Count}");
// Even depth = solid fill; odd depth = hole/cavity
}
Drawing Helpers
using EmguExtensions;
// Generate regular polygon vertices
var hexVertices = DrawingExtensions.GetPolygonVertices(6, new SizeF(50, 50), new PointF(100, 100));
// Multi-line text with alignment
mat.PutTextExtended("Line 1\nLine 2\nLine 3",
new Point(50, 50),
FontFace.HersheyComplex,
1.0,
EmguExtensions.WhiteColor,
lineAlignment: PutTextLineAlignment.Center);
Available Compressors
| Compressor | Class | Description |
|---|---|---|
| None | MatCompressorNone |
No compression (raw bytes) |
| PNG | MatCompressorPng |
PNG image encoding via OpenCV |
| Deflate | MatCompressorDeflate |
Deflate stream compression |
| GZip | MatCompressorGZip |
GZip stream compression |
| ZLib | MatCompressorZLib |
ZLib stream compression |
| Brotli | MatCompressorBrotli |
Brotli stream compression |
| Zstd | MatCompressorZstd |
Zstandard compression (.NET 11+) |
All compressors are singletons accessed via Instance (e.g., MatCompressorBrotli.Instance). Public CompressAsync
and DecompressAsync methods call overrideable CompressCoreAsync and DecompressCoreAsync methods, so custom
compressors can provide native async behavior instead of only using the default background-thread wrapper.
CompressionLevel values are mapped to each compressor's native level range. For Brotli, NoCompression, Fastest,
Optimal, and SmallestSize map to qualities 0, 1, 4, and 11 respectively.
CMat automatically falls back to raw (uncompressed) storage when:
- The source is smaller than
ThresholdToCompress(default 512 bytes), or - Compression produces a result larger than the original.
Benchmarks
Compressor benchmark
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8246/25H2/2025Update/HudsonValley2)
AMD Ryzen 9 7845HX with Radeon Graphics 3.00GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v4
Job-MTJJIS : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v4
MaxIterationCount=16
| Method | MatSize | CompressorName | Level | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|----------- |-------- |--------------- |-------------- |------------:|------------:|------------:|---------:|---------:|---------:|----------:|
| Compress | 1920 | Brotli | NoCompression | 318.3 us | 14.55 us | 14.29 us | - | - | - | 3200 B |
| Compress | 1920 | Brotli | Fastest | 332.3 us | 54.77 us | 53.79 us | - | - | - | 2816 B |
| Compress | 1920 | None | NoCompression | 337.8 us | 11.88 us | 11.66 us | 194.3359 | 194.3359 | 194.3359 | 3686483 B |
| Compress | 1920 | Deflate | Fastest | 636.3 us | 55.72 us | 54.73 us | 1.9531 | - | - | 45744 B |
| Compress | 1920 | GZip | Fastest | 638.2 us | 28.39 us | 27.88 us | 2.4414 | - | - | 45792 B |
| Compress | 1920 | ZLib | Fastest | 640.3 us | 49.51 us | 48.62 us | 2.4414 | - | - | 45784 B |
| Compress | 1920 | GZip | NoCompression | 769.6 us | 13.23 us | 12.38 us | 276.3672 | 276.3672 | 276.3672 | 3688281 B |
| Compress | 1920 | ZLib | NoCompression | 770.1 us | 27.85 us | 27.35 us | 284.1797 | 284.1797 | 284.1797 | 3688256 B |
| Compress | 1920 | Deflate | NoCompression | 862.5 us | 14.73 us | 13.78 us | 280.2734 | 280.2734 | 280.2734 | 3688228 B |
| Compress | 1920 | Deflate | Optimal | 1,303.5 us | 42.47 us | 41.71 us | 0.9766 | - | - | 23512 B |
| Compress | 1920 | GZip | Optimal | 1,305.6 us | 93.40 us | 91.74 us | - | - | - | 23568 B |
| Compress | 1920 | ZLib | Optimal | 1,353.0 us | 67.70 us | 66.49 us | - | - | - | 23552 B |
| Compress | 1920 | Brotli | Optimal | 4,997.6 us | 181.04 us | 177.80 us | - | - | - | 1313 B |
| Compress | 1920 | GZip | SmallestSize | 8,240.1 us | 397.06 us | 389.97 us | - | - | - | 15552 B |
| Compress | 1920 | ZLib | SmallestSize | 8,380.9 us | 372.70 us | 366.04 us | - | - | - | 15536 B |
| Compress | 1920 | Deflate | SmallestSize | 8,492.4 us | 427.02 us | 419.39 us | - | - | - | 15504 B |
| Compress | 1920 | PNG | Fastest | 15,511.5 us | 1,415.48 us | 1,390.19 us | - | - | - | 25736 B |
| Compress | 1920 | PNG | NoCompression | 15,571.9 us | 654.92 us | 643.22 us | 375.0000 | 375.0000 | 375.0000 | 3694733 B |
| Compress | 1920 | PNG | Optimal | 16,101.5 us | 679.06 us | 666.93 us | - | - | - | 25496 B |
| Compress | 1920 | PNG | SmallestSize | 35,974.1 us | 2,452.16 us | 2,408.35 us | - | - | - | 9280 B |
| Compress | 1920 | Brotli | SmallestSize | 66,692.0 us | 9,677.48 us | 9,504.58 us | - | - | - | 692 B |
| | | | | | | | | | | |
| Decompress | 1920 | Deflate | SmallestSize | 113.4 us | 1.35 us | 1.19 us | - | - | - | 280 B |
| Decompress | 1920 | Deflate | Fastest | 119.7 us | 2.57 us | 2.52 us | - | - | - | 280 B |
| Decompress | 1920 | Deflate | Optimal | 123.6 us | 3.56 us | 3.50 us | - | - | - | 280 B |
| Decompress | 1920 | Deflate | NoCompression | 145.0 us | 2.05 us | 1.82 us | - | - | - | 280 B |
| Decompress | 1920 | None | NoCompression | 147.1 us | 1.93 us | 1.71 us | - | - | - | - |
| Decompress | 1920 | ZLib | Fastest | 157.1 us | 1.58 us | 1.32 us | - | - | - | 312 B |
| Decompress | 1920 | GZip | Fastest | 161.6 us | 2.30 us | 1.92 us | - | - | - | 312 B |
| Decompress | 1920 | GZip | Optimal | 162.2 us | 2.07 us | 1.93 us | - | - | - | 312 B |
| Decompress | 1920 | GZip | SmallestSize | 168.2 us | 8.01 us | 7.86 us | - | - | - | 312 B |
| Decompress | 1920 | ZLib | Optimal | 177.4 us | 1.59 us | 1.41 us | - | - | - | 312 B |
| Decompress | 1920 | GZip | NoCompression | 182.6 us | 1.61 us | 1.50 us | - | - | - | 312 B |
| Decompress | 1920 | ZLib | SmallestSize | 197.6 us | 8.23 us | 8.08 us | - | - | - | 312 B |
| Decompress | 1920 | ZLib | NoCompression | 225.5 us | 13.96 us | 13.71 us | - | - | - | 312 B |
| Decompress | 1920 | Brotli | NoCompression | 1,560.4 us | 24.39 us | 21.62 us | - | - | - | - |
| Decompress | 1920 | Brotli | Fastest | 2,107.5 us | 267.52 us | 262.74 us | - | - | - | - |
| Decompress | 1920 | Brotli | SmallestSize | 2,354.4 us | 49.89 us | 49.00 us | - | - | - | - |
| Decompress | 1920 | Brotli | Optimal | 3,384.4 us | 254.81 us | 250.26 us | - | - | - | - |
| Decompress | 1920 | PNG | Fastest | 6,079.8 us | 39.73 us | 35.22 us | - | - | - | 88 B |
| Decompress | 1920 | PNG | Optimal | 6,120.8 us | 43.09 us | 38.19 us | - | - | - | 88 B |
| Decompress | 1920 | PNG | NoCompression | 6,830.0 us | 47.14 us | 44.09 us | - | - | - | 88 B |
| Decompress | 1920 | PNG | SmallestSize | 8,797.8 us | 39.41 us | 34.94 us | - | - | - | 88 B |
Mat.ToArray() benchmark
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8246/25H2/2025Update/HudsonValley2)
AMD Ryzen 9 7845HX with Radeon Graphics 3.00GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 10.0.202
[Host] : .NET 10.0.6 (10.0.6, 10.0.626.17701), X64 RyuJIT x86-64-v4
DefaultJob : .NET 10.0.6 (10.0.6, 10.0.626.17701), X64 RyuJIT x86-64-v4
| Method | MatSize | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---------------------- |-------- |-----------:|---------:|---------:|------:|--------:|---------:|---------:|---------:|----------:|------------:|
| GetRawData | 1920 | 617.7 us | 6.08 us | 5.08 us | 0.99 | 0.04 | 999.0234 | 999.0234 | 999.0234 | 3.52 MB | 1.00 |
| GetSpan.ToArray | 1920 | 623.8 us | 10.47 us | 9.28 us | 1.00 | 0.05 | 999.0234 | 999.0234 | 999.0234 | 3.52 MB | 1.00 |
| 'ToArray (extension)' | 1920 | 626.5 us | 11.84 us | 28.15 us | 1.00 | 0.06 | 999.0234 | 999.0234 | 999.0234 | 3.52 MB | 1.00 |
| | | | | | | | | | | | |
| 'ToArray (extension)' | 3840 | 1,444.4 us | 28.81 us | 32.02 us | 1.00 | 0.03 | 500.0000 | 500.0000 | 500.0000 | 14.06 MB | 1.00 |
| GetSpan.ToArray | 3840 | 1,551.3 us | 16.07 us | 15.04 us | 1.07 | 0.02 | 500.0000 | 500.0000 | 500.0000 | 14.06 MB | 1.00 |
| GetRawData | 3840 | 1,582.3 us | 21.02 us | 19.66 us | 1.10 | 0.03 | 500.0000 | 500.0000 | 500.0000 | 14.06 MB | 1.00 |
Building from Source
# Restore and build
dotnet restore
dotnet build
# Run tests
dotnet test EmguExtensions.Tests
# Pack for NuGet (Release only)
dotnet pack --configuration Release --output .
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License – see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CommunityToolkit.HighPerformance (>= 8.4.2)
- DotNext (>= 6.3.0)
- DotNext.IO (>= 6.3.0)
- Emgu.CV (>= 4.13.0.5924)
- StageKit.Primitives (>= 0.2.2)
- System.IO.Hashing (>= 10.0.9)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on EmguExtensions:
| Package | Downloads |
|---|---|
|
UVtools.Core
MSLA/DLP, file analysis, calibration, repair, conversion and manipulation |
|
|
EmguExtensions.Avalonia
Avalonia integration for EmguExtensions: convert Emgu.CV Mat images to Avalonia WriteableBitmap with direct memory locking. |
GitHub repositories
This package is not used by any popular GitHub repositories.