I spent a month measuring it, one round trip at a time, on a real phone. Here is what came back.
Everyone knows chat apps "compress" photos. Almost nobody knows how, because the answer is not in any documentation — you have to send an image, download what returns, and read the JPEG header. So that is what I did, several hundred times, and the results were more specific and more useful than I expected.
A JPEG carries the quantization table it was encoded with. Most software uses a scaled version of the reference table in the JPEG standard's Annex K — pick a quality number, the table scales, done. So if you know what Annex K looks like, you can usually tell what encoded a file.
This is not Annex K scaled. The best-fit scale is 0.650 — nominally around quality 68 — but the per-coefficient ratio ranges from 0.28 to 1.69, with a 37% mean residual. The average step is close to quality 65; the shape is completely different, with a much steeper roll-off. Low frequencies are barely touched at 6 to 12, and the far corner is crushed at 167.
It is also content-independent. I saw the identical table across 82 returned files spanning five different output geometries. Not similar — byte-identical.
Very flat, very gentle. And Facebook returns a byte-identical table — same Meta pipeline, so you cannot tell an Instagram file from a Facebook one by its quantization table alone. Only the geometry separates them.
Telegram is the ordinary case: a scaled Annex K near quality 87. Nothing custom.
WhatsApp HD is a different table again, min 4, max 19, mean 12.2 — much finer than the standard send, which is what you would hope from a mode called HD.
| Platform | What it does to the frame | |---|---| | WhatsApp, standard | Caps the long edge at 1600. At or below, dimensions pass through untouched | | WhatsApp, HD on | Keeps 4096 — a 4096×3072 photo returns 4096×3072 | | Telegram, as photo | Re-encodes everything to 1280×960 | | Telegram, as file | Untouched. Byte-identical, in my tests | | Instagram | Does not cap — normalises onto a 1440×1440 square canvas | | Facebook | 2048 long edge | | X/Twitter | 4096 long edge |
Instagram does not have a maximum size, it has a shape. Upload something 1080 wide and it comes back upscaled to 1440. Upload 4:3 and it comes back padded to square. There is no size small enough to be safe, because the normalisation is not a cap.
WhatsApp caps the long edge, not the width. A portrait photo comes back 1200×1600, not 1600×1200. Obvious in hindsight; not obvious when your code says width.
Send a photo your phone took in portrait, and there is a good chance it is stored as landscape pixels with an EXIF flag saying "rotate this 90°".
WhatsApp applies that flag physically and strips it. I sent a 3840×2160 file with orientation=6 and got back 2160×3840 with no orientation tag. The pixels were rotated, not relabelled.
For ordinary photos this is invisible and correct. For anything that depends on where pixels sit — watermarking, hidden data, forensic comparison — a 90° rotation is total loss, and it happens silently to a photo the user shot normally.
I was measuring all this because I was building steganography: hiding data inside a photo so it survives being sent through a chat app. That turns out to be almost entirely a geometry problem.
Hidden data in JPEG lives in the 8×8 DCT blocks the format is built on. If a platform resamples the image, the spacing of that grid changes, the decoder starts reading across block boundaries, and the payload is not degraded — it is destroyed. Every catastrophic failure I measured came out at roughly 50% bit error rate, which is the same as guessing.
So the whole game is not stronger error correction. It is handing the platform an image it has no reason to touch. Match its output geometry and the grid survives intact.
Then there is a second, subtler win. If you encode your image using the platform's own quantization table, its re-encode has nothing left to change. I extracted Meta's table from images Instagram returned and encoded on it directly. The share of the embedding band that survived went from 85.9% to 100%, and — the part I did not expect — the required embedding strength halved, so the image came out visibly cleaner as well as more robust.
