如何使用AVIF:新一代图像压缩格式

发布于 大漠

一直以来,我们熟悉的图片格式或者说到目前运用到Web页面或Web应用的图片格式主要是.jpg.png.gif.svg,以及.webp等,其实能运用到Web上的图片格式除了这些之外,还可以是JPEG 2000JPEG XR等。但最近社区有关于图片格式讨论最多的是AVIF图像,它将是新一代图像压缩格式,该格式(AVIF)图像质量与压缩文件大小的比率要优于我们熟悉的JPEG、PNG,甚至Webp图像格式。那么AVIF是什么样的图像呢?我们在Web开发中可以使用这种格式图像?如果可以,我们又将如何使用呢?今天这篇文章我们就来一起探讨这方面的知识,如果你感兴趣的话,请继续往下阅读。

AVIF是什么?

AVIF是由AV1视频的关键帧衍生而来的一种新的图像格式。这种格式的图像最早是由Netflix公司提出,并且运用到该公司的项目中。虽是Netflix公司提出,但是AVIF格式的图像是由开放媒体联盟与Google、Cisco和Xiph.org(他们与Firefox浏览器的创建者Mozilla合作)合作开发的。这种格式被创建为一种开源和免篡权的图像格式(与JPEG XR不同,后者是一种压缩得非常小的文件格式,但实现它需要昂贵的许可证,说白了就是要银子)。

AVIF这种新的图像格式已经得到Chrome 85+浏览支持,而且Android也会很快支持这种格式图像,Firefox也正在努力实现中,同时Safari也会很快支持这种图像格式,因为苹果是创建AV1视频的成员之一。

话又说回来,AVIF与JPEG或WebP相比,AVIF图像文件大小得到很大的缩减:

  • 与JPEG相比,AVIF节省了50%
  • 与WebP相比,AVIF节省了20%

另外AVIF格式非常灵活,它支持任何图像解码器,可以是有损的或无损的,也有能力使用一个透明(Alpha)通道,甚至有能力存储一系列动画帧。它也是第一个支持HDR颜色的图像格式之一,提供更高的亮度,我位深度和色域。

既然AVIF这么优秀,那么我们就有必要开始关注和了解AVIF了。而且我们也不用过于担心浏览器对AVIF的支持度,因为我们可以使用<picture>对不支持AVIF的客户端(浏览器)做优雅降级:

<picture>
    <!-- 如果浏览器支持AVIF格式图像则会采用该格式图像  -->
    <source type="image/avif" srcset="snow.avif" />

    <!-- 如果浏览器不支持AVIF格式图像则会采用img引用的图像 -->
    <img src="snow.jpg" alt="Hut in the snow" />
</picture>

Netflix的图像压缩

要继续往下聊AVIF格式的图像,就有必要先了解一下Netflix的图像压缩。

有关于Netflix的图像压缩详细的介绍可以阅读 @Netflix Technology Blog提供的博文《AVIF for Next-Generation Image Coding》。

Netflix是一家流媒体公司,Netflix的会员可以在各种设备(智能电视、手机、平板、个人电脑和连接到电视屏幕)观看视频。换句话说,他们的用户界面(UI界面)会用到很多跨设备的图像。最简单的就是Netflix线上平台(网站)上会有很多影视海报,而且这些海报都是大图:

如上所述,图像资源通常具有在图像上合成的渐变文本图像。这种特殊处理导致了各种各样的特征,而这些特征并不一定会出现在自然图像中。硬边(包括边缘两侧存在色度差异的边缘)很常见,需要很好的细节保存,因为它们通常出现在显著的位置,并传递重要信息。同样,保留人物面部的细节也是非常重要的。在某情况下,背景纹理复杂,会表现出广泛的频率范围。

在摄取图像资源之后,压缩管道会开始启动,并准备好要交付给设备的压缩图像资源。目标是使压缩后的图像看起来尽可能接近原始图像,同时减少所需的字节数。鉴于用户界面以图像为主的特性,压缩好这些图像是至关重要的。而这涉及到色度子采样( color subsampling)、编解码器参数和编码分辨率的正确组合等。

让我们以色度子采样为例。基于人类视觉系统对亮度比色度更敏感这一事实,我们选择了 4:2:0 采样方式,而不是原来的 4:4:4 采样方式,将需要进行编码的样本数量减半(在所有 3 个色彩平面上计数)。但是,4:2:0 采样方式可能会在具有颜色过渡的地方出现色渗和锯齿的现象。下面,我们在 4:4:4 采样方式的原始源和转换为 4:2:0 采样方式的源之间切换。切换显示仅仅有色度子采样引入的损失,甚至在编解码器进入图片之前也是如此。

在以 4:4:4 采样方式的原始源图像与转换为 4:2:0 采样方式的原始源图像之间切换。只显示作品的顶部。读者可以放大网页,查看由于 4:2:0 采样方式而出现的 Netflix 标志周围的锯齿。

然而,在某些源图像中,由于 4:2:0 采样方式造成的损失对来人类视觉感知来说并不明显,那么,在这种情况下,使用 4:2:0 采样方式可能是有利的。理想情况下,编解码器应该能够支持这两种子采样格式。但是,有些编解码器只支持 4:2:0 采样方式,如 Webp。

图像解码是个复杂知识体系,这里不做过多阐述

Netflix这么做是有着他自己的诉求: 需要高清的图像的同时又希望图像体积较小。而AVIF格式图像又具备这些特性:

  • 具有更好的压缩效率
  • 具有更广泛的功能集

AVIF的特点

尽管现代视频编解码器主要是为了视频而开发的,但是视频编解码器中的帧内编码工具与图像压缩工具并没有什么太大的不同。鉴于现代视频编解码器的巨大压缩收益,它们作为图像编码格式还是很有吸引力的。重用现有硬件进行视频压缩、 解压有潜在的好处。考虑到依赖于操作系统的用户界面组成的特殊性,以及移动未压缩图像像素的架构含义,硬件中的图像解码可能并不是一个主要的动力。

在图像编码格式方面,动态图像专家组(Moving Picture Experts Group,MPEG)已将与编解码器无关的通用图像容器格式进行了标准化:ISO/IEC 23000-12 标准(又称 HEIF)。最值得一提的是,HEIF 已经用于存储 HEVC 编码的图像(在其 HEIC 变体中),但也能够存储 AVC 编码的图像,甚至 JPEG 编码的图像。开发媒体联盟(Alliance for Open Media,AOM)最近对这种格式进行了扩展,以指定 AVIF 格式的 AV1 编码图像的存储。基本的 HEIF 格式提供了图像格式应有的典型特性,例如:支持任何图像编解码器,能够使用有损或无损模式进行压缩,支持不同的子采样和位深度等。此外,该格式还允许存储一系列动画帧(为动画 GIF 提供了一种期待已久的高效替代方案),并能够制定 alpha 通道(这在用户界面中非常有用)。由于 HEIF 格式借鉴了下一代视频压缩的技术,因此该格式允许保留元数据,如色彩色域和高动态范围(high dynamic range,HDR)等信息。

AVIF图像格式的压缩对比

在@Netflix TechBlog的《AVIF for Next-Generation Image Coding》和 @Jake的《AVIF has landed》教程中都提供了实际的示例,可视化的向大家阐述了AVIF图像格式和其他图像格式压缩对比的结果。

我们可以借助Squoosh这个App来查看AVIF图像格式与其他常见的图像格式压缩结果的对比。

Squoosh这个应用的使用非常的简单,只需要把需要的图像拖到这个应用中,就可以查看相应的对比结果:

我们来看一个实际示例,假设在你的Web应用中可能会使用类似下图这样的一张图像:

上图未做任何优化处理的之个,它的在大小大约是607kb(采用的是PNG图像格式),我们把这张图放到Squoosh这个App中,在相同质量(比如Quality为75%)处理条件下,不同图像格式下的大小:

  原始图像 MozJPEG WebP Browser PNG Browser JPEG Browser WebP AVIF
大小(kb 607kb 33.2kb 36.2kb 510kb 33.1kb 36.3kb 18kb
相比原始图像变小   95% 94% 16% 95% 94% 97%

正如上表所示,同一张图像,在AVIF格式下(Max Quality为 30, Min Quality为0, Effort为2)大小才18kb,差不多在原图上减少了近97%

但我们在Web应用或Web页面上使用图像不仅仅是追求图像大小(体积)而且还追求图像的质量。在Web中使用图像的质量有一个基本原则:

可接受的图像质量

“可接受的图像质量”一般是指:

  • 如果用户在页面的上下文中查看图像,并且由于压缩而让图像很难看,那么这种压缩级别是不可接受的
  • 与原始图像相比,图像失去明显的细节是没有问题的,除非这些细节对图像的上下文非常重要

也就是说,图像压缩应该根据它将呈现给用户的大小和使用的环境来判断。比如说,你将一幅图像作为一件艺术品来展示,那么图像质量和细节的就变得尤其重要;如果你将一幅图像只是做为Web背景或仅是装饰物,那么图像质量和细节就相比要低一点,只要不会让图像在Web上下文中让用户感到有不适感觉即可。

有关于Web就算中图像优化相关的教程还可以阅读:

Web中如何使用AVIF

到目前为止,Chrome 80+已经支持AVIF(.avif文件)格式的图像,在Firefox 80浏览器中开启相关的AVIF相关的配置也可以让其支持该格式图像。

在Firefox 80的浏览器地址栏中输入about:config,并且将image.avif.enabled的值设置为true,这样也能让Firefox 80浏览器访问AVIF格式的图像。

获取AVIF格式图像

要在Web开发中使用AVIF格式的图像(文件扩展名一般为.avif),首先需要有.avif格式的图像。就目前为止,我们熟悉的图像编辑软件几乎还没有能直接将图像保存为.avif格式文件。但我们可以借助第三方应用将其他格式(比如我们熟悉的JPEGPNG等)转换成AVIF。

目前社区中生成AVIF图像格式的应用比较有名的是前面提到的 Squoosh App

这样就可以将导出来的.avif文件运用到浏览器了,比如像下图使用Chrome浏览器打开AVIF格式图像:

也可以使用Convertio的AVIF在线转换工具,该工具还提供了Chrome浏览器的扩展插件

当然,你要是喜欢使用命令终端来操作的话,还可以AOMedia提供的libavif库来编码或解码AVIF格式的图像。此外,对于macOS用户,还可以使用 Homebrew,可以使用在命令终端执行:

» brew install joedrago/repo/avifenc

安装完成之后,在命令终端执行:

» avifenc --help 

可以查阅到相关操作的命令。

将AVIF格式图像运用到Web中

有了AVIF格式图像之后,我们可以像使用其他格式的图像一样运用到Web中,比如使用<img>加载:

<img src="path/image.avif"  alt="" />

也可以在CSS的background-imagemask-imageborder-image等属性中使用,比如:

.avif {
    background-image: url('path/image.avif')
}

如果你担心支持AVIF的主流浏览器不多,那么还可以使用<picture><img>结合起来,做一个优雅的降级使用:

<picture>
    <!-- 支持AVIF的浏览器将使用AVIF格式图像 -->
    <source srcset="path/image.avif" type="image/avif"> 

    <!-- 如果不支持AVIF但支持WebP,将使用WebP格式图像 -->
    <source srcset="path/image.webp" type="image/webp">

    <!-- 如果浏览器根本不支持<picture>,它将退回到使用默认的<img> -->
    <img src="path/image.jpg" alt="Description of Photo">
</picture>

在CSS中,我们还可根据CSS媒体查询来做一个判断,比如说,要是用户开启“开启了请求少量网络流量的Web页面”,对应媒体查询的prefers-reduced-data: reduce条件

那么我们可以这样使用AVIF格式图像(应该该格式图像体积小):

@media (prefers-reduced-data: reduce) {
    header {
        background-image: image-set(
            'path/image.avif' 1x format('image/avif'),
            'path/image@2x.avif' 2x format('image/avif'),
            'path/image.webp' 1x format('image/webp'),
            'path/image@2x.webp' 2x format('image/webp'),
            'path/image.png' 1x format('image/png'),
            'path/image@2x.png 2x format('image/png')
        )
    }

    .avif {
        background-image: url('path/image.avif')
    }
}

在CSS 媒体查询模块Level 5中新增了一些根据用户喜好设置等相关的媒体查询条件,这样可以根据用户喜好提供不同样式,给用户一个更偏向自己喜好的一个体验。有关于这方面更详细的介绍,可以阅读《CSS媒体查询新特性》一文。

AVIF Content-Type配置

分别使用Chrome和Firefox访问Netlify,你会发现,Firefox上碰到.avif的图像不能正常的显示出来。你会发现Firefox浏览器对于.avif文件返回的Content-Typeapplication/octet-stream,这是导致Firefox浏览器不能正常访问.avif文件的主要原因。我们通过在Netlify配置文件(netlify.toml)中定义自定义头来解决这个问题。

[[headers]]
    for = "*.avif"
    [headers.values]
        Content-Type = "image/avif"
        Content-Disposition = "inline"

我们将Content-Disposition设置为inline(还有一个值为attachment),这样浏览器将尝试在浏览器内部而不是外部渲染文件。一个很好的例子就是在浏览器中打开和下载PDF文件。虽然inline是默认行为,但是指定其他的值也不会有别的问题,因为这是一个新的文件类型。

有关于netlify.toml文档更详细的配置可以查阅Netlify提供的相关文档

AVIF的Polyfill

在Web中使用AVIF格式图像除了可以为浏览器做优雅降级之外,还可以使用AVIF的Polyfill

该Polyfill的使用也很简单。首选安装avif.js

» npm install avif.js

index.js引入avif-sw.js

// 下面代码放到index.js中,然后把avif-sw.js放在web服务器根目录
require("avif.js").register("/avif-sw.js");

并且将index.js放到对应的.html

<body>
    <!-- 注册worker -->
    <script src="index.js"></script>

    <!-- 使用IMG标签嵌入AVIF图像 -->
    <img src="path/image.avif">

    <!-- 或者通过CSS属性 -->
    <div style="background: url(path/image2.avif)"></div>
</body>

AVIF的Polyfill官文文档中也提供了一个Demo,对应的index.js代码如下:

import avif from "../avif.js";

if ("serviceWorker" in navigator) {
    avif.register(navigator.serviceWorker.register("../avif-sw.js"), {
        wasmURL: require("dav1d.js/dav1d.wasm"),
        // forcePolyfill: true,
    });
}

function showExternalURL() {
    document.body.innerHTML = "";
    navigator.serviceWorker.addEventListener("message", e => {
        const msg = e.data;
        if (msg && msg.type === "avif-task") {
            const blob = new Blob([msg.data], {type: "image/bmp"});
            const img = document.createElement("img");
            img.src = URL.createObjectURL(blob);
            document.body.appendChild(img);
        }
    });
    fetch(extURL).then(res => res.arrayBuffer()).then(avifArr => {
        navigator.serviceWorker.controller.postMessage({
            id: "demo",
            type: "avif-task",
            data: avifArr,
        }, [avifArr]);
    });
}

function setupInteractiveDemo() {
    const loadButton = document.getElementById("load-button");
    const customItem = document.getElementById("custom-item");
    const fileInput = document.getElementById("file-input");

    function clearItems() {
        const items = document.querySelectorAll(".item:not(.user)");
        for (const item of items) {
            item.remove();
        }
    }

    function emptyCustomItem() {
        customItem.classList.add("hidden");
        while (customItem.firstChild) {
            customItem.firstChild.remove();
        }
    }

    // TODO(Kagami): Add this to library API.
    function sendDecodeRequest(file) {
        const reader = new FileReader();
        reader.onload = e => {
            const avifArr = e.target.result;
            navigator.serviceWorker.controller.postMessage({
                id: "demo",  // We have only single concurrent task so doesn't matter
                type: "avif-task",
                data: avifArr,
            }, [avifArr]);
        };
        reader.readAsArrayBuffer(file);
    }

    function showCustomImage(bmpArr) {
        const img = document.createElement("img");
        const blob = new Blob([bmpArr], {type: "image/bmp"});
        img.className = "user-img";
        img.src = URL.createObjectURL(blob);
        customItem.appendChild(img);
        customItem.classList.remove("hidden");
        loadButton.disabled = false;
    }

    loadButton.addEventListener("click", () => {
        fileInput.click();
    });

    fileInput.addEventListener("change", () => {
        loadButton.disabled = true;
        clearItems();
        emptyCustomItem();
        sendDecodeRequest(fileInput.files[0]);
        fileInput.value = null;  // Allow to select same value second time
    });

    navigator.serviceWorker.addEventListener("message", e => {
        const msg = e.data;
        if (msg && msg.type === "avif-task") {
            showCustomImage(msg.data);
        }
    });
}

const urlParams = new URLSearchParams(location.search);
const extURL = urlParams.get("src");
document.addEventListener("DOMContentLoaded", extURL ? showExternalURL : setupInteractiveDemo);

在使用avif.js时需要注意:

  • 需要使用HTTPS协议
  • 不支持在Firefox/Edge隐私模式窗口
  • 页面第一次访问需要重加载来显示静态资源

实际上,理论上浏览器不支持Service workers也是可以解析AVIF,让浏览器显示的,问题就是fetch avif格式文件不方便,此Polyfill脚本日后可能会进行支持。

小结

AVIF格式图像是一种新的图像格式,目前支持的浏览器还不多,但它将会是未来的主流之一。正如文章中提到的,在保持相同图像质量的条件之下,AVIF格式图像比以往我们熟知的图像格式,比如JPEG、PNG和WebP等要小得多。就目前为止,虽然支持AVIF格多图像的主流浏览器还不多,但我们可以使用AVIF Polyfill,让.avif在你的Web中运用起来,或者还可以考虑一些优雅的降级方式,在项目中开始使用AVIF。

AVIF给我们带来的好处是很是显的,我想在不久的将来就会得到广泛的使用。因此。我想说的是:AVIF已经离我们很近了!