Open a HEIC file in a hex editor. The first twelve bytes:
00 00 00 18 66 74 79 70 68 65 69 63
That is ISOBMFF — the same container standard MP4 uses. 0x18 (24 bytes) is the box size, ftyp is the file type box, heic is the major brand. A HEIC file is not an image encoding. It is a container with a brand marker, holding HEVC-encoded images.
What HEIC Actually Is
HEIC = High Efficiency Image Container. Apple's branded take on HEIF, standardized by MPEG as ISO/IEC 23008-12 in 2015. JPEG is both a compression algorithm and a file format. HEIC is only a container. The compression inside is HEVC (H.265), the same codec used for 4K video.
One HEIC file can hold:
- One primary image
- Multiple alternate images (burst shots)
- Image sequences (Live Photos: still + 3-second video)
- Alpha channels and depth maps
- 16-bit color depth per channel (JPEG caps at 8-bit)
The container uses boxes — same structure as MP4:
| Box | Purpose |
|---|---|
ftyp | File type and compatibility brands |
meta | Item metadata and properties |
mdat | Raw encoded image data (HEVC bitstream) |
iloc | Item locations within mdat |
That extensibility lets HEIC do things JPEG never could. Parsing it means understanding the box hierarchy, not just reading a raw bitstream.
Why Apple Switched
Apple did not invent HEIC. MPEG published HEIF in 2015. Apple adopted it as the iPhone default in iOS 11 (2017).
The switch was arithmetic, not marketing. A 12MP iPhone photo in JPEG is ~3.5 MB. In HEIC, ~1.8 MB. At 50 GB of free iCloud storage, that gap means roughly 14,000 extra photos. Apple sells storage tiers. The math writes itself.
There was also ecosystem alignment. Apple already used HEVC for video (H.265 in iOS 11). Reusing the same codec for still images meant shared hardware decode blocks on A-series chips, lower power draw, and one licensing path.
The Trade-offs
HEIC beats JPEG on nearly every metric except compatibility.
| Aspect | HEIC | JPEG |
|---|---|---|
| Compression efficiency | ~40–50% smaller at same quality | Baseline |
| Color depth | Up to 16-bit | 8-bit |
| Transparency | Yes | No |
| Multiple images per file | Yes | No |
| Lossless re-edit | Yes | No |
| Native Windows support | Requires HEIF extension | Universal |
| Web browser support | Safari only | Universal |
| Android support | Native on 9+ | Universal |
| Patent licensing | HEVC patent pool | JPEG is royalty-free |
HEVC sits under multiple patent pools (MPEG LA, Access Advance). Apple covers the fees for iOS users. Third-party vendors do not have that luxury. That uncertainty is a large part of why adoption outside the Apple ecosystem stalled.
Detecting HEIC by Reading the File Signature
Do not trust the .heic extension. Read the first 32 bytes and parse the ftyp box.
Exact byte layout of a valid HEIC header:
Bytes 0–3: Box size (big-endian uint32)
Bytes 4–7: Box type: "ftyp" (0x66 0x74 0x79 0x70)
Bytes 8–11: Major brand: "heic" or "heif" or "mif1"
Bytes 12–15: Minor version (usually 0x00000000)
Bytes 16+: Compatible brands list (e.g., "mif1", "heic", "MiHE")
TypeScript in the browser:
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)
}
Our converter runs this check before attempting decode. Wrong brand = instant rejection, no wasted 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"}
Higher-level option with filetype:
import filetype
kind = filetype.guess("photo.heic")
if kind and kind.extension in ("heic", "heif"):
print(kind.mime) # image/heic
Or pillow-heif:
from pillow_heif import is_heif
if is_heif("photo.heic"):
# valid container
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);
}
With fileinfo:
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file('photo.heic');
// image/heic or image/heif
ImageMagick CLI
ImageMagick 7+ with libheif:
magick identify -verbose photo.heic | grep "Format:"
# Format: HEIC (High Efficiency Image Container)
Without libheif, you get no decode delegate for this image format. On most Linux distros, libheif-examples provides heif-convert as a fallback.
The Conversion Path
Knowing a file is HEIC does not open it. Windows needs the HEIF Image Extensions. Most browsers refuse to render HEIC in <img> tags. Android 9+ handles it natively; older devices do not.
The fix: convert. Our HEIC to JPG converter runs the entire pipeline in your browser:
- Drop files — single photos or entire folders, batch processing automatic.
- Signature validation — the exact byte check above runs on every file. Non-HEIC files are rejected immediately with a clear mismatch message.
- Client-side decoding — a WebAssembly build of libheif runs locally. No uploads. No server.
- Quality-preserving output — JPG at 90% quality keeps original resolution and color accuracy.
- Batch download — individual files or a single ZIP archive.
For lossless output with transparency: HEIC to PNG. For web-optimized sizes: HEIC to WebP.
Your files never leave your device. That is the point.


