ImageResize 1.2.3
dotnet add package ImageResize --version 1.2.3
NuGet\Install-Package ImageResize -Version 1.2.3
<PackageReference Include="ImageResize" Version="1.2.3" />
<PackageVersion Include="ImageResize" Version="1.2.3" />
<PackageReference Include="ImageResize" />
paket add ImageResize --version 1.2.3
#r "nuget: ImageResize, 1.2.3"
#:package ImageResize@1.2.3
#addin nuget:?package=ImageResize&version=1.2.3
#tool nuget:?package=ImageResize&version=1.2.3
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.
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"; // URL prefix to monitor (e.g., /media/*)
o.ContentRoot = Path.Combine(builder.Environment.WebRootPath, "images"); // Where original images are stored
o.CacheRoot = Path.Combine(builder.Environment.WebRootPath, "_imgcache"); // Where resized images are cached
o.AllowUpscale = false; // Don't enlarge images beyond original size
o.DefaultQuality = 85; // Default JPEG/WebP quality
o.PngCompressionLevel = 6; // PNG compression (0-9)
o.Backend = ImageBackend.SkiaSharp; // Image processing backend
o.Cache.MaxCacheBytes = 1073741824; // 1GB cache limit (0 = unlimited)
o.Cache.PruneOnStartup = true; // Clean old cache files on app start
});
URL Structure and File System Mapping
The middleware intercepts requests under the configured RequestPathPrefix
(default: /media
) and maps them to files on disk. Here's how it works:
URL Pattern:
/media/{relative-path-to-image}?width={width}&height={height}&quality={quality}
File System Mapping:
- Original Images: Stored in
ContentRoot
(default:wwwroot/images/
) - Cached Resized Images: Stored in
CacheRoot
(default:wwwroot/_imgcache/
)
Example:
- Request:
GET /media/photos/cat.jpg?width=800
- Original file:
wwwroot/images/photos/cat.jpg
- Cached file:
wwwroot/_imgcache/{hash}/cat.jpg
(with sharding likeab/cd/{hash}.jpg
)
Key Points:
- The
/media
prefix is not a real folder - it's just a URL route that the middleware intercepts - Original images remain untouched in their original location
- Resized images are automatically cached in a separate folder for performance
- Cache uses SHA1 hashing with folder sharding to prevent filesystem issues with many files
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
# Serve original image (no resize parameters)
GET /media/photos/cat.jpg
Configuration
appsettings.json
{
"ImageResize": {
"EnableMiddleware": true, // Enable/disable the middleware
"RequestPathPrefix": "/media", // URL prefix to monitor for image requests
"ContentRoot": "wwwroot/images", // Directory containing original images
"CacheRoot": "wwwroot/_imgcache", // Directory for cached resized images
"AllowUpscale": false, // Prevent enlarging images beyond original size
"DefaultQuality": 99, // Default JPEG/WebP quality (1-100)
"PngCompressionLevel": 6, // PNG compression level (0-9)
"Bounds": {
"MinWidth": 16, "MaxWidth": 4096, // Width limits in pixels
"MinHeight": 16, "MaxHeight": 4096, // Height limits in pixels
"MinQuality": 10, "MaxQuality": 100 // Quality limits (JPEG/WebP only)
},
"HashOriginalContent": false, // Include file content in cache key (slower but more accurate)
"Cache": {
"FolderSharding": 2, // Subfolder levels for cache organization (0-4)
"PruneOnStartup": false, // Clean old cache files when app starts
"MaxCacheBytes": 0 // Cache size limit in bytes (0 = unlimited)
},
"ResponseCache": {
"ClientCacheSeconds": 604800, // Browser cache duration (7 days)
"SendETag": true, // Send ETag headers for caching
"SendLastModified": true // Send Last-Modified headers for caching
},
"AllowedExtensions": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tif", ".tiff"],
"Backend": "SkiaSharp" // Image processing backend
}
}
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);
// Save the result
await resizedImage.SaveAsync(filePath);
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);
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 | Versions 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. |
-
net9.0
- Microsoft.Extensions.Logging (>= 9.0.9)
- SkiaSharp (>= 3.119.0)
- SkiaSharp.NativeAssets.Linux (>= 3.119.0)
- SkiaSharp.NativeAssets.macOS (>= 3.119.0)
- SkiaSharp.NativeAssets.Win32 (>= 3.119.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.