Skip to content

Imaging

Granit.Imaging provides a fluent image processing pipeline for transformations such as resize, crop, compress, format conversion, and watermarking. The pipeline strips EXIF metadata by default to prevent GPS coordinates and camera details leaking via uploaded photos (GDPR). The Magick.NET implementation supports JPEG, PNG, WebP, AVIF, GIF, BMP, and TIFF.

  • DirectoryGranit.Imaging/ Image processing abstractions, fluent pipeline API
    • Granit.Imaging.MagickNet Magick.NET implementation (JPEG/PNG/WebP/AVIF/GIF/BMP/TIFF)
PackageRoleDepends on
Granit.ImagingIImageProcessor, IImagePipeline fluent APIGranit.Core
Granit.Imaging.MagickNetMagickNetImageProcessor (singleton)Granit.Imaging
[DependsOn(typeof(GranitImagingMagickNetModule))]
public class AppModule : GranitModule { }
builder.AddGranitImagingMagickNet();

MagickNetImageProcessor is registered as a singleton — Magick.NET is thread-safe and initializes its native codec once per process.

IImageProcessor.Load() opens an image and returns an IImagePipeline. The pipeline holds native resources and must be disposed after use:

public class PatientPhotoProcessor(IImageProcessor imageProcessor)
{
public async Task<ImageResult> CreateThumbnailAsync(
Stream sourceImage,
CancellationToken cancellationToken)
{
await using IImagePipeline pipeline = imageProcessor.Load(sourceImage);
return await pipeline
.Resize(200, 200, ResizeMode.Crop)
.StripMetadata()
.Compress(quality: 80)
.ConvertTo(ImageFormat.WebP)
.ToResultAsync(cancellationToken);
}
}
MethodDescription
Resize(width, height, mode)Resize with configurable strategy
Crop(rectangle)Crop to rectangular region
Compress(quality)Set output quality (0-100)
ConvertTo(format)Change output format
Watermark(data, position, opacity)Composite watermark overlay
StripMetadata()Remove EXIF, IPTC, XMP metadata (GDPR)
ToResultAsync()Terminal: encode and return ImageResult
SaveToStreamAsync(stream)Terminal: encode and write to stream
ModeBehavior
MaxFit within bounds, preserving aspect ratio (may be smaller than target)
PadFit within bounds with transparent padding to exact dimensions
CropFill exact dimensions by resizing and center-cropping overflow
StretchStretch to exact dimensions (distorts aspect ratio)
MinResize to minimum bounds covering target dimensions entirely
await pipeline.SaveAsWebPAsync(cancellationToken); // ConvertTo(WebP) + ToResultAsync
await pipeline.SaveAsAvifAsync(cancellationToken); // ConvertTo(AVIF) + ToResultAsync
await pipeline.SaveAsJpegAsync(cancellationToken); // ConvertTo(JPEG) + ToResultAsync
await pipeline.SaveAsPngAsync(cancellationToken); // ConvertTo(PNG) + ToResultAsync
FormatReadWriteNotes
JPEGYesYesLossy, no transparency
PNGYesYesLossless, transparency
WebPYesYesModern lossy/lossless, smaller than JPEG
AVIFYesYesNext-gen, best compression ratio
GIFYesYes256 colors, animation support
BMPYesYesUncompressed bitmap
TIFFYesYesLossless, medical imaging

StripMetadata() removes all EXIF, IPTC, and XMP metadata from images. This is critical for GDPR compliance: uploaded photos often contain GPS coordinates, camera serial numbers, timestamps, and other personally identifiable information.

await using IImagePipeline pipeline = imageProcessor.Load(uploadedPhoto);
ImageResult sanitized = await pipeline
.StripMetadata()
.SaveAsWebPAsync(cancellationToken);

A typical pattern: subscribe to BlobValidated events to post-process images asynchronously after upload validation. The Wolverine handler downloads the validated blob, applies the image pipeline, and re-uploads the result:

public static class BlobValidatedHandler
{
public static async Task HandleAsync(
BlobValidated @event,
IBlobStorage blobStorage,
IImageProcessor imageProcessor,
CancellationToken cancellationToken)
{
if (@event.ContainerName != "patient-photos")
return;
PresignedDownloadUrl download = await blobStorage
.CreateDownloadUrlAsync("patient-photos", @event.BlobId,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
// fetch image from download.Url, process with imageProcessor,
// re-upload thumbnail to "patient-photos-thumbs" container
}
}

This keeps the upload path fast (202 Accepted) while image processing runs in the background. See Blob Storage domain events for the full event list.

CategoryKey typesPackage
ModuleGranitImagingModule, GranitImagingMagickNetModule
ProcessorIImageProcessor, IImagePipeline, ImageResultGranit.Imaging
FormatsImageFormat, ImageSizeGranit.Imaging
ModesResizeMode, CropRectangle, WatermarkPositionGranit.Imaging
ExtensionsAddGranitImagingMagickNet()Granit.Imaging.MagickNet