Agent Skills: Image Optimization

Use when implementing responsive images, format conversion, focal point cropping, or image processing pipelines. Covers srcset generation, WebP/AVIF conversion, lazy loading, and image transformation APIs for headless CMS.

UncategorizedID: melodic-software/claude-code-plugins/image-optimization

Install this agent skill to your local

pnpm dlx add-skill https://github.com/melodic-software/claude-code-plugins/tree/HEAD/plugins/content-management-system/skills/image-optimization

Skill Files

Browse the full folder contents for image-optimization.

Download Skill

Loading file tree…

plugins/content-management-system/skills/image-optimization/SKILL.md

Skill Metadata

Name
image-optimization
Description
Use when implementing responsive images, format conversion, focal point cropping, or image processing pipelines. Covers srcset generation, WebP/AVIF conversion, lazy loading, and image transformation APIs for headless CMS.

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