ImageResize 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package ImageResize --version 1.1.0
                    
NuGet\Install-Package ImageResize -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ImageResize" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ImageResize" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="ImageResize" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ImageResize --version 1.1.0
                    
#r "nuget: ImageResize, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ImageResize@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ImageResize&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=ImageResize&version=1.1.0
                    
Install as a Cake Tool

ImageResize

A minimal, cross-platform image resize middleware for .NET Core that provides a drop-in replacement for some common ImageSharp functionality. Built with SkiaSharp for fast, reliable image processing across Windows, Linux, and macOS.

NuGet License: MIT .NET

Important: Make no mistake, this is not a full replacement for ImageSharp and I'm sure it's faster/better than this. This is a 'vibe' coded project I did with cursor to test the new grok code fast model, and I haven't tested it extensively. I have only tested it my own projects and the Example project within this solution - So any help or bugs, please free to do a PR.

Features

  • Querystring-based resizing: ?width=800&height=600&quality=80
  • Aspect ratio preservation: Always fits within specified dimensions
  • Multiple formats: JPEG, PNG, WebP, GIF (first frame), BMP, TIFF (first page)
  • Disk caching: Atomic writes with configurable sharding and size management
  • HTTP caching: ETags, Last-Modified, Cache-Control headers
  • Concurrency safe: Prevents thundering herd with keyed locks
  • Security: Path traversal protection and bounds validation
  • Backend support: Extensible codec architecture (SkiaSharp, future backends)
  • ImageSharp Compatibility Layer: Drop-in replacement for common ImageSharp operations
  • OSS-friendly: MIT licensed with no commercial restrictions

Installation

dotnet add package ImageResize

Quick Start

Program.cs

using ImageResize.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Simple setup with automatic defaults
builder.Services.AddImageResize(builder.Environment);

var app = builder.Build();

app.UseImageResize(); // Before UseStaticFiles and before routing app.UseRouting() etc...
app.UseStaticFiles();

app.Run();

Advanced Configuration (Optional)

builder.Services.AddImageResize(o =>
{
    o.RequestPathPrefix = "/media";
    o.ContentRoot = Path.Combine(builder.Environment.WebRootPath, "images");
    o.CacheRoot = Path.Combine(builder.Environment.WebRootPath, "_imgcache");
    o.AllowUpscale = false;
    o.DefaultQuality = 85;
    o.PngCompressionLevel = 6;
    o.Backend = ImageBackend.SkiaSharp;
    o.Cache.MaxCacheBytes = 1073741824; // 1GB cache limit
    o.Cache.PruneOnStartup = true;
});

Usage Examples

# Resize to 800px width (preserves aspect ratio)
GET /media/photos/cat.jpg?width=800

# Fit within 800x600 box
GET /media/photos/cat.jpg?width=800&height=600

# Resize with quality control
GET /media/photos/cat.jpg?height=1080&quality=85

Configuration

appsettings.json

{
  "ImageResize": {
    "EnableMiddleware": true,
    "RequestPathPrefix": "/media",
    "ContentRoot": "wwwroot/images",
    "CacheRoot": "wwwroot/_imgcache",
    "AllowUpscale": false,
    "DefaultQuality": 80,
    "PngCompressionLevel": 6,
    "Bounds": {
      "MinWidth": 16, "MaxWidth": 4096,
      "MinHeight": 16, "MaxHeight": 4096,
      "MinQuality": 10, "MaxQuality": 95
    },
    "HashOriginalContent": false,
    "Cache": {
      "FolderSharding": 2,
      "PruneOnStartup": false,
      "MaxCacheBytes": 0
    },
    "ResponseCache": {
      "ClientCacheSeconds": 604800,
      "SendETag": true,
      "SendLastModified": true
    },
    "AllowedExtensions": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tif", ".tiff"],
    "Backend": "SkiaSharp"
  }
}

Programmatic Usage

// Inject the service
public class MyController : ControllerBase
{
    private readonly IImageResizerService _resizer;

    public MyController(IImageResizerService resizer)
    {
        _resizer = resizer;
    }

    [HttpGet("thumbnail/{id}")]
    public async Task<IActionResult> GetThumbnail(int id)
    {
        var result = await _resizer.EnsureResizedAsync(
            $"photos/{id}.jpg",
            new ResizeOptions(Width: 300, Height: 300, Quality: 80)
        );

        return PhysicalFile(result.CachedPath, result.ContentType);
    }
}

Enhanced ImageResult Properties

The ImageResult class provides all the properties you need:

using var image = await stream.LoadAsync(resizerService);

// Basic properties (ImageSharp compatible)
int width = image.Width;
int height = image.Height;
string contentType = image.ContentType;

// Enhanced metadata
long fileSize = image.FileSize;                    // File size in bytes
string fileSizeHuman = image.FileSizeHumanReadable; // "1.2 MB"
string format = image.Format;                      // "JPEG", "PNG", etc.
string extension = image.FileExtension;            // ".jpg", ".png", etc.

// Original dimensions
int originalWidth = image.OriginalWidth;
int originalHeight = image.OriginalHeight;

// Processing information
bool wasResized = image.WasResized;
bool isProcessed = image.IsProcessed;
int? quality = image.Quality;

// Computed properties
double aspectRatio = image.AspectRatio;            // Width/Height ratio
bool isLandscape = image.IsLandscape;
bool isPortrait = image.IsPortrait;
bool isSquare = image.IsSquare;
long pixelCount = image.PixelCount;               // Total pixels

Usage Examples

First, get a stream from your image source:

// From a file path
using var fileStream = File.OpenRead("path/to/image.jpg");

// From an HTTP file upload (ASP.NET Core)
public async Task<IActionResult> UploadImage(IFormFile uploadedFile)
{
    await using var stream = uploadedFile.OpenReadStream();
    // ... process stream
}

// From a byte array
var imageBytes = await File.ReadAllBytesAsync("path/to/image.jpg");
using var memoryStream = new MemoryStream(imageBytes);

// From a URL
using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync("https://example.com/image.jpg");
using var urlStream = await response.Content.ReadAsStreamAsync();

Then process the stream:

// Simple resize with one method call
var options = new ResizeOptions(Width: 1920, Height: 1080, Quality: 90);
using var resizedImage = await resizerService.ResizeAsync(stream, null, options);

// Access all metadata
Console.WriteLine($"Resized: {resizedImage.Width}x{resizedImage.Height} " +
                 $"({resizedImage.FileSizeHumanReadable}) " +
                 $"{resizedImage.Format} format");

// Save the result
await resizedImage.SaveAsync(filePath);

Advanced Usage with Custom Quality

// Process with custom quality settings
var options = new ResizeOptions(Width: 1920, Height: 1080, Quality: 90);
using var imageResult = await resizerService.ResizeAsync(originalStream, null, options);

// Access computed properties
bool wasResized = imageResult.WasResized;        // true
double aspectRatio = imageResult.AspectRatio;     // 1.6
string sizeDisplay = imageResult.FileSizeHumanReadable; // "245 KB"
bool isLandscape = imageResult.IsLandscape;
long pixelCount = imageResult.PixelCount;

Migration from ImageSharp

// OLD (ImageSharp)
using var image = await Image.LoadAsync(stream);
await image.SaveAsync(filePath);
media.Width = image.Width;
media.Height = image.Height;

// NEW (ImageResize) - Simple one-call approach
var options = new ResizeOptions(Width: 1920, Height: 1080, Quality: 85);
using var resizedImage = await resizerService.ResizeAsync(stream, null, options);
await resizedImage.SaveAsync(filePath);

// Access all the metadata you need
media.Width = resizedImage.Width;
media.Height = resizedImage.Height;
media.FileSize = resizedImage.FileSize;
media.ContentType = resizedImage.ContentType;
media.Format = resizedImage.Format;
media.AspectRatio = resizedImage.AspectRatio;
media.WasResized = resizedImage.WasResized;

Convenience Methods

For common resize operations, use these simple extension methods:

// Resize to specific width (maintains aspect ratio)
using var resized = await resizerService.ResizeToWidthAsync(stream, 800);

// Resize to specific height (maintains aspect ratio)
using var resized = await resizerService.ResizeToHeightAsync(stream, 600);

// Resize to fit within dimensions (maintains aspect ratio)
using var resized = await resizerService.ResizeToFitAsync(stream, 1920, 1080);

// Create thumbnail (default 300x300)
using var thumbnail = await resizerService.CreateThumbnailAsync(stream);

// Create custom size thumbnail
using var thumbnail = await resizerService.CreateThumbnailAsync(stream, 150);

Cache Design

  • Key generation: SHA1 hash of normalized path + options + source signature
  • Source signature: Last modified time + file size (+ optional content hash)
  • Atomic writes: Temp file → rename for consistency
  • Folder sharding: Configurable subfolder splitting (e.g., ab/cd/hash.ext)
  • Size management: Automatic cleanup when MaxCacheBytes exceeded
  • Startup pruning: Optional cleanup of old files on application startup

Supported Formats

Format Read Write Notes
JPEG Quality 1-100
PNG Compression level 0-9 (configurable)
WebP Quality 1-100
GIF First frame only
BMP
TIFF First page only

Backend Support

Currently supports SkiaSharp backend with framework for additional backends:

  • SkiaSharp: Cross-platform, high-performance (default)
  • SystemDrawing: Windows-only, .NET Framework compatible (planned)
  • MagickNet: ImageMagick integration (planned)

Configure via Backend setting in appsettings.json.

Performance

  • Memory efficient: Streams data without loading entire images
  • Concurrent safe: Keyed locks prevent duplicate processing
  • HTTP optimized: Conditional requests (304) and client caching
  • Configurable quality: Balance file size vs. visual quality
  • Smart caching: Automatic cache size management and startup pruning
  • Backend flexibility: Choose optimal codec for your platform

Security

  • Path traversal prevention
  • Configurable size bounds
  • Input validation on all parameters
  • Safe file operations with atomic writes

License

MIT

Contributing

PRs welcome! See the Example app for usage examples and tests for implementation details.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ImageResize:

Package Downloads
ZauberCMS.Core

ZauberCMS core package

ZauberCMS.Components

ZauberCMS components package

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.3 54 9/26/2025
1.2.2 67 9/26/2025
1.2.1 71 9/26/2025
1.2.0 126 9/11/2025
1.1.0 119 9/11/2025
1.0.0 118 9/10/2025
1.0.0-beta 119 9/10/2025