Image Optimization
Guidance for implementing responsive images, format optimization, and image processing pipelines for headless CMS.
When to Use This Skill
-
Implementing responsive image delivery
-
Converting images to modern formats (WebP, AVIF)
-
Building image processing pipelines
-
Implementing focal point cropping
-
Optimizing image loading performance
Responsive Image Patterns
Srcset for Resolution Switching
<!-- Basic srcset with width descriptors --> <img src="/images/hero.jpg" srcset=" /images/hero-400w.jpg 400w, /images/hero-800w.jpg 800w, /images/hero-1200w.jpg 1200w, /images/hero-1600w.jpg 1600w " sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px" alt="Hero image"
<!-- Srcset with pixel density --> <img src="/images/logo.png" srcset=" /images/logo.png 1x, /images/logo@2x.png 2x, /images/logo@3x.png 3x " alt="Logo"
Picture Element for Art Direction
<picture> <!-- Mobile: Square crop --> <source media="(max-width: 600px)" srcset="/images/hero-mobile.webp" type="image/webp"
<source media="(max-width: 600px)" srcset="/images/hero-mobile.jpg"
<!-- Desktop: Wide crop --> <source srcset="/images/hero-desktop.webp" type="image/webp"
<!-- Fallback --> <img src="/images/hero-desktop.jpg" alt="Hero"> </picture>
Image Processing Service
Core Processing
public class ImageProcessingService { public async Task<Stream> ProcessAsync( Stream source, ImageProcessingOptions options) { using var image = await Image.LoadAsync(source);
// Resize
if (options.Width.HasValue || options.Height.HasValue)
{
var resizeOptions = new ResizeOptions
{
Size = new Size(
options.Width ?? 0,
options.Height ?? 0),
Mode = options.ResizeMode,
Position = options.FocalPoint != null
? CalculateAnchor(options.FocalPoint, image.Size)
: AnchorPositionMode.Center
};
image.Mutate(x => x.Resize(resizeOptions));
}
// Apply effects
if (options.Blur > 0)
{
image.Mutate(x => x.GaussianBlur(options.Blur));
}
if (options.Grayscale)
{
image.Mutate(x => x.Grayscale());
}
// Encode to target format
var outputStream = new MemoryStream();
await EncodeAsync(image, outputStream, options);
outputStream.Position = 0;
return outputStream;
}
private async Task EncodeAsync(
Image image,
Stream output,
ImageProcessingOptions options)
{
switch (options.Format)
{
case ImageFormat.WebP:
await image.SaveAsWebpAsync(output, new WebpEncoder
{
Quality = options.Quality,
Method = WebpEncodingMethod.BestQuality
});
break;
case ImageFormat.Avif:
await image.SaveAsAvifAsync(output, new AvifEncoder
{
Quality = options.Quality
});
break;
case ImageFormat.Jpeg:
await image.SaveAsJpegAsync(output, new JpegEncoder
{
Quality = options.Quality,
ColorType = JpegEncodingColor.YCbCrRatio420
});
break;
case ImageFormat.Png:
await image.SaveAsPngAsync(output, new PngEncoder
{
CompressionLevel = PngCompressionLevel.BestCompression
});
break;
}
}
}
public class ImageProcessingOptions { public int? Width { get; set; } public int? Height { get; set; } public ResizeMode ResizeMode { get; set; } = ResizeMode.Max; public FocalPoint? FocalPoint { get; set; } public ImageFormat Format { get; set; } = ImageFormat.WebP; public int Quality { get; set; } = 80; public float Blur { get; set; } public bool Grayscale { get; set; } }
public class FocalPoint { public float X { get; set; } // 0-1, left to right public float Y { get; set; } // 0-1, top to bottom }
public enum ImageFormat { Jpeg, Png, WebP, Avif, Gif }
Preset-Based Processing
public class ImagePresets { public static readonly Dictionary<string, ImageProcessingOptions> Presets = new() { ["thumbnail"] = new() { Width = 150, Height = 150, ResizeMode = ResizeMode.Crop, Format = ImageFormat.WebP, Quality = 75 }, ["card"] = new() { Width = 400, Height = 300, ResizeMode = ResizeMode.Crop, Format = ImageFormat.WebP, Quality = 80 }, ["hero"] = new() { Width = 1920, Height = 800, ResizeMode = ResizeMode.Crop, Format = ImageFormat.WebP, Quality = 85 }, ["og-image"] = new() { Width = 1200, Height = 630, ResizeMode = ResizeMode.Crop, Format = ImageFormat.Jpeg, Quality = 90 }, ["blur-placeholder"] = new() { Width = 20, Height = 20, Format = ImageFormat.Jpeg, Quality = 30, Blur = 5 } }; }
Focal Point Cropping
Focal Point Model
public class FocalPointService { public AnchorPositionMode CalculateAnchor( FocalPoint focal, Size imageSize, Size targetSize) { // Calculate which part of image will be cropped var imageAspect = (float)imageSize.Width / imageSize.Height; var targetAspect = (float)targetSize.Width / targetSize.Height;
if (Math.Abs(imageAspect - targetAspect) < 0.01f)
{
return AnchorPositionMode.Center;
}
// Determine crop direction
var cropHorizontal = imageAspect > targetAspect;
if (cropHorizontal)
{
// Cropping sides, use X focal point
return focal.X switch
{
< 0.33f => AnchorPositionMode.Left,
> 0.66f => AnchorPositionMode.Right,
_ => AnchorPositionMode.Center
};
}
else
{
// Cropping top/bottom, use Y focal point
return focal.Y switch
{
< 0.33f => AnchorPositionMode.Top,
> 0.66f => AnchorPositionMode.Bottom,
_ => AnchorPositionMode.Center
};
}
}
}
Storing Focal Point
public class MediaItem { public Guid Id { get; set; } public string FileName { get; set; } = string.Empty;
// Focal point for smart cropping
public FocalPoint? FocalPoint { get; set; }
}
// Set via API PATCH /api/media/{id} { "focalPoint": { "x": 0.3, "y": 0.2 } }
On-the-Fly Image Transformation
URL-Based Transformation
Base URL
https://cdn.example.com/media/hero.jpg
With transformations
https://cdn.example.com/media/hero.jpg?w=800&h=600&fit=crop https://cdn.example.com/media/hero.jpg?w=400&format=webp&q=80 https://cdn.example.com/media/hero.jpg?preset=thumbnail
Transformation Endpoint
[Route("media/{path}")] public class ImageTransformController : ControllerBase { [HttpGet] [ResponseCache(Duration = 31536000, VaryByQueryKeys = new[] { "" })] public async Task<IActionResult> GetTransformed( string path, [FromQuery] int? w, [FromQuery] int? h, [FromQuery] string? format, [FromQuery] int? q, [FromQuery] string? fit, [FromQuery] string? preset) { // Get original image var original = await _mediaService.GetStreamAsync(path); if (original == null) return NotFound();
// Build options from query or preset
var options = preset != null
? ImagePresets.Presets.GetValueOrDefault(preset) ?? new()
: new ImageProcessingOptions
{
Width = w,
Height = h,
Format = ParseFormat(format),
Quality = q ?? 80,
ResizeMode = ParseFit(fit)
};
// Process image
var processed = await _imageProcessor.ProcessAsync(original, options);
return File(processed, GetMimeType(options.Format));
}
}
Srcset Generation
Responsive Image Service
public class ResponsiveImageService { private readonly int[] _defaultWidths = { 320, 640, 960, 1280, 1920 };
public ResponsiveImageData GenerateSrcset(
MediaItem media,
ResponsiveImageOptions? options = null)
{
options ??= new ResponsiveImageOptions();
var widths = options.Widths ?? _defaultWidths;
var srcset = widths
.Where(w => w <= (media.Metadata.Width ?? int.MaxValue))
.Select(w => new SrcsetEntry
{
Url = BuildTransformUrl(media, w, options.Format),
Width = w
})
.ToList();
return new ResponsiveImageData
{
Src = BuildTransformUrl(media, options.DefaultWidth, options.Format),
Srcset = srcset,
Sizes = options.Sizes ?? "(max-width: 1200px) 100vw, 1200px",
Alt = media.Metadata.Alt ?? "",
Width = media.Metadata.Width,
Height = media.Metadata.Height,
BlurDataUrl = options.IncludeBlurPlaceholder
? BuildTransformUrl(media, 20, ImageFormat.Jpeg, blur: 5)
: null
};
}
private string BuildTransformUrl(
MediaItem media,
int width,
ImageFormat format,
int? blur = null)
{
var query = $"?w={width}&format={format.ToString().ToLower()}";
if (blur.HasValue) query += $"&blur={blur}";
return $"{_cdnBaseUrl}/media/{media.StoragePath}{query}";
}
}
public class ResponsiveImageData { public string Src { get; set; } = string.Empty; public List<SrcsetEntry> Srcset { get; set; } = new(); public string Sizes { get; set; } = string.Empty; public string Alt { get; set; } = string.Empty; public int? Width { get; set; } public int? Height { get; set; } public string? BlurDataUrl { get; set; } }
public class SrcsetEntry { public string Url { get; set; } = string.Empty; public int Width { get; set; }
public override string ToString() => $"{Url} {Width}w";
}
Low-Quality Image Placeholders (LQIP)
Blur Hash Generation
public class BlurHashService { public string GenerateBlurHash(Image image, int componentsX = 4, int componentsY = 3) { // Resize to small dimensions for hash calculation using var small = image.Clone(x => x.Resize(32, 32));
// Calculate blur hash
var pixels = new Pixel[32, 32];
for (var y = 0; y < 32; y++)
{
for (var x = 0; x < 32; x++)
{
var pixel = small[x, y];
pixels[x, y] = new Pixel(pixel.R, pixel.G, pixel.B);
}
}
return BlurHash.Encode(pixels, componentsX, componentsY);
}
}
Placeholder Strategies
Strategy Size Quality Use Case
Blur hash ~30 chars Good Inline in HTML
Tiny JPEG ~500 bytes Medium Data URL
Dominant color 7 chars Simple CSS background
SVG trace ~2KB Good Artistic sites
Performance Best Practices
Lazy Loading
<!-- Native lazy loading --> <img src="/images/photo.jpg" loading="lazy" decoding="async" alt="Photo"
<!-- With blur placeholder --> <div class="image-container" style="background: url(data:image/jpeg;base64,...)"> <img src="/images/photo.jpg" loading="lazy" onload="this.parentElement.style.background = 'none'" alt="Photo"
</div>
Format Selection
public ImageFormat SelectOptimalFormat(string acceptHeader, ImageFormat preferred) { if (acceptHeader.Contains("image/avif")) return ImageFormat.Avif;
if (acceptHeader.Contains("image/webp"))
return ImageFormat.WebP;
return preferred;
}
Image API Response
{ "data": { "id": "media-123", "original": { "url": "https://cdn.example.com/media/original/hero.jpg", "width": 3840, "height": 2160, "mimeType": "image/jpeg", "sizeBytes": 2456789 }, "responsive": { "src": "https://cdn.example.com/media/hero.jpg?w=1280&format=webp", "srcset": "https://cdn.example.com/media/hero.jpg?w=320&format=webp 320w, ...", "sizes": "(max-width: 1200px) 100vw, 1200px" }, "placeholder": { "blurHash": "LEHV6nWB2yk8pyo0adR*.7kCMdnj", "dataUrl": "data:image/jpeg;base64,/9j/4AAQ..." }, "focalPoint": { "x": 0.5, "y": 0.3 } } }
Related Skills
-
media-asset-management
-
Media library and upload
-
cdn-media-delivery
-
CDN integration and caching
-
headless-api-design
-
Image API endpoints