HEIC
HEIC is the format iPhones and most Android phones save photos in: HEIF files whose images are coded with HEVC (H.265). HEVC is covered by patents, so Egami reads and writes HEIC through a separate package, Egami.Heic, that an app opts in to. The core Egami package contains no HEVC code and never depends on Egami.Heic.
Egami.Heic decodes HEIC images with a managed HEVC decoder: intra and inter pictures of the Main, Main 10, Main Still Picture
and format range extensions profiles, up to 12 bits and from 4:0:0 to 4:4:4. Gain maps, the photo's other images (thumbnails,
depth maps, mattes) and image sequences are read too, and HevcStreamDecoder decodes HEVC video streams. Saving to HEIC
codes the image with a managed HEVC intra encoder.
Without Egami.Heic
Core Egami recognizes HEIC by its brands (heic, heix, hevc and the other HEVC brands) and reads the container, so
Raster.Probe gives the size, the natural pixel type and the orientation. Raster.Load throws an UnsupportedFormatException
that names the package.
// Core Egami detects HEIC and reads its container: size, natural pixel type and orientation.
RasterInfo info = Raster.Probe("photo.heic");
string name = info.Format.Name; // "HEIC"
// Loading needs the Egami.Heic package: the exception says so.
try
{
using Raster image = Raster.Load("photo.heic");
}
catch (UnsupportedFormatException e)
{
Console.WriteLine(e.Message);
}
The size is the image's after its clean aperture (clap) crop; its rotation and mirroring (irot and imir) are given as
RasterInfo.Orientation, as for AVIF. The natural pixel type follows the bit depth: Rgb8 for 8-bit files, Rgb16 for 10 and
12 bits, with alpha when the file has an alpha image, and gray for monochrome.
Registering Egami.Heic
Nothing registers Egami.Heic on its own. WithHeic(), an extension method in HeicContextExtensions, returns a context with
Egami.Heic's HeicFormat.Info in place of core Egami's HEIC entry; pass it to the calls, or make it the default once at startup
with EgamiContext.SetDefault(new EgamiContext().WithHeic()).
// Opt in to HEVC once: register Egami.Heic in a context (at startup, EgamiContext.SetDefault(new EgamiContext().WithHeic())
// makes it the default for every call).
EgamiContext heic = new EgamiContext().WithHeic();
RasterInfo info = Raster.Probe("photo.heic", heic);
bool fromPackage = info.Format == HeicFormat.Info;
Egami.Heic ships with every Egami release, at the same version, and depends on exactly that version of Egami.
Loading
With Egami.Heic registered, HEIC loads like any format, in its natural pixel type.
// An iPhone photo: a grid of HEVC tiles, decoded in parallel and converted to RGB as heif-dec converts them.
using Raster photo = Raster.Load("photo.heic", heic);
photo.Apply(ops => ops.AutoOrient()); // irot and imir come as the orientation
photo.Save("photo.jpg");
- Grids. Phone photos are grids of tiles (an iPhone photo has 48 or more). The tiles decode in parallel, on up to
EgamiContext.MaxParallelismthreads, and aReaderOptions.Regiondecodes only the tiles it touches. Tiles and wavefront rows inside one HEVC picture decode in parallel too. The pixels are the same at any number of threads. - Color. The planes are converted to RGB as libheif's heif-dec converts them, so
Raster.Loadgives the same samples. The phone photos and HEVC-coded files of Egami's test corpus equal heif-dec's output sample for sample, except a clean aperture at half-sample offsets, which Egami crops at whole samples. The colors come from thecolrproperty, or else from the HEVC stream's own color description. An ICC profile is kept inRasterMetadata.IccProfile. - Geometry and alpha. The clean aperture (
clap) is cropped; rotation and mirroring (irotandimir) are kept as the orientation, whichAutoOrientapplies. An alpha image loads as straight alpha. Overlays (iovl) are composed. - Metadata. Exif and XMP items are read into
RasterMetadata.ExifandRasterMetadata.Xmp.
The decoded HEVC planes are available without the conversion to RGB, as for AVIF and JPEG (see Planar YUV):
// The HEVC planes themselves, without the conversion to RGB: luma, then chroma at the file's subsampling.
using YuvRaster planes = YuvRaster.Load("photo.heic", heic);
ChromaFormat chroma = planes.ChromaFormat; // Yuv420 for phone photos
int depth = planes.BitDepth; // 8, or 10 for HDR photos
Every picture of the HEVC conformance streams (JCT-VC HEVC_v1 and RExt) in these profiles decodes bit-exact to HM, the reference decoder, and matches its decoded picture hash.
HDR photos
iPhones store an HDR photo as an SDR image with a gain map: Apple's HDRGainMap auxiliary image (its headroom in the maker note
or the map's XMP), and on newer iPhones an ISO 21496-1 tone-map item as well, which is preferred. Load gives the SDR image with
the map in RasterMetadata.GainMap; ReaderOptions.Hdr gives the HDR rendition for a display's headroom, as for
Ultra HDR JPEGs.
// An iPhone photo's gain map (Apple's HDRGainMap, or ISO 21496-1 on newer iPhones) comes with the SDR image; ask for the HDR
// rendition for a display with 2 stops of headroom.
using Raster sdr = Raster.Load("portrait.heic", heic);
bool hasGainMap = sdr.Metadata.GainMap is not null;
using Raster hdr = Raster.Load("portrait.heic", heic, new ReaderOptions { Hdr = new HdrDecode { Headroom = 2 } });
A gain map that cannot be read (Apple's without a headroom, a damaged tone-map item) never fails the load: the image loads as
SDR with a Heic.GainMapDropped warning.
Thumbnails, depth maps and mattes
HeicDetails.Items lists every item of the file with its HeicItemRole, what it is to the photo: the primary image, thumbnails, the gain map,
depth maps and other auxiliary images (an iPhone's portrait, skin and sky mattes), Exif and XMP. HeicImage.ReadItem reads an
image item on its own, with its own color and crop, and its own rotation as the orientation.
// The other images a photo holds: thumbnails, gain maps, depth maps and mattes (portrait, skin, sky).
HeicDetails details = (HeicDetails)sdr.Metadata.FormatDetails!;
HeicItem sky = details.Items.First(i => i.AuxiliaryType == "urn:com:apple:photo:2020:aux:semanticskymatte");
using Raster matte = HeicImage.ReadItem("portrait.heic", sky.Id, context: heic);
matte.Apply(ops => ops.AutoOrient());
matte.Save("sky-matte.png");
Image sequences
A HEIC image sequence (the hevc and hevx brands), such as a burst or an animation, loads every frame, as an animation: the
track's pictures in output order, inter-coded ones too, equal to libheif's heif-dec -S.
// A burst or animated HEIC file: every frame, inter-coded ones too, in output order.
using Raster burst = Raster.Load("burst.heic", heic);
int frames = burst.FrameCount;
HEVC streams
HevcStreamDecoder decodes an HEVC video stream an access unit at a time, as Egami.Video's HEVC support does: an Annex B byte
stream, or an MP4 or Matroska track's samples after SetConfiguration with its hvcC record. Pictures come out in output order
into a YuvRaster, cropped to the stream's conformance window, with their order count, NAL unit type and color in
HevcFrameInfo (with its HevcPictureKind); Flush lets the last ones out at the end of the stream. Decoding starts at an IRAP picture; leading pictures of
a CRA picture that starts the stream are left out, and only the base layer of a layered stream decodes. In HevcDecoderOptions,
ConcealErrors keeps a
picture with damaged slice data as far as it decoded, and CheckPictureHashes checks each picture against its decoded picture
hash.
// An HEVC stream from any source: Annex B here, or SetConfiguration with an MP4 track's hvcC and its samples.
using HevcStreamDecoder decoder = new(context: heic);
decoder.Send(File.ReadAllBytes("clip.h265"));
decoder.Flush(); // the end of the stream: the last pictures come out
int shown = 0;
while (decoder.NextFrame is HevcFrameInfo next)
{
using YuvRaster<byte> frame = YuvRaster.Create(next.Width, next.Height, next.Chroma, YuvColor.Bt709Limited);
decoder.TryReceiveFrame(frame, out HevcFrameInfo info); // info.Poc, info.Kind, info.Color
shown++;
}
Writing
With Egami.Heic registered, Save writes HEIC for the .heic and .heif extensions and the image/heic MIME type, through
HeicWriterOptions. Core Egami alone cannot write HEIC: its UnsupportedFormatException names the package.
// With Egami.Heic registered, Save picks HEIC from the extension: lossy 4:2:0 at quality 50 on heif-enc's scale, effort 4.
duck.Save("duck-q50.heic");
// Smaller and softer, or every sample exact (4:4:4 RGB with the identity matrix, which needs a range extensions decoder).
duck.Save("duck-q30.heic", new HeicWriterOptions { Quality = 30 });
duck.Save("duck-lossless.heic", new HeicWriterOptions { Lossless = true });
- Quality.
Qualityruns from 0 to 100 on heif-enc's scale, 50 by default, so the same number gives about the file size heif-enc gives with x265. Files are about as small as x265's at the same SSIMULACRA2 score. A built-in perceptual tuning gives busy areas a coarser quantizer and flat ones a finer one. - Effort.
Effortruns from 0 (fastest) to 10 (smallest files), 4 by default: about the size and the time of x265's medium preset. Higher efforts search more intra modes, block sizes and transform splits. - Format. Lossy images are 4:2:0 at 8 bits (Main) or 10 bits for deeper pixels and HDR (Main 10), which every phone, PC and
HEVC decoder reads.
Chromaasks for 4:4:4, 4:2:2 or 4:0:0 andBitDepthfor 12 bits; those use HEVC's format range extensions, which some phones and operating systems cannot decode.Losslesskeeps every sample: 4:4:4 RGB with the identity matrix, coded without transform or quantization, which needs a range extensions decoder too. - Color. RGB is converted with the BT.601 matrix for SDR images (as iPhones and libheif write) and BT.2020 for PQ and HLG,
full range, with sharp chroma downsampling from effort 5. The
colrbox gives the code points; an ICC profile they cannot describe is written too. PQ and HLG images getclliandmdcvfromRasterMetadata.Hdr. - Alpha. Transparency is an auxiliary image coded as Main 4:2:0, which every decoder reads, straight and exact by default;
AlphaQualitybelow 100 codes it lossy. - Size. An image up to 8,912,896 samples (HEVC level 5.1, such as 4096 x 2176) is one picture; a larger one is a grid of 512 x
512 cells, as iPhones write.
GridTileSizeasks for a grid of other cells. Cells and the picture's CTB rows (wavefronts) code in parallel; the file is the same on any number of threads. - Metadata and extras. Exif and XMP are kept as items, the orientation as
irotandimir(the pixels are not rotated), a gain map as an ISO 21496-1 tone-map item, andThumbnailSizeadds a thumbnail.TargetSizesearches the quality for the largest file not above a size. - Not written. Image sequences: a raster of several frames writes its first, with a
Save.FramesDroppedwarning.
// A thumbnail for file browsers, iPhone-style grid cells, and a file of at most 4 KB.
duck.Save("duck-thumb.heic", new HeicWriterOptions { ThumbnailSize = 64, GridTileSize = new Size(64, 64) });
duck.Save("duck-4k.heic", new HeicWriterOptions { TargetSize = 4_000 });
Every stream Egami.Heic writes decodes to the encoder's own reconstruction in Egami's decoder, and libheif's heif-dec gives the same pixels.
Patents
Egami.Heic decodes and encodes HEVC (H.265), a format covered by patents. Egami's authors grant no patent rights and are not lawyers. Whether you need a patent licence depends on your country and on how you use and distribute this package; commercial products may need one from the HEVC patent pools or holders. The core Egami package contains no HEVC code and does not depend on this package.