如果你使用的是 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。