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
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
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()
}
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
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())
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.
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'
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)
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)