用十六进制编辑器打开一个 HEIC 文件。前十二个字节:
00 00 00 18 66 74 79 70 68 65 69 63
这是 ISOBMFF —— MP4 使用的同一个容器标准。0x18(24 字节)是 box 大小,ftyp 是文件类型 box,heic 是主品牌。HEIC 文件不是图像编码,它是一个带品牌标记的容器,内部封装 HEVC 编码的图像。
HEIC 到底是什么
HEIC = High Efficiency Image Container。Apple 对 HEIF 的品牌化实现,MPEG 于 2015 年标准化为 ISO/IEC 23008-12。JPEG 同时是压缩算法和文件格式,HEIC 只是一个容器。内部压缩用的是 HEVC(H.265),与 4K 视频同一个编解码器。
单个 HEIC 文件可以存储:
- 一幅主图像
- 多幅备选图像(连拍)
- 图像序列(实况照片:静态图 + 3 秒视频)
- Alpha 通道和深度图
- 每通道 16 位色深(JPEG 上限为 8 位)
容器使用 box 结构 —— 与 MP4 完全一致:
| Box | 作用 |
|---|---|
ftyp | 文件类型和兼容品牌 |
meta | 项目元数据和属性 |
mdat | 原始编码图像数据(HEVC 码流) |
iloc | 项目在 mdat 中的位置 |
正是这种可扩展性让 HEIC 做到了 JPEG 永远做不到的事。解析它需要理解 box 层级,而不是简单读取原始码流。
Apple 为什么切换
Apple 没有发明 HEIC。MPEG 于 2015 年发布了 HEIF,Apple 在 2017 年的 iOS 11 中将其采纳为 iPhone 默认相机格式。
切换的驱动力是算术,不是营销。一张 1200 万像素的 iPhone 照片,JPEG 约 3.5 MB,HEIC 约 1.8 MB。在 50 GB 的免费 iCloud 存储档位下,这个差距意味着大约多出 14,000 张照片。Apple 卖存储档位,这笔账不用算第二遍。
另一个因素是生态对齐。Apple 早已在视频中采用 HEVC(iOS 11 中的 H.265)。对静态图像复用同一个编解码器,意味着 A 系列芯片上可以共享硬件解码模块、降低功耗,并且只需要一条授权路径。
利弊权衡
HEIC 在几乎所有指标上都优于 JPEG,除了兼容性。
| 维度 | HEIC | JPEG |
|---|---|---|
| 压缩效率 | 同等画质下小约 40–50% | 基准 |
| 色深 | 最高 16 位 | 8 位 |
| 透明度 | 支持 | 不支持 |
| 单文件多图 | 支持 | 不支持 |
| 无损再编辑 | 支持 | 不支持 |
| Windows 原生支持 | 需安装 HEIF 扩展 | 通用 |
| Web 浏览器支持 | 仅 Safari | 通用 |
| Android 支持 | 9 以上原生支持 | 通用 |
| 专利授权 | HEVC 专利池 | JPEG 免版税 |
HEVC 被多个专利池覆盖(MPEG LA、Access Advance)。Apple 替 iOS 用户支付了授权费,第三方厂商没有这份待遇。这种不确定性是 Apple 生态系统之外采用停滞的主要原因。
通过读取文件签名检测 HEIC
不要信任 .heic 扩展名。读取前 32 字节,解析 ftyp box。
有效 HEIC 文件头的精确字节布局:
Bytes 0–3: Box 大小(大端 uint32)
Bytes 4–7: Box 类型: "ftyp"(0x66 0x74 0x79 0x70)
Bytes 8–11: 主品牌: "heic" 或 "heif" 或 "mif1"
Bytes 12–15: 次版本号(通常为 0x00000000)
Bytes 16+: 兼容品牌列表(如 "mif1"、"heic"、"MiHE")
浏览器端 TypeScript:
async function isHeic(file: File): Promise<boolean> {
const buffer = await file.slice(0, 32).arrayBuffer()
const bytes = new Uint8Array(buffer)
if (String.fromCharCode(...bytes.slice(4, 8)) !== "ftyp") return false
const brand = String.fromCharCode(...bytes.slice(8, 12))
return ["heic", "heif", "mif1", "msf1"].includes(brand)
}
我们的转换器在尝试解码前会执行这项检查。品牌不对 = 立即拒绝,不浪费 CPU。
Python
def is_heic(path: str) -> bool:
with open(path, "rb") as f:
header = f.read(32)
if len(header) < 12:
return False
if header[4:8].decode("ascii", errors="ignore") != "ftyp":
return False
return header[8:12].decode("ascii", errors="ignore") in {"heic", "heif", "mif1", "msf1"}
更高级的检测可以用 filetype:
import filetype
kind = filetype.guess("photo.heic")
if kind and kind.extension in ("heic", "heif"):
print(kind.mime) # image/heic
或使用 pillow-heif:
from pillow_heif import is_heif
if is_heif("photo.heic"):
# 有效容器
pass
Go
func isHeic(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 32)
if _, err := f.Read(buf); err != nil {
return false
}
if string(buf[4:8]) != "ftyp" {
return false
}
brand := string(buf[8:12])
return brand == "heic" || brand == "heif" || brand == "mif1" || brand == "msf1"
}
PHP
function isHeic(string $path): bool {
$header = file_get_contents($path, false, null, 0, 32);
if (strlen($header) < 12) return false;
if (substr($header, 4, 4) !== 'ftyp') return false;
return in_array(substr($header, 8, 4), ['heic', 'heif', 'mif1', 'msf1'], true);
}
使用 fileinfo:
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file('photo.heic');
// image/heic 或 image/heif
ImageMagick CLI
ImageMagick 7+ 在编译时带上 libheif 后支持 HEIC:
magick identify -verbose photo.heic | grep "Format:"
# Format: HEIC (High Efficiency Image Container)
如果未编译 libheif,会返回 no decode delegate for this image format。大多数 Linux 发行版上,安装 libheif-examples 会提供 heif-convert 作为替代方案。
转换路径
知道一个文件是 HEIC,不代表你能打开它。Windows 需要 HEIF Image Extensions。大多数浏览器拒绝在 <img> 标签中渲染 HEIC。Android 9+ 原生支持,老旧设备不支持。
解决方式:转换。我们的 HEIC 转 JPG 转换器 在浏览器中处理整个流程:
- 拖拽文件 — 单张照片或整个文件夹,自动批量处理。
- 签名验证 — 上述字节检查对每个文件运行。非 HEIC 文件立即拒绝并给出清晰提示。
- 客户端解码 — libheif 的 WebAssembly 版本在本地运行。不上传。不触碰服务器。
- 保质量输出 — 90% 质量的 JPG 保留原始分辨率和色彩精度。
- 批量下载 — 单个文件或一个 ZIP 压缩包。
需要带透明度的无损输出:HEIC 转 PNG。需要针对 Web 优化的体积:HEIC 转 WebP。
你的文件永远不会离开你的设备。这才是重点。


