图像优化Skill image-optimization

该技能专注于实现响应式图像、优化图像格式和处理图像管道,包括生成srcset、转换到WebP/AVIF、懒加载和提供图像转换API,适用于无头CMS和现代Web开发。关键词:图像优化,响应式图像,WebP转换,AVIF转换,懒加载,图像处理API,焦点裁剪,性能优化。

前端开发 0 次安装 0 次浏览 更新于 3/11/2026

名称: 图像优化 描述: 用于实现响应式图像、格式转换、焦点裁剪或图像处理管道时使用。涵盖srcset生成、WebP/AVIF转换、懒加载和用于无头CMS的图像转换API。 允许工具: 读取, 全局, 搜索, 任务, 技能

图像优化

关于为无头CMS实现响应式图像、格式优化和图像处理管道的指南。

何时使用此技能

  • 实现响应式图像交付
  • 将图像转换为现代格式(WebP、AVIF)
  • 构建图像处理管道
  • 实现焦点裁剪
  • 优化图像加载性能

响应式图像模式

Srcset用于分辨率切换

<!-- 基本srcset带有宽度描述符 -->
<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="英雄图像"
>

<!-- Srcset带有像素密度 -->
<img
  src="/images/logo.png"
  srcset="
    /images/logo.png 1x,
    /images/logo@2x.png 2x,
    /images/logo@3x.png 3x
  "
  alt="徽标"
>

Picture元素用于艺术指导

<picture>
  <!-- 移动端:方形裁剪 -->
  <source
    media="(max-width: 600px)"
    srcset="/images/hero-mobile.webp"
    type="image/webp"
  >
  <source
    media="(max-width: 600px)"
    srcset="/images/hero-mobile.jpg"
  >

  <!-- 桌面端:宽幅裁剪 -->
  <source
    srcset="/images/hero-desktop.webp"
    type="image/webp"
  >

  <!-- 回退 -->
  <img src="/images/hero-desktop.jpg" alt="英雄">
</picture>

图像处理服务

核心处理

public class ImageProcessingService
{
    public async Task<Stream> ProcessAsync(
        Stream source,
        ImageProcessingOptions options)
    {
        using var image = await Image.LoadAsync(source);

        // 调整大小
        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));
        }

        // 应用效果
        if (options.Blur > 0)
        {
            image.Mutate(x => x.GaussianBlur(options.Blur));
        }

        if (options.Grayscale)
        {
            image.Mutate(x => x.Grayscale());
        }

        // 编码为目标格式
        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, 从左到右
    public float Y { get; set; } // 0-1, 从上到下
}

public enum ImageFormat
{
    Jpeg,
    Png,
    WebP,
    Avif,
    Gif
}

基于预设的处理

public class ImagePresets
{
    public static readonly Dictionary<string, ImageProcessingOptions> Presets = new()
    {
        ["缩略图"] = new()
        {
            Width = 150,
            Height = 150,
            ResizeMode = ResizeMode.Crop,
            Format = ImageFormat.WebP,
            Quality = 75
        },
        ["卡片"] = new()
        {
            Width = 400,
            Height = 300,
            ResizeMode = ResizeMode.Crop,
            Format = ImageFormat.WebP,
            Quality = 80
        },
        ["英雄"] = new()
        {
            Width = 1920,
            Height = 800,
            ResizeMode = ResizeMode.Crop,
            Format = ImageFormat.WebP,
            Quality = 85
        },
        ["开放图谱图像"] = new()
        {
            Width = 1200,
            Height = 630,
            ResizeMode = ResizeMode.Crop,
            Format = ImageFormat.Jpeg,
            Quality = 90
        },
        ["模糊占位符"] = new()
        {
            Width = 20,
            Height = 20,
            Format = ImageFormat.Jpeg,
            Quality = 30,
            Blur = 5
        }
    };
}

焦点裁剪

焦点模型

public class FocalPointService
{
    public AnchorPositionMode CalculateAnchor(
        FocalPoint focal,
        Size imageSize,
        Size targetSize)
    {
        // 计算图像裁剪部分
        var imageAspect = (float)imageSize.Width / imageSize.Height;
        var targetAspect = (float)targetSize.Width / targetSize.Height;

        if (Math.Abs(imageAspect - targetAspect) < 0.01f)
        {
            return AnchorPositionMode.Center;
        }

        // 确定裁剪方向
        var cropHorizontal = imageAspect > targetAspect;

        if (cropHorizontal)
        {
            // 裁剪侧面,使用X焦点
            return focal.X switch
            {
                < 0.33f => AnchorPositionMode.Left,
                > 0.66f => AnchorPositionMode.Right,
                _ => AnchorPositionMode.Center
            };
        }
        else
        {
            // 裁剪顶部/底部,使用Y焦点
            return focal.Y switch
            {
                < 0.33f => AnchorPositionMode.Top,
                > 0.66f => AnchorPositionMode.Bottom,
                _ => AnchorPositionMode.Center
            };
        }
    }
}

存储焦点

public class MediaItem
{
    public Guid Id { get; set; }
    public string FileName { get; set; } = string.Empty;

    // 用于智能裁剪的焦点
    public FocalPoint? FocalPoint { get; set; }
}

// 通过API设置
PATCH /api/media/{id}
{
  "focalPoint": { "x": 0.3, "y": 0.2 }
}

即时图像转换

基于URL的转换

# 基础URL
https://cdn.example.com/media/hero.jpg

# 带转换
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

转换端点

[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)
    {
        // 获取原始图像
        var original = await _mediaService.GetStreamAsync(path);
        if (original == null) return NotFound();

        // 从查询或预设构建选项
        var options = preset != null
            ? ImagePresets.Presets.GetValueOrDefault(preset) ?? new()
            : new ImageProcessingOptions
            {
                Width = w,
                Height = h,
                Format = ParseFormat(format),
                Quality = q ?? 80,
                ResizeMode = ParseFit(fit)
            };

        // 处理图像
        var processed = await _imageProcessor.ProcessAsync(original, options);

        return File(processed, GetMimeType(options.Format));
    }
}

Srcset生成

响应式图像服务

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";
}

低质量图像占位符(LQIP)

模糊哈希生成

public class BlurHashService
{
    public string GenerateBlurHash(Image image, int componentsX = 4, int componentsY = 3)
    {
        // 调整到小尺寸用于哈希计算
        using var small = image.Clone(x => x.Resize(32, 32));

        // 计算模糊哈希
        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);
    }
}

占位符策略

策略 大小 质量 使用场景
模糊哈希 ~30字符 HTML内联
微小JPEG ~500字节 数据URL
主导颜色 7字符 简单 CSS背景
SVG追踪 ~2KB 艺术网站

性能最佳实践

懒加载

<!-- 原生懒加载 -->
<img
  src="/images/photo.jpg"
  loading="lazy"
  decoding="async"
  alt="照片"
>

<!-- 带模糊占位符 -->
<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="照片"
  >
</div>

格式选择

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;
}

图像API响应

{
  "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 }
  }
}

相关技能

  • 媒体资产管理 - 媒体库和上传
  • CDN媒体交付 - CDN集成和缓存
  • 无头API设计 - 图像API端点