DEV Community

코딩나우(하늘아래)
코딩나우(하늘아래)

Posted on Originally published at coding-now.com

Windows can decode (and encode) HEIC without ImageMagick - but your browser can't decode it at all

Someone hands you a folder of iPhone photos, every file is .heic, and the form you need to upload them to wants JPG. The usual advice is "install ImageMagick" or "use this website". Neither is necessary on Windows, and the website advice hides something worth knowing about browsers.

Renaming is not converting (the 10-second check)

$ head -c 16 photo.heic | xxd
00000000: 0000 0020 6674 7970 6865 6963  ... ftypheic
Enter fullscreen mode Exit fullscreen mode

Copy it to photo.jpg and those bytes don't move. Windows agrees about what it really is:

Add-Type -AssemblyName PresentationCore
$d = [System.Windows.Media.Imaging.BitmapDecoder]::Create([Uri]"C:\tmp\renamed.jpg", 'None', 'OnLoad')
$d.CodecInfo.FriendlyName   # -> Microsoft HEIF Decoder
Enter fullscreen mode Exit fullscreen mode

Viewers sniff content, so the renamed file opens fine — right up until something validates the format.

The no-install route: WIC from PowerShell

Windows has had a HEIF codec since the Store extensions shipped, and WIC exposes it to .NET, so a whole folder is a loop:

Add-Type -AssemblyName PresentationCore

Get-ChildItem "C:\Users\you\Pictures\*.heic" | ForEach-Object {
    $f = [System.Windows.Media.Imaging.BitmapDecoder]::Create([Uri]$_.FullName, 'None', 'OnLoad').Frames[0]
    $e = New-Object System.Windows.Media.Imaging.JpegBitmapEncoder
    $e.QualityLevel = 90
    $e.Frames.Add($f)
    $o = [System.IO.File]::Create([IO.Path]::ChangeExtension($_.FullName, '.jpg'))
    $e.Save($o); $o.Close()
}
Enter fullscreen mode Exit fullscreen mode

On my machine (Windows 11 25H2, build 26200.9457) that reported Microsoft HEIF Decoder, handed back a Bgr32 frame, and turned a 75,897-byte HEIC into a 59,241-byte quality-90 JPEG.

Two Store packages matter here: Microsoft.HEIFImageExtension (1.2.48 here) is the free one, and because the picture inside a HEIC is HEVC-compressed, some machines also need Microsoft.HEVCVideoExtension (2.5.33 here), which is a paid Store item unless your OEM shipped it. Check what you have:

Get-AppxPackage | Where-Object { $_.Name -match "HEIF|HEVC" } | Select-Object Name, Version
Enter fullscreen mode Exit fullscreen mode

Bonus: Windows will also write HEIC

WPF has no HEIF encoder class, but WinRT does, and PowerShell can reach it. This is how I produced a real .heic to test against without owning an iPhone:

Add-Type -AssemblyName System.Runtime.WindowsRuntime
# ... AsTask/Await helpers omitted ...
$encId   = [Windows.Graphics.Imaging.BitmapEncoder]::HeifEncoderId
$encoder = AwaitOp ([Windows.Graphics.Imaging.BitmapEncoder]::CreateAsync($encId, $dstStream)) ([Windows.Graphics.Imaging.BitmapEncoder])
$encoder.SetSoftwareBitmap($bitmap)
AwaitAct ($encoder.FlushAsync())
Enter fullscreen mode Exit fullscreen mode

A 1000x380 PNG came back out as a 75,897-byte .heic whose first bytes were ftypheic. Useful for fixtures.

The part that surprised me: browsers can't decode HEIC

I assumed a client-side converter was possible and it isn't. In Chrome 152:

img.src = "test-photo.heic";
// img.naturalWidth === 0, img.complete === true

await createImageBitmap(await (await fetch("test-photo.heic")).blob());
// InvalidStateError: The source image could not be decoded.
Enter fullscreen mode Exit fullscreen mode

drawImage on that element throws InvalidStateError too. So every "HEIC to JPG" site you find is uploading the file to a server and converting it there — not a privacy nitpick, an architectural consequence. If the photos are of people or documents, that matters.

(For contrast, the same page loaded a PNG at 1000x380 without complaint, so this is about the format, not the page.)

Python: Pillow alone won't open it

>>> Image.open("test-photo.heic")
UnidentifiedImageError: cannot identify image file 'test-photo.heic'
Enter fullscreen mode Exit fullscreen mode

You need the plugin that registers the opener:

import pillow_heif
from PIL import Image
from pathlib import Path

pillow_heif.register_heif_opener()

for src in Path(".").glob("*.heic"):
    Image.open(src).convert("RGB").save(src.with_suffix(".jpg"), quality=90)
Enter fullscreen mode Exit fullscreen mode

That is the portable option — and the one to pick if you need EXIF handling or transparency rules you control, rather than whatever WIC's default frame gives you.

Quick reference

Need Use
A folder, nothing installed, Windows PowerShell + WIC
Cross-platform or in a pipeline Pillow + pillow-heif
A test fixture, no iPhone WinRT HeifEncoderId
Client-side in a browser Not possible — HEIC doesn't decode
Photos of people or documents Anything except an upload-based site

One more thing worth setting expectations on: the JPEG is usually bigger than the HEIC, because HEIC is the more efficient format. Converting "to save space" is backwards; resize instead.

Everything above was measured on Windows 11 25H2 with the HEIF and HEVC extensions installed — the codec name, the frame format, the byte counts, the Chrome errors and the Pillow failure are all from that run, not from memory.


The non-developer version, including the iPhone setting that stops HEIC files appearing in the first place: https://www.coding-now.com/en/guides/heic-to-jpg?utm_source=devto

Has anyone found a shipping browser that decodes HEIC, or a flag that enables it?

Top comments (0)