HPPH 2.0.0
dotnet add package HPPH --version 2.0.0
NuGet\Install-Package HPPH -Version 2.0.0
<PackageReference Include="HPPH" Version="2.0.0" />
<PackageVersion Include="HPPH" Version="2.0.0" />
<PackageReference Include="HPPH" />
paket add HPPH --version 2.0.0
#r "nuget: HPPH, 2.0.0"
#:package HPPH@2.0.0
#addin nuget:?package=HPPH&version=2.0.0
#tool nuget:?package=HPPH&version=2.0.0
HPPH
C# High Performance Pixel Handling
Summary
Helpers to work with 24 and 32 bit color data, optimized for performance and written fully in C#.
Most operations are allocation free and use SIMD (up to AVX-512) where available.
| Package | Description |
|---|---|
| HPPH | Core package, contains everything described below |
| HPPH.SkiaSharp | Loading of images and conversion from and to SkiaSharp images and bitmaps |
| HPPH.System.Drawing | Loading of images and conversion from and to System.Drawing bitmaps (Windows only) |
| HPPH.WPF | Loading of images and conversion from and to WPF bitmaps (Windows only) |
Supports .NET 10 and .NET 11.
Colors
| Type | Size |
|---|---|
ColorRGB, ColorBGR |
24 bit |
ColorARGB, ColorABGR, ColorRGBA, ColorBGRA |
32 bit |
All colors implement IColor<T> and expose R, G, B and A. 24 bit colors always return 255 for A.
The format of a color type is available through T.ColorFormat or the instances on IColorFormat.
ColorRGBA color = new(255, 128, 0, 255);
IColorFormat format = ColorRGBA.ColorFormat; // same as IColorFormat.RGBA
Images
Image<T> is a class holding its pixels in a byte array.
RefImage<T> is a ref struct that works on top of any span (arrays, stackalloc, native memory).
Both can be a view on a region of a bigger buffer, so Stride (bytes per row) is not always Width * BytesPerPixel.
IImage is used where the color format is only known at runtime. Image<T> is the only implementation.
Creating
// copies the data
Image<ColorRGB> image = Image<ColorRGB>.Create(colors, width, height);
Image<ColorBGRA> image2 = Image<ColorBGRA>.Create(bytes, width, height, stride);
// uses the buffer without copying, changes to it show up in the image
Image<ColorBGRA> wrapped = Image<ColorBGRA>.Wrap(bytes, width, height, stride);
RefImage<ColorBGRA> refImage = RefImage<ColorBGRA>.Wrap(span, width, height, stride);
// color format only known at runtime
IImage image3 = format.CreateImage(bytes, width, height, stride);
IImage wrapped2 = format.WrapImage(bytes, width, height, stride);
Accessing pixels
ColorRGB pixel = image[x, y];
foreach (ColorRGB color in image) { } // row by row
foreach (ImageRow<ColorRGB> row in image.Rows) { } // row.AsSpan() for a ReadOnlySpan<ColorRGB>
foreach (ImageColumn<ColorRGB> column in image.Columns) { }
ColorRGB[] firstRow = image.Rows[0].ToArray();
// the memory of the image, for example to pass it on with its stride
ReadOnlySpan<byte> bytes = image.GetBytes(); // from the first pixel to the end of the last row, includes padding
Regions
Regions share the data of the image, nothing is copied.
A slice keeps the whole buffer of its image alive. Use ToImage to get an independent copy, for example before storing a small region of a big image.
RefImage<ColorRGB> region = image[10, 10, 100, 50]; // allocation free
Image<ColorRGB> slice = image.Slice(10, 10, 100, 50); // can be stored in a field
Image<ColorRGB> copy = region.ToImage(); // independent copy
Image<ColorRGB> copy2 = slice.ToImage(); // same for slices
Copying and converting
ColorRGB[] pixels = image.ToArray();
byte[] raw = image.ToRawArray(); // rows without padding
image.CopyTo(buffer);
image.CopyTo(buffer, stride); // rows are written stride bytes apart
Image<ColorBGRA> bgra = image.ConvertTo<ColorBGRA>();
image.ConvertTo<ColorBGRA>(buffer, stride);
Comparing
Equals checks for reference equality. Use ContentEquals to compare format, size and pixels. Row padding is ignored.
bool equal = image.ContentEquals(other);
Custom operations
To work with an IImage without knowing its color format, implement IImageOperation<TResult> and pass it to Execute.
readonly struct MaxRed : IImageOperation<byte>
{
public byte Execute<T>(RefImage<T> image) where T : unmanaged, IColor<T>
{
byte max = 0;
foreach (T color in image)
max = Math.Max(max, color.R);
return max;
}
}
byte maxRed = image.Execute<MaxRed, byte>(default); // no allocations
byte maxRed2 = image.Execute(new MaxRed()); // shorter, but boxes struct operations
Image<T> and RefImage<T> offer the same Execute methods.
Operations
All operations are extension methods in PixelHelper.
Sum, average, min-max and palettes work on Image<T>, RefImage<T>, IImage and ReadOnlySpan<T>. Sorting and conversion work on spans.
Flip, rotate and downscale work on Image<T>, RefImage<T> and IImage. They return a new image or write into a buffer you provide.
Sum
Sums up each channel into a long.
ColorSum sum = image.Sum();
long red = sum.R;
Average
ColorRGB average = image.Average();
IColor average2 = someIImage.Average();
Min-Max
Gets the minimum and maximum of each channel.
ColorMinMax minMax = image.MinMax();
byte redRange = minMax.RedRange; // RedMax - RedMin
Sort
Sorts in place by a single channel. The sort is stable.
Span<ColorRGB> colors = image.ToArray();
colors.SortByRed();
colors.SortByGreen();
colors.SortByBlue();
colors.SortByAlpha();
Color palettes
Reduces the colors to a palette of the given size.
ColorRGB[] palette = image.CreateColorPalette(16);
// pixelCounts[i] is the number of pixels represented by palette[i]
Span<long> pixelCounts = stackalloc long[16];
ColorRGB[] palette2 = image.CreateColorPalette(16, pixelCounts);
CreateColorPalette uses Wu's algorithm. It's higher quality and in most cases faster than median cut.
To pick one explicitly use CreateWuColorPalette or CreateMedianCutColorPalette (palette size has to be a power of 2).
- Splits are only done on red, green and blue, alpha is averaged.
- Palettes are ordered by the number of pixels each color represents (most common first).
- Palettes never contain duplicates. If there aren't enough distinct colors, the palette is smaller than requested.
Conversion
All formats can be converted into each other.
Span<ColorRGB> rgb = image.ToArray();
ColorBGRA[] bgra = rgb.Convert<ColorRGB, ColorBGRA>();
rgb.Convert(bgraBuffer);
// only for formats of the same size
Span<ColorBGR> bgr = rgb.ConvertInPlace<ColorRGB, ColorBGR>();
Conversion is mostly limited by memory bandwidth. Writing to freshly allocated memory often costs more than the conversion itself, so reuse buffers where possible.
Flip
Mirrors an image. Horizontal swaps left and right, Vertical swaps top and bottom.
Image<ColorRGB> mirrored = image.Flip(ImageFlip.Horizontal);
Image<ColorRGB> upsideDown = image.Flip(ImageFlip.Horizontal | ImageFlip.Vertical);
image.Flip(ImageFlip.Vertical, buffer); // Span<ColorRGB>, rows without padding
image.Flip(ImageFlip.Vertical, bytes, stride); // rows are written stride bytes apart
The destination can't overlap the memory of the image.
Rotate
Rotates an image clockwise by 90, 180 or 270 degrees. For 90 and 270 degrees width and height are swapped.
Image<ColorRGB> rotated = image.Rotate(ImageRotation.Rotate90); // image.Height x image.Width
image.Rotate(ImageRotation.Rotate270, buffer); // Span<ColorRGB>, rows without padding
image.Rotate(ImageRotation.Rotate270, bytes, image.Height * 3); // the stride is for the rotated rows
The destination can't overlap the memory of the image.
Downscale
Shrinks an image by an integer factor: each pixel of the result is the average of a block of factor x factor pixels, rounded like Average.
Columns and rows at the edges that don't fill a whole block are left out. The result is at least 1 x 1 pixel.
Image<ColorBGRA> half = image.Downscale(2); // image.Width / 2 x image.Height / 2
Image<ColorBGRA> wide = image.Downscale(4, 2); // factors for x and y
ColorBGRA average = image.Downscale(int.MaxValue)[0, 0]; // same as image.Average()
(int width, int height) = PixelHelper.GetDownscaledSize(image.Width, image.Height, 2);
image.Downscale(2, bytes, width * 4);
The destination can't overlap the memory of the image.
SkiaSharp, System.Drawing and WPF
Loading files and converting from and to their image types:
Image<ColorBGRA> image = ImageHelper.LoadImage("image.png");
using SKImage skImage = image.ToSKImage();
See HPPH.SkiaSharp, HPPH.System.Drawing and HPPH.WPF 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. net11.0 is compatible. |
-
net10.0
- No dependencies.
-
net11.0
- No dependencies.
NuGet packages (6)
Showing the top 5 NuGet packages that depend on HPPH:
| Package | Downloads |
|---|---|
|
RGB.NET.Presets
Presets-Presets of RGB.NET |
|
|
ScreenCapture.NET
Core functionality for Screen-Capturing |
|
|
StableDiffusion.NET
Stable-Diffusion for .NET based on stable-diffusion.cpp |
|
|
HPPH.SkiaSharp
SkiaSharp extensions for HPPH |
|
|
HPPH.System.Drawing
System.Drawing extensions for HPPH |
GitHub repositories (3)
Showing the top 3 popular GitHub repositories that depend on HPPH:
| Repository | Stars |
|---|---|
|
DarthAffe/RGB.NET
The one-stop SDK for RGB-peripherals
|
|
|
DarthAffe/StableDiffusion.NET
C# Wrapper for StableDiffusion.cpp
|
|
|
DarthAffe/ScreenCapture.NET
Fast and easy to use screen-capturing
|