Disclosure: This report on the image-processing tests run by the LighterPics project was written with AI assistance.
Drawing a photo to a canvas and exporting it again is a common way to remove EXIF. The tests here inspected 3,586 outputs from browser image-processing pipelines. The parser found no GPS coordinates, camera make or model, capture date, XMP or IPTC entries. Every WebKit output still contained an EXIF block.
The test set was small: 25 JPEG photos, one generated test JPEG, and 10 generated PNGs used only in Chromium. The 3,586 files came from applying different operations and settings to those inputs, not from 3,586 distinct photos. Only three of the photos and the test JPEG contained GPS coordinates.
Metadata found in the output
| Field or block detected | Chromium 145.0.7632.6 | Firefox 146.0.1 | WebKit 26.0 |
|---|---|---|---|
| GPS coordinates | 0 of 3,198 | 0 of 207 | 0 of 181 |
| Camera make or model | 0 of 3,198 | 0 of 207 | 0 of 181 |
Capture date (DateTimeOriginal) | 0 of 3,198 | 0 of 207 | 0 of 181 |
| XMP | 0 of 3,198 | 0 of 207 | 0 of 181 |
| IPTC entries | 0 of 3,198 | 0 of 207 | 0 of 181 |
| Any EXIF block | 0 of 3,198 | 0 of 207 | 181 of 181 |
| ICC colour profile | 2,451 of 3,198 | 0 of 207 | 0 of 181 |
These are counts of output files inspected, not metadata-removal rates for inputs that carried each field. Chromium also ran a broader set of settings. Its totals include MozJPEG and OxiPNG WebAssembly encoders, so the aggregate is not a pure comparison of canvas encoders. The format breakdown below separates those paths.
The engine versions in the table are headless Playwright builds tested on macOS.
WebKit wrote a new EXIF block
All 181 WebKit outputs contained EXIF: an APP1 segment in JPEG and an eXIf chunk in PNG. The tag set was the same in every file:
- IFD0 contained only the pointer to the Exif IFD (tag 34665).
- The Exif IFD contained
ColorSpace(40961),PixelXDimension(40962) andPixelYDimension(40963).
These are the tag names defined by CIPA DC-008. The PNG specification defines the eXIf chunk. [1] [2]
The block also appeared in all 14 outputs made from the two photos with no input EXIF. Every WebKit JPEG contained an APP13 segment labelled Photoshop 3.0, but the parser found no IPTC datasets in those segments.
An EXIF-presence check would therefore flag every WebKit output, including files whose inputs had no EXIF. A check for the fields of concern is more informative.
Colour metadata varied by encoder
Chromium wrote an ICC profile into every canvas-encoded JPEG and every WebP output. That included 1,314 outputs made from inputs with no ICC profile. Its canvas PNGs contained no colour-profile chunk, and neither did its WebAssembly outputs.
Firefox wrote no ICC profile in any tested format. WebKit wrote an sRGB chunk in PNG, but no ICC profile in JPEG or PNG.
The HTML standard explicitly covers resolution metadata and colour profiles when serializing a canvas bitmap. It is not a promise that the resulting file will contain no metadata. [3]
The harness detected the presence of ICC profiles without decoding their contents. These results therefore do not establish whether an input profile was preserved or replaced.
File structure by encoder and formatFor JPEG, this table lists APPn, SOF and SOS markers only. PNG and WebP entries list chunk types in order.
| Engine | Output path | Markers or chunks | Files |
|---|---|---|---|
| Chromium | JPEG, canvas | APP0 JFIF, APP2 ICC profile, SOF0, SOS | 1,587 |
| Chromium | JPEG, MozJPEG WebAssembly | APP0 JFIF, SOF2, SOS | 441 |
| Chromium | WebP | VP8X, ICCP, then VP8 (713), ALPH + VP8 (115) or VP8L (36) | 864 |
| Chromium | PNG, canvas and OxiPNG | IHDR, IDAT, IEND | 306 |
| Firefox | JPEG | APP0 JFIF, SOF0, SOS | 155 |
| Firefox | WebP | VP8 | 26 |
| Firefox | PNG | IHDR, IDAT, IEND | 26 |
| WebKit | JPEG | APP0 JFIF, APP1 Exif, APP13 Photoshop 3.0, SOF0, SOS | 155 |
| WebKit | PNG | IHDR, sRGB, eXIf, IDAT, IEND | 26 |
Of the 306 Chromium PNGs, 296 used the canvas encoder. No output contained a JPEG COM segment or a PNG text chunk.
The orientation tag was gone; the dimensions were rotated
The generated test JPEG had 1600×1200 stored pixels and EXIF Orientation 6. Every converter output from it was 1200×1600, with no Orientation tag: 21 outputs in Chromium, three in Firefox and two in WebKit.
The converter used createImageBitmap(blob, {imageOrientation: "from-image"}). The output dimensions are consistent with applying the input orientation before encoding. "from-image" is also the default in the ImageBitmap options. [4]
A WebP request could return PNG
The application rejected all 26 WebKit conversion attempts that requested WebP because the returned blob was not WebP. In the single-photo snippet check below, the returned type was image/png.
Canvas export falls back to PNG when the requested format is unsupported. Check the returned blob.type before assigning an extension, or a .webp download may contain PNG data. [5]
Test setup and limits
The harness built LighterPics locally and called the same processing functions and Web Workers as the tool pages. The canvas paths decoded with createImageBitmap(), drew to a canvas or OffscreenCanvas, and exported with toBlob() or convertToBlob(). Two Chromium compressor modes used MozJPEG and OxiPNG WebAssembly encoders instead.
The runs used Playwright 1.58.2 on an Apple M3 Pro, macOS Darwin 25.6.0, arm64. Playwright supplies the browser builds; these were not tests of the corresponding branded releases. [6]
The 25 CC0 JPEG photos came from Wikimedia Commons and were SHA-256 checked before the run. EXIF was present in 23 photos; GPS coordinates in three; camera make or model in 15; a capture date in 16; XMP in 19; IPTC entries in 15; and an ICC profile in 14. The generated JPEG added a known orientation, camera fields, capture date, GPS coordinates and an ICC profile. Chromium also processed five generated screenshots and five generated graphics, all PNGs.
Operations covered compression, conversion, resize, crop, rotation, flipping and watermarking. Chromium ran the full settings matrix; Firefox and WebKit ran a smaller selection of default settings. The parser inspected JPEG markers, PNG and WebP chunks, EXIF tag numbers and IPTC dataset counts. Per-tool summaries are available on the project page. [7]
The scope is limited to these inputs and builds. HEIC, TIFF and PNGs carrying text metadata were not part of the cross-engine photo set. The checks did not inspect ICC contents or look for information in the pixels, such as visible text or steganography. They do not establish that arbitrary input files will be free of personal information after re-encoding.
Inspect a file in your browser
This snippet lists container blocks for one of the test photos before and after export. It does not parse the individual EXIF fields, so it cannot reproduce the full privacy check on its own.
Open about:blank in a desktop browser and paste it into the DevTools console. The Wikimedia file server allows the cross-origin request.
(async () => { // Lists JPEG marker segments (up to SOS), PNG chunks or WebP chunks, in file order. function listBlocks(b) { const out = [], u32 = i => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0; if (b[0] === 0xff && b[1] === 0xd8) { // JPEG: marker segments up to SOS for (let i = 2; i + 4 <= b.length && b[i] === 0xff;) { const m = b[i + 1], len = b[i + 2] << 8 | b[i + 3]; let name = m >= 0xe0 && m <= 0xef ? "APP" + (m - 0xe0) : m === 0xfe ? "COM" : m === 0xdb ? "DQT" : m === 0xc4 ? "DHT" : m === 0xdd ? "DRI" : m === 0xda ? "SOS" : m >= 0xc0 && m <= 0xcf ? "SOF" + (m - 0xc0) : "0x" + m.toString(16); if (name.startsWith("APP")) { // APPn segments start with an ID string let id = ""; for (let j = i + 4; j < i + 36 && b[j]; j++) id += String.fromCharCode(b[j]); name += ":" + id; } out.push(name); if (m === 0xda) break; // compressed image data follows i += 2 + len; } } else if (b[0] === 0x89 && b[1] === 0x50) { // PNG: chunk types for (let i = 8; i + 8 <= b.length;) { const type = String.fromCharCode(b[i + 4], b[i + 5], b[i + 6], b[i + 7]); out.push(type); if (type === "IEND") break; i += 12 + u32(i); } } else if (b[0] === 0x52 && b[8] === 0x57) { // "RIFF....WEBP": chunk FourCCs for (let i = 12; i + 8 <= b.length;) { const size = (b[i + 4] | b[i + 5] << 8 | b[i + 6] << 16 | b[i + 7] << 24) >>> 0; out.push(String.fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3])); i += 8 + size + (size & 1); } } const runs = []; // "IDAT IDAT IDAT" -> "IDAT×3" for (const name of out) { const last = runs[runs.length - 1]; if (last && last.name === name) last.count++; else runs.push({name, count: 1}); } return runs.map(r => r.count > 1 ? r.name + "×" + r.count : r.name); } const bytes = async blob => new Uint8Array(await blob.arrayBuffer()); const src = await (await fetch("https://upload.wikimedia.org/wikipedia/commons/0/0f/Star_night_%28Unsplash%29.jpg")).blob(); console.log("input", src.size, listBlocks(await bytes(src)).join(" ")); const bitmap = await createImageBitmap(src, {imageOrientation: "from-image"}); const canvas = document.createElement("canvas"); canvas.width = bitmap.width; canvas.height = bitmap.height; canvas.getContext("2d").drawImage(bitmap, 0, 0); for (const type of ["image/jpeg", "image/webp", "image/png"]) { const out = await new Promise(resolve => canvas.toBlob(resolve, type, 0.92)); console.log(type, "->", out.type, out.size, listBlocks(await bytes(out)).join(" ")); } })(); The input should include EXIF, XMP, an ICC profile and an APP13 segment. The type after the arrow is the type actually returned by the encoder.
In the recorded check on 2026-11-11, the snippet matched the harness byte counts and recorded block lists for this photo in all three builds. WebKit returned image/png for the WebP request, at 8,191,145 bytes, matching the size of its explicit PNG export.
The main Chromium run used site and harness commit eacd79c, harness version 1.1.0, on 2026-11-10. The Firefox and WebKit run used harness commit 349005d, version 1.2.0, with the same site code on 2026-11-11.
Results are recorded in results-2026-11-10-r5.json, results-2026-11-11-metadata.json and its repeat file. The repeat matched after timing fields were excluded; the main Chromium run was not repeated at this code version.
The repository and raw results are private. The linked project page provides summary tables, not a publicly reproducible copy of the full benchmark.
The 25 CC0 test photos (Wikimedia Commons)| # | File on Commons | Author as recorded on Commons | Licence | Pixels | Bytes |
|---|---|---|---|---|---|
| 1 | Советский Будильник 12ч34м.jpg | Petra Dr | CC0 | 2896×1944 | 2,186,980 |
| 2 | Alone in the unspoilt wilderness (Unsplash).jpg | David Marcu davidmarcu | CC0 | 3506×2329 | 3,655,400 |
| 3 | Brickwall (Unsplash).jpg | Namroud Gorguis namroud | CC0 | 2784×1856 | 5,603,777 |
| 4 | Coffee at work (Unsplash).jpg | Cathryn Lavery cathrynlavery | CC0 | 2400×1600 | 985,861 |
| 5 | Friends 1 (Unsplash).jpg | Joel Mwakasege joelmwakasege | CC0 | 2592×1933 | 316,480 |
| 6 | Green grass texture (Unsplash).jpg | Marc Wilnauer wk25 | CC0 | 3232×1824 | 3,497,050 |
| 7 | Healthy Cafe Food (Unsplash).jpg | Jade Wulfraat jadew | CC0 | 2936×1960 | 2,812,198 |
| 8 | Kolkata City at night wide view.jpg | XYZ 91973 | CC0 | 8632×864 | 533,436 |
| 9 | Landscape-mountains-clouds-trees (24326873785).jpg | www.Pixel.la Free Stock Photos | CC0 | 5184×1520 | 2,864,749 |
| 10 | Leaf texture in macro (Unsplash).jpg | Vadim Gromov motioq | CC0 | 3885×2590 | 3,557,162 |
| 11 | Lighthouse at night (Unsplash).jpg | Janus Y janus | CC0 | 3888×2592 | 2,563,831 |
| 12 | Log pile in close-up (Unsplash).jpg | Sven Scheuermeier sveninho | CC0 | 2560×1707 | 2,616,623 |
| 13 | Man with tribal face paint (Unsplash).jpg | Scott Umstattd scott_umstattd | CC0 | 3464×2309 | 4,303,508 |
| 14 | Marmolada, Italy.jpg | Marco Bonomo | CC0 | 3904×2604 | 3,396,302 |
| 15 | Organic Carrots (Unsplash).jpg | Harshal Hirve harshalhirve | CC0 | 4160×2416 | 2,392,141 |
| 16 | Pieces of Watermelon (Unsplash).jpg | Neha Deshmukh nehadeshmukh | CC0 | 2000×3000 | 3,070,980 |
| 17 | Portrait of Prof Mikhail Lemeshko.jpg | not recorded | CC0 | 2034×2717 | 742,583 |
| 18 | Rangpur city highway at night 1.jpg | Cerium4B | CC0 | 2048×1536 | 450,984 |
| 19 | Singapore night (Unsplash).jpg | Pierpaolo Lanfrancotti goofy1981 | CC0 | 4153×2535 | 1,122,928 |
| 20 | Small and medium pumpkins (Unsplash).jpg | Corey Blaz blazphoto | CC0 | 4032×3024 | 2,285,378 |
| 21 | Smiling Eyes (Unsplash).jpg | Luca Campioni white73widow | CC0 | 2136×1536 | 981,696 |
| 22 | Star night (Unsplash).jpg | Pasi Jormalainen paatse | CC0 | 2488×1558 | 878,879 |
| 23 | Strong woman (Unsplash).jpg | Scott Webb scottwebb | CC0 | 4190×2782 | 3,846,153 |
| 24 | Woman watching the sunset (Unsplash).jpg | Lucas Gallone lucasgallone | CC0 | 3264×1836 | 1,491,446 |
| 25 | Woman with a pink flower (Unsplash).jpg | Camila Damásio miladamasio | CC0 | 3331×2221 | 4,663,027 |
