DEV Community

AsyncMonk
AsyncMonk

Posted on

HEIC to JPG made a sample photo 38% bigger: 3 checks before handing client photos over

A common small-project moment: a client zips up photos straight off their iPhone and asks you to put them on the site. Every file ends in .heic. The reflex is to batch-convert to JPG and move on, because JPG opens everywhere. I wanted to know what that reflex actually costs, so I ran a sample through it. The HEIC went in at 745.1 KB and the JPG came out at 1.01 MB, 38% bigger than the file I started with.

About the sample, since it matters: it is not an iPhone original. It's a CC0 photo from Wikimedia Commons (shot on an iPhone 6 in 2015, 3264×2448) that I encoded to HEIC with the macOS system encoder. I made one copy tagged as a portrait shot (EXIF orientation 6) and one upscaled to 4032×3024. HDR gain maps and Live Photos weren't part of it, because I have no real device originals to test those with.

That gave me three things I now check before anything goes back to a client.

1. Can the other side open HEIC at all?

In the browser engines bundled with Playwright on macOS, a plain img tag pointing at the HEIC failed in Chromium 149 and Firefox 151. WebKit 26.5 displayed it with the right orientation, but it leans on macOS's own decoder, so I don't read that as "Safari everywhere", and I didn't test Windows or phones. On Windows, Microsoft says opening .heic needs both the HEIF Image Extensions and the HEVC Video Extensions, and the HEVC one may be a paid item in the Store. Server side, Pillow 11.3 without the pillow-heif plugin throws UnidentifiedImageError, and ffmpeg 7.1's default command gave me a 512×512 image, which is one tile of the full picture. Both can handle HEIC once set up properly, but I don't get to set up the client's stack. If any step in their pipeline is unknown, I don't ship HEIC.

2. Is the portrait still a portrait downstream?

There are two ways to store a portrait shot. You can rotate the pixels, or you can keep landscape pixels and write an orientation tag that viewers are expected to honour. macOS sips does the second. Its JPG of my portrait sample is 3264×2448 pixels with EXIF orientation 6. Preview on the Mac shows it upright, so it looks fine locally. Anything downstream that ignores EXIF, like a thumbnail script, a crop step that decides by width vs height, or an old upload widget, will show it lying on its side. Opening that file with Pillow gives a size of (3264, 2448), and applying the tag is left to you.

The browser converter I compared against, imging, rotates the pixels instead: 2448×3264 out, orientation written as 1, and a mean per-pixel difference of 0.7 to 1.5 against the correctly oriented sips render, so it isn't rotated twice. Its result panel prints the output dimensions, which makes the check quick.

imging's result panel after converting the 745.1 KB HEIC to JPG: 2448×3264 output and a warning that it is 38% larger than the original. Sample: CC0 Wikimedia Commons photo (iPhone 6), encoded to HEIC with the macOS system encoder

3. How big did it get?

This is the one I had wrong. HEIC compresses far better than JPEG, so matching the look in JPEG costs bytes. The converter auto-picked quality 88 for this photo (it filed the airport shot under its graphics/text preset), and that is where the +38% came from. The landscape copy went +37%. The 4032×3024 copy went from 840.1 KB to 1.32 MB, +61%, and even at quality 80 it was still 982.9 KB, +17%.

File size of the same portrait HEIC converted to JPG, WebP and PNG. Sample: CC0 Wikimedia Commons photo (iPhone 6), encoded to HEIC with the macOS system encoder; numbers apply to this sample only

For the 3264×2448 portrait: JPG at quality 80 was 742.2 KB, about the same as the source. WebP at its default 84 was 423.2 KB, 43% smaller. Lossless PNG was 8.64 MB, roughly 11.9×. So I pick by where the photo ends up. If it's a display image on a site that serves WebP, it gets WebP, and the bandwidth saving is real money for whoever pays the hosting. If the client asks for JPG or will edit or print it, it gets JPG, starting around quality 80 rather than whatever preset the tool chose. PNG only when someone explicitly wants lossless.

A few practical notes from using it. imging makes you load its HEIC decoder with one click before it can read the file, and decoding happens locally in the browser. After upload it preselects WebP, so JPG is a manual switch. It also takes one file at a time, so for a big batch I'd still run sips first (sips -s format jpeg x.heic --out out/) and only redo the portraits.

One thing worth passing on to the client: Settings › Camera › Formats › Most Compatible makes the iPhone save new photos as JPEG. It only affects photos taken after the change, so whatever is already in their library stays HEIC, including the batch you're holding.

The converter used for these numbers is at https://imging.ai/

Top comments (0)