Apr 08, 2025
2 min read
Rust,

heif/heic 格式到其他图片的转换

本文介绍了 HEIF/HEIC 格式图片的转换方法,因其基于 HEVC 技术存在专利问题,兼容性较差。在 Rust 中可通过 `libheif-rs` 解码 HEIF 图片并借助 `image` crate 转换为其他格式。前端则可使用 `heic-convert` 实现类似功能,重点在于构造符合要求的 buffer 数据进行格式转换。

如果你使用的是 iPhone 并且相机拍摄格式设置选项是“高效”的话,你会得到 HEIF 格式的图片。HEIF/HEIC 使用了基于 HEVC(High Efficiency Video Coding,高效视频编码,也称 H.265) 的技术,虽然能够以比传统 JPEG 格式更高的压缩率存储图像,但是,哪怕在 2025 年,它的兼容性也很差。

最大的原因是 HEVC(H.265)是有专利问题的。开源项目不可能支付得起大额的专利费用。因此在开源上, HEIF 图片支持奇差。目前 HEIF 我所知道的唯一的编码/解码器应该是 c++ 实现的 libheif

虽然它的兼容性差,但是由于苹果的庞大用户,在面对 C 端做应用的时候,就不得不为苹果用户考虑 HEIF 图片的兼容的问题。

Rust 中使用的也是基于 libheif 的绑定库:

libheif-rs = { version = "2.1", default-features = false, features = ["v1_17"] }
image = {version = "0.25.6"}

由于是绑定库,因此需要在系统里安装 libheif。比如 Windows:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.bat
./vcpkg integrate install
./vcpkg install libheif

ubuntu/debian 直接走命令: sudo apt install libheif-dev

macOS 直接使用 brew 安装: brew install libheif

由于 image crate 集成了大部分常用的图片格式,因此我们的目标是,使用 libheif-rs 解码 heif 图片,并转换成 image 对象,由 image 对象自然转换其他常用的格式。

解码图片:

	let lib_heif = LibHeif::new();
    //let ctx = HeifContext::read_from_file("./data/test.heif")?; //如果你从文件中加载的话
    let ctx = HeifContext::read_from_bytes(data)?;
    let handle = ctx.primary_image_handle()?;
    //解码HEIF图像为RGB格式
    let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;

这样我们就得到了图像像素数据:

let planes = image.planes();

将像素数据转换为RgbImage格式:

let img_buf = image::RgbImage::from_raw( image.width(), image.height(), planes.interleaved.unwrap().data.to_vec(), ) .context("Invalid image data")?;

剩下的事情就简单了,比如我们保存为 jpg 格式:

img_buf.save_with_format("output.jpg", image::ImageFormat::Jpeg)?;

题外话

曾经有幸在前端上处理过 heif 的问题。

前端有一个 heic2any 的包。看起来它的文档是最详细的,但是这个包似乎有问题,会有一些错误。好像会得到类似于HEIC/HEIF files are not supported 的奇怪错误。

最好的方式是使用 heic-convert 这个包。 heic-convert 的问题是,它只提供了 nodejs 环境的调用文档。

具体的 browser 实现如下:

import convert from 'heic-convert/browser'

let elementFiles = (e.target as HTMLInputElement).files;
const file = elementFiles[0];

const buffer = new Uint8Array(await file.arrayBuffer())
const outputBuffer = await convert({
buffer: buffer, // the HEIC file buffer
format: 'JPEG', // output format
quality: 1      // the jpeg compression quality, between 0 and 1
});
const blob = new Blob([outputBuffer], {type: "image/jpeg"});
const heicFile = new File([blob], 'example.jpg', {type: "image/jpeg"});

主要重点在于构造 buffer 的数据上, convert 的 buffer 签名是 ArrayBufferLike