How do HEIF, JPEG, PNG and animated images differ, and what does ImageIO let you do with them?

18.5.1 · L3 · Interview

Predict first

A three-frame GIF and a PNG are written and read back. What do their source types and frame count say, and are HEIC and HEIF supported?

import ImageIO
import CoreGraphics
import UniformTypeIdentifiers
import Foundation

let ctx = CGContext(data: nil, width: 8, height: 8, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
let cg = ctx.makeImage()!
let gif = NSMutableData()
let d = CGImageDestinationCreateWithData(gif, UTType.gif.identifier as CFString, 3, nil)!
for _ in 0..<3 { CGImageDestinationAddImage(d, cg, nil) }
CGImageDestinationFinalize(d)
let src = CGImageSourceCreateWithData(gif as CFData, nil)!
print(CGImageSourceGetType(src) as String?, CGImageSourceGetCount(src))
let png = NSMutableData()
let pd = CGImageDestinationCreateWithData(png, UTType.png.identifier as CFString, 1, nil)!
CGImageDestinationAddImage(pd, cg, nil)
CGImageDestinationFinalize(pd)
print(CGImageSourceGetType(CGImageSourceCreateWithData(png as CFData, nil)!) as String?)
print((CGImageDestinationCopyTypeIdentifiers() as! [String]).contains("public.heic"))
  • Optional("com.compuserve.gif") 3, then Optional("public.png"), then true
  • Optional("public.gif") 1, then Optional("public.png"), then false
  • nil 3, then nil, then true

Mental model. Image formats are different packaging. JPEG is compressed photo paper, PNG a lossless envelope, HEIC a slimmer container that holds extras like depth, and an animated GIF is a flipbook, whose pages must be unfolded in memory to be shown quickly.

ImageIO (CGImageSource and CGImageDestination) is the low-level engine under UIImage, AsyncImage, Photos and Quick Look: it reads and writes image files without decoding the whole bitmap, exposes metadata, and supports many formats. The formats: JPEG (lossy, no transparency, universal, photos), PNG (lossless, alpha, good for graphics and screenshots, large for photos), HEIF/HEIC (the modern container Apple cameras use: about half the size of JPEG at the same quality, supports alpha, depth, multiple images and 10-bit colour; the system encodes and decodes it, but older or non-Apple clients may not, so convert for sharing or web use), GIF and APNG and animated WebP (animation, with GIF limited to 256 colours), and AVIF and JPEG XL on newer systems. Use the type identifier (CGImageSourceGetType returns public.png, com.compuserve.gif) and a UTType to detect and choose a format, and CGImageSourceCopyTypeIdentifiers() to list what the system supports (HEIC is supported on modern devices). Reading properties (CGImageSourceCopyPropertiesAtIndex) gives pixel size, orientation, colour model, EXIF, GPS and the GIF frame delay without decoding the pixels, which is how you read dimensions cheaply before deciding how to load.

Animated images are multi-frame sources: a three-frame GIF yields CGImageSourceGetCount(src) == 3, and you create each frame with CGImageSourceCreateImageAtIndex plus the per-frame delay from the properties; UIImage.animatedImage and SwiftUI’s image views handle simple cases, but a long animation decoded to bitmaps costs frames × width × height × 4 bytes of memory, so decode lazily and cache a window of frames, or prefer a video (HEVC, H.264, or an animated sticker format) for long or large animations, which is smaller and hardware decoded. Writing: create a destination with a type identifier and image count, add images (with per-frame properties such as GIF delay or JPEG/HEIC compression quality via kCGImageDestinationLossyCompressionQuality), and finalise. Practical rules: downsample at decode (the thumbnail API, see the image pipeline question) instead of loading full images, respect the orientation metadata, preserve or strip metadata deliberately (location data is a privacy risk when sharing), prefer HEIF for storage and JPEG for compatibility, avoid re-encoding lossy formats repeatedly, and choose a colour space (Display P3 for wide-gamut photos) consciously. For wide colour and HDR (gain maps, 10-bit) use the matching APIs and check the display capability.

A GIF source reports its type and one image per frame, a PNG reports its own type, and the system can encode HEIC.

Common traps

  • Decoding a long animated GIF fully into memory. Frames × pixels × 4 bytes adds up fast; use video or decode lazily.
  • Sharing HEIC with services that cannot read it. Convert to JPEG when compatibility matters.

They might follow up with

  • How do you read an image’s size without decoding it? CGImageSourceCopyPropertiesAtIndex returns the pixel dimensions from the header.
  • How many frames does a GIF source report? CGImageSourceGetCount, one per frame.

Related questions

  • 18.5.2 How do Core Image and Metal relate, why is Core Image lazy, and how do you build a smooth image-processing…
  • 18.3.2 What are CVPixelBuffer, CMSampleBuffer and IOSurface, and how do you process frames without copying them?
  • 12.7.1 How do you design an image loading pipeline: decoding, downsampling, caching and de-duplication?

How do Core Image and Metal relate, why is Core Image lazy, and how… →