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