You do not need an app on any of the three. One page in Chrome, one file at a time — here is what that actually costs on a phone-sized screen.
Short answer: open the converter in Chrome on the Android phone, the Chromebook or the Linux machine, pick the HEIC file and download the JPG. There is nothing to install on any of the three, because the decoder is not software you install — it is a JavaScript file the page fetches once. I measured that first load today at 688,921 bytes across six requests, of which 679,228 bytes are the decoder itself (2,999,737 bytes once uncompressed). That file is an Emscripten build shipped with isWasm2js set — libheif compiled to WebAssembly and then translated into JavaScript — and it makes no call to the browser’s WebAssembly API anywhere in its 2,999,737 bytes.
What I measured on this site: I drove a real Chrome against the live converter with a phone-sized screen (412×915), an Android user-agent and touch emulation, and pushed a 12 MP HEIC through it — 4,032×3,024, 1,267,632 bytes in. It came back as a 1,708,595-byte JPG in 3,175–6,527 ms across eight runs, and all eight output files were byte-identical (SHA-256 1fca374ec67e5fd5…). The upload box starts 333 px from the top on that screen, so it is visible without scrolling, and nothing overflows sideways.
All three fail on HEIC for different reasons, and the difference matters.
The browser route is the same on all three for one reason: it does not ask the operating system for a HEIC decoder. It ships one.
On a phone the first question is data, and on a small screen the second is whether you have to scroll to reach the file box. I measured both with cache disabled, one fresh load each:
| Screen | Upload box starts at | Visible without scrolling | Sideways overflow |
|---|---|---|---|
| 412×915 (Android emulation) | 333 px | Yes — box ends at 497 px | None |
| 1,264×805 (desktop window) | 288 px | Yes — box ends at 468 px | None |
| 1,366×768 (Chromebook emulation) | 288 px | Yes — box ends at 468 px | None |
| 1,600×900 (Linux emulation) | 288 px | Yes — box ends at 468 px | None |
At every one of those widths the page’s scroll width equalled the viewport width, so nothing is cut off and nothing slides sideways. On the first load the browser fetched six resources totalling 688,921 bytes; the decoder alone was 679,228 bytes on the wire (2,999,737 bytes decoded) and took 161 ms, with the document itself finishing in 438 ms. After that first load the decoder is cached, which is why the page keeps working with the network off — measured separately in the offline test.
I used a single 4,032×3,024 HEIC of 1,267,632 bytes and ran it through the live converter at the default 85% quality, twice at each CPU setting, on the 412×915 Android emulation. Same bytes every time — only the file name changed, because the file input ignores a second pick of an identical path:
| CPU setting | File | JPG out | Time | SHA-256 of output |
|---|---|---|---|---|
| 1× (no throttle) | r1a.heic | 1,708,595 bytes | 6,527 ms | 1fca374ec67e5fd5… |
| 1× (no throttle) | r1b.heic | 1,708,595 bytes | 4,881 ms | 1fca374ec67e5fd5… |
| 4× throttle | r4a.heic | 1,708,595 bytes | 3,279 ms | 1fca374ec67e5fd5… |
| 4× throttle | r4b.heic | 1,708,595 bytes | 3,799 ms | 1fca374ec67e5fd5… |
| 8× throttle | r8a.heic | 1,708,595 bytes | 3,565 ms | 1fca374ec67e5fd5… |
| 8× throttle | r8b.heic | 1,708,595 bytes | 3,793 ms | 1fca374ec67e5fd5… |
| 20× throttle | r20a.heic | 1,708,595 bytes | 3,261 ms | 1fca374ec67e5fd5… |
| 20× throttle | r20b.heic | 1,708,595 bytes | 3,175 ms | 1fca374ec67e5fd5… |
The same file run in a plain desktop window with no emulation finished in 5,128 ms and produced the same 1,708,595 bytes with the same hash. So the phone-sized screen and the Android user-agent change nothing about the result — the JPG is bit-for-bit the same file you would get on a laptop.
I tried, and the experiment did not do what I wanted. Throttling the CPU 4×, 8× and 20× did not slow the conversion down: the same file actually came out faster at 20× (3,175 ms) than at 1× (4,881 ms). Two measurements explain that:
Worker the page creates, it makes exactly one, from a blob: URL. Main-thread throttling in DevTools targets the main thread, so it barely touches work happening in that worker.That means the numbers in the table above are desktop-PC numbers wearing a phone costume. I did not run this on a real phone today, so I am not going to publish a phone timing. What I can say from today’s runs: on my machine a 12 MP photo lands somewhere between 3.2 and 6.5 seconds, and a small one is quick — a 700×476 photo of 41,465 bytes converted to 38,078 bytes in 909 ms on the Android emulation. Where a real phone falls inside that, I have not measured.
One number worth knowing if you are worried about a phone running out of memory: across all fifteen 12 MP runs today, the page’s JavaScript heap peaked at 158.5 MiB and came back down afterwards; after the small photo it sat at 12.3 MiB. Nothing crashed and nothing was refused.
capture attribute (I checked)..heic,.heif,image/heic,image/heif, so switch the picker to a file manager and browse to the file..jpg ending.Two limits, both measured rather than guessed: the input is single-select (multiple is false), and when I pushed five files in at once the browser kept one — so it is one photo per round, with no cap on how many rounds you do. The cost of two hundred rounds on a desktop is in the 200-file run, and the volume you get at each quality setting is in the file-size measurements.
ChromeOS is the easy case: Chrome is already there, the page is a web app, and nothing needs installing or enabling. The upload box lands 288 px from the top at 1,366×768, so it is on screen without scrolling even on a small Chromebook display, and there is no sideways overflow. Use the upload box and pick the file from the Files app. I have not measured dragging a file from the ChromeOS Files app into the tab, so I will not promise that it works — the upload button is the path I know runs.
If the Chromebook is managed and refuses installs, that changes nothing here: the decoder arrives as part of the page.
Let me be exact about what I did and did not do. The “Linux” row in my table is a Chrome running on Windows with a Linux user-agent string. That is not a Linux measurement and I am not going to present it as one — it is the same binary, so it only shows the page does not sniff the operating system.
What does carry over to Linux without me needing to measure it: the page’s only requirements are a browser that runs JavaScript and one 679,228-byte download. There is no codec package to install, no libheif to find in your distribution’s repositories, and no version to argue with. The native alternatives — a heif-convert binary from libheif, ImageMagick built with a HEIF delegate, a recent GIMP — are real and are the right answer if you are converting a folder in a script, but I did not run any of them today, so this page carries no timings for them.
After one load with a connection, yes — and that is worth knowing on all three systems. The decoder is cached and the file never leaves the device. The full test, including what breaks after you clear the cache, is in the offline measurement.
I have not measured those. Everything in this page was run in Chrome’s engine today, so I will not claim a browser I did not test. What the page needs from a browser is JavaScript and the ability to fetch the decoder; I have not verified that on anything else.
Not measured. What I can say is what the decoder is made of: an Emscripten build with isWasm2js set — libheif built to WebAssembly and then translated into JavaScript — with zero references to the WebAssembly API in the 2,999,737 bytes the page ships. So nothing in it asks the browser for WebAssembly at runtime. Old WebView components I did not test.
One file per round — the input is single-select, and five files pushed in at once left the browser holding one. There is no limit on rounds. Whether a phone survives a hundred of them is something I have not measured; the desktop cost of two hundred is in the 200-file run.
No. The output carries no EXIF at all — camera model, GPS and timestamps are dropped. That was measured by opening the output file, in the EXIF test. On Android that also means the JPG you hand over is less revealing than the HEIC you started with.
heif-convert, ImageMagick or GIMP was run, so no timings are given for them.Try it on whichever of the three you are holding: open the converter, tap the upload box, and watch the JPG come back without anything leaving the device.
First published: 27 September 2026