Skip to content

Getting Started

Four concrete scenarios, each ~3 minutes.


Terminal window
dotnet add package Pragmatic.Imaging

The NuGet includes the native pragmatic_native binary for supported RIDs. See native-deployment.md for the platform matrix.


Load a JPEG, resize to thumbnail dimensions, convert to WebP.

using Pragmatic.Imaging;
using var pipe = ImagePipeline.Load(File.ReadAllBytes("photo.jpg"));
using var output = File.Create("photo-thumb.webp");
pipe.Thumbnail(maxWidth: 400, maxHeight: 400)
.EncodeTo(output, ImageFormat.Webp, quality: 80);

Thumbnail preserves aspect ratio and fits the longest side within the bounding box. For an exact fixed size, use Resize(width, height).


Convert a photo to a grayscale sharpened version.

using var pipe = ImagePipeline.Load(bytes);
pipe.Grayscale()
.Sharpen(sigma: 1.5f, threshold: 2)
.Brightness(10) // nudge brighter
.Contrast(1.1f)
.EncodeTo(output, ImageFormat.Png);

See operations.md for all filter parameters.


Scenario 3 — inspect an upload before decoding

Section titled “Scenario 3 — inspect an upload before decoding”

A user uploads an image. Validate dimensions and format before running anything expensive.

using Pragmatic.Imaging;
using var upload = Request.Body;
var info = ImageInfo.FromStream(upload, maxBytes: 65_536);
if (info.Width > 8000 || info.Height > 8000)
return BadRequest("Image too large");
if (info.Format is not (ImageFormat.Jpeg or ImageFormat.Png or ImageFormat.Webp))
return BadRequest("Unsupported format");
upload.Position = 0; // ImageInfo only read the header
using var pipe = ImagePipeline.Load(upload, new ImagingOptions
{
MaxWidth = 8000,
MaxHeight = 8000,
AllowedFormats = ImageFormat.Jpeg | ImageFormat.Png | ImageFormat.Webp,
});
// ... proceed with processing

ImageInfo.FromStream reads only enough of the stream to identify the header — typically the first few KB. The caller is responsible for rewinding the stream before decoding.


Process a folder of images with bounded concurrency.

using Pragmatic.Imaging;
var inputs = Directory.GetFiles("photos", "*.jpg");
var batch = new ImageBatch(maxConcurrency: 4);
await batch.ProcessAsync(inputs, async (path, ct) =>
{
var bytes = await File.ReadAllBytesAsync(path, ct);
using var pipe = ImagePipeline.Load(bytes);
var thumbPath = Path.ChangeExtension(path, ".thumb.webp");
using var output = File.Create(thumbPath);
pipe.Thumbnail(256, 256).EncodeTo(output, ImageFormat.Webp, quality: 75);
});

ImageBatch.ProcessAsync caps how many pipelines are alive at once — adjust maxConcurrency based on the memory ceiling you can afford. On a typical server, 4-8 is a safe starting point.


No pipeline needed — QR generation is a direct call.

using var output = File.Create("qr.png");
QrCode.GeneratePng(
text: "https://pragmaticdesign.net",
output: output,
moduleSize: 10,
margin: 2);

moduleSize is the pixel size of each QR module; margin is the quiet zone in modules. The output is always PNG — thread-safe, no options object needed.


Always pass ImagingOptions for untrusted input:

var safe = new ImagingOptions
{
MaxWidth = 4096,
MaxHeight = 4096,
MaxDecodedBytes = 100 * 1024 * 1024,
AllowedFormats = ImageFormat.Jpeg | ImageFormat.Png | ImageFormat.Webp,
};
using var pipe = ImagePipeline.Load(userUpload, safe);

Defaults are conservative but not adversarial-hardened — configure for your workload.

For HTTP responses, pipe directly to the response body:

[HttpGet("/thumbnail")]
public async Task ThumbnailAsync(CancellationToken ct)
{
var bytes = await GetImageAsync(ct);
using var pipe = ImagePipeline.Load(bytes);
Response.ContentType = "image/webp";
pipe.Thumbnail(400, 400).EncodeTo(Response.Body, ImageFormat.Webp);
}