DEV Community

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

Posted on Edited on Originally published at coding-now.com

Windows can decode (and encode) HEIC without ImageMagick - and the browser ships no decoder

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 ship no HEIC decoder

No browser decodes HEIC on its own. 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

Correction (2026-09-28): a reader on r/PowerShell pointed out that this measures the absence of a built-in decoder, not an impossibility — libheif compiled to WebAssembly decodes HEIC client-side, and heic2any / libheif-js are exactly that (2.6MB and 8.4MB unpacked). The reason converter sites upload is that download cost, not a browser limitation. Corrected here and in the original article.

drawImage on that element throws InvalidStateError too. So a site that converts HEIC either ships its own decoder or uploads your file to a server, and in practice nearly all of them upload. 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 Only with a bundled WASM decoder (heic2any 2.6MB, libheif-js 8.4MB)
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)