Remove metadata from an image without re-encoding it

Pixel data untouched Copy-paste commands No upload

Short answer: metadata lives in separate segments before the image data. Deleting those segments does not re-encode anything, so the picture does not change and does not lose quality. You are removing bytes, not re-compressing pixels.

Scope, stated first: this removes file metadata. It cannot remove Claude's text watermark, because that watermark is not metadata and not characters — it is a statistical pattern in how the words were chosen. Anthropic states plainly that “nothing is added to the text and there are no hidden characters.” What Claude does attach to image files is a C2PA credential, which is metadata and genuinely can be removed. Two different layers, two different answers. The full breakdown →

Three ways to do it, pick one:

exiftool -all= -overwrite_original photo.jpg        # strips EXIF, XMP, IPTC, GPS
exiftool -all= -overwrite_original *.jpg *.png      # whole folder
from PIL import Image
im = Image.open("photo.jpg")
im.save("clean.jpg", exif=b"")                       # re-encodes — see the warning below

Or drop the file into the browser cleaner on this site, which removes the segments directly and never re-encodes.

One warning before you run anything: -all= and im.save() are not the same operation. The first removes metadata and leaves the pixels byte-identical. The second re-compresses the image and will lose a little quality every time you run it. If you care about the image, use a segment-level tool.

What is actually inside the file

Metadata is not mixed into your pixels. In a JPEG it sits in APPn segments at the start of the file, before the compressed image data. In a PNG it sits in named chunks. Either way it is a discrete block you can cut out and throw away.

JPEG FILE STRUCTURE — HEADER SEGMENTS, THEN PIXELS SOI keep APP1 EXIF + XMP APP11 C2PA APP13 IPTC APP14 Adobe pixels untouched Dropped: EXIF, XMP, IPTC and the C2PA credential. Kept: SOI, the Adobe colour transform, and every pixel. PNG is the same idea with named chunks: tEXt, zTXt, iTXt, eXIf and caBX go; iCCP, gAMA, cHRM, sRGB and pHYs stay.

What each block holds, and why it matters

BlockWhat is in itRisk if you leave it
APP1 EXIFCamera make and model, lens, shutter, ISO, orientation, GPS coordinates, sometimes a serial numberThe classic one: a photo taken at home publishes your home coordinates. Also a hardware fingerprint that links separate uploads to one device.
APP1 XMPAdobe's XML blob: edit history, software versions, keywords, sometimes the original document pathReveals your toolchain and, occasionally, a local file path with your username in it.
APP11 JUMBFThe C2PA content credential — a signed record of who made or processed the fileAnyone with a C2PA reader sees the provenance claim. See the section below, because this one is a real trade-off rather than a clear win.
APP13IPTC / Photoshop IRB: captions, credit line, copyright noticeMostly harmless, but it is still text about you attached to a file you are handing over.
PNG tEXt / zTXt / iTXtFree-form key/value text: Software, Author, Comment, and often generator parameters from AI image toolsOften the most revealing block in a PNG, because it is plain readable text.
PNG eXIfAn EXIF block, inside a PNGSame as JPEG EXIF, including GPS.

What you must not delete

This is where careless tools go wrong, and it is worth checking whichever tool you use.

BlockWhy it has to stay
JPEG APP14 "Adobe"Signals the colour transform (YCbCr vs YCCK). Drop it and some decoders render the image with inverted or shifted colour.
PNG iCCPThe embedded ICC colour profile. Drop it and the same pixels get interpreted in a different colour space — the image visibly changes on a colour-managed display.
PNG gAMA / cHRM / sRGBGamma and chromaticity. Dropping them shifts brightness and hue.
PNG pHYsPhysical pixel dimensions, i.e. the intended print size and DPI. Drop it and a print workflow may place the image at the wrong size.
JPEG / PNG EXIF OrientationOne number, 1–8, saying how to rotate the stored pixels. See below — this is the one tag worth keeping out of the whole EXIF block.

A tool that "removes all metadata" by deleting every non-essential block will quietly break colour on PNGs and can break colour on JPEGs. The one on this site keeps the five blocks above and drops only the personal and provenance blocks listed earlier — the exact list is readable in the page source, since the whole thing is plain JavaScript.

Why the orientation tag gets put back

Orientation is the one EXIF tag this tool rebuilds after stripping. A phone held upright often writes landscape pixels plus Orientation = 6, and every current viewer rotates the picture when it draws it. Delete the tag and those photos come back sideways: not a single pixel moved, but the picture you see did. That is exactly the failure mode a metadata remover is not supposed to have.

It is also not personal data. Make, model, serial number, timestamps and GPS all sit in the same EXIF block and all of them still go. What is written back is a 36-byte APP1 segment holding one tag and nothing else, and the same trick applies to a PNG's eXIf chunk. If the original had no orientation worth keeping, nothing is written back at all.

Two things this means in practice. A JPEG whose orientation mattered shrinks by about 220 bytes instead of 256 — the stub costs 36 bytes, and the measured table below reflects that. And a file you have already cleaned stays byte-identical on a second pass, because the only EXIF left in it is the stub itself.

We measured it: what actually changes

That claim is easy to make and just as easy to check, so we checked it rather than asserting it. Because the cleaner on this site is plain JavaScript with no build step, you can run it yourself and hash the picture data before and after. These are the real numbers from doing exactly that, on real files rather than toy swatches:

FileBytes before → afterPicture dataVerdict
JPEG 640×480, with EXIF + GPS + APP14 + C2PA59,915 → 59,695 (−220 B)scan 59,020 B, hash 57f33aa4…57f33aa4…unchanged; EXIF cut down to the orientation tag, C2PA gone, APP14 kept
same JPEG, saved again at the same quality (control)59,915 → 59,618scan 58,995 B, hash 5f3a3c6a…changed — this is what re-encoding looks like
PNG 512×384, with iCCP + 3×tEXt + pHYs + eXIf6,893 → 6,737IDAT 6,273 B, identical hashunchanged; ICC profile and DPI kept byte-for-byte
PNG with gAMA + cHRM + sRGB injected6,580 → 6,424IDAT 6,273 B, identical hashunchanged; all three colour blocks kept

The second row is the point. It is the same photograph, saved again at the same quality setting, and its picture data hashes differently. So when the stripped file hashes identically, that is a property of the operation and not an artefact of the test — the bytes the decoder reads to draw the image never moved. The only thing that left the file was metadata.

To reproduce it, hash everything after the header segments rather than the whole file, since the whole file obviously changes:

# JPEG: the scan data starts after the SOS marker
node -e 'const f=require("fs").readFileSync("photo.jpg");const c=require("crypto");
let i=2;while(f[i]===0xff){const m=f[i+1];if(m===0xda){const n=f.readUInt16BE(i+2);
console.log(c.createHash("sha256").update(f.slice(i+2+n)).digest("hex").slice(0,16));break}
i+=2+f.readUInt16BE(i+2)}'

# PNG: concatenate every IDAT payload
python -c 'import struct,zlib,hashlib;d=open("photo.png","rb").read();i=8;s=b""
while i<len(d):
    n=struct.unpack(">I",d[i:i+4])[0];t=d[i+4:i+8]
    if t==b"IDAT":s+=d[i+8:i+8+n]
    i+=12+n
print(hashlib.sha256(s).hexdigest()[:16])'

Run each one on the original and on the cleaned file. The hashes match. Run them on a file that went through a re-encoder and they will not. Your exact numbers will differ — files differ — but the property does not.

The C2PA credential is a genuine trade-off

C2PA is the open standard camera makers and photo editors use to record where a file came from. Anthropic attaches a C2PA credential when Claude produces a supported file type — PNG, JPG, SVG — as a signed note that the file was made or processed with Claude. Anthropic describes it as "very different from a watermark", noting that "nothing in the file changes — it is not embedded or hidden."

Because it is metadata, it can be removed. But be clear about what that costs you:

Check whether a file carries one before deciding:

exiftool -a -G1 -s photo.png | grep -iE "c2pa|jumbf|provenance|claim_generator"

If you get a JUMBF or C2PA line, the credential is there. Whether to remove it is a decision about what you want the file to prove, not a technical one. Why this is different from a text watermark →

What is safe to drop, at a glance

SAFE TO DROP EXIF: GPS, camera, lens, serial XMP: edit history, toolchain IPTC: captions, credit, copyright PNG tEXt / zTXt / iTXt text C2PA credential (but see above) — orientation stays, see above MUST KEEP ICC colour profile (iCCP) Gamma + chromaticity (gAMA, cHRM) Physical size / DPI (pHYs) Adobe colour transform (APP14) Every pixel of image data EXIF orientation (rotation only)

Batch it, and check the result

ExifTool handles a whole tree and leaves the pixels alone:

exiftool -all= -r -overwrite_original -ext jpg -ext jpeg -ext png ./photos

Verify that the metadata is gone but the image is intact:

exiftool -a -G1 -s clean.jpg | head -20      # expect no EXIF / XMP / IPTC blocks
identify -verbose clean.jpg | grep -i profile  # ICC profile should still be listed

Checking without installing anything

Every container carries a fixed identifier string it is required to have. That makes a raw byte search enough to answer “is it still there?” — Python standard library only, no packages:

python -c "b=open('photo.jpg','rb').read();print([n for n,s in [('EXIF',b'Exif\x00\x00'),('XMP',b'http://ns.adobe.com/xap/1.0/'),('ICC',b'ICC_PROFILE'),('IPTC',b'Photoshop 3.0'),('C2PA',b'c2pa')] if s in b])"

Run against a deliberately metadata-heavy test JPEG, before and after a segment-level strip:

before: ['EXIF', 'XMP', 'ICC', 'IPTC', 'C2PA']
after:  ['ICC']

ICC survives on purpose — that is the colour profile, and it identifies nobody. If your output still lists EXIF or C2PA after a strip, the tool you used did not remove what it claimed. The shell equivalent, when you want counts rather than a yes/no:

grep -o -a -E 'Exif|ns\.adobe\.com/xap/1\.0|ICC_PROFILE|Photoshop 3\.0|c2pa' photo.jpg | sort | uniq -c
before:  1 Exif   2 ns.adobe.com/xap/1.0   1 ICC_PROFILE   1 Photoshop 3.0   2 c2pa
after:   1 ICC_PROFILE

Note that plain grep -c is the wrong flag for this — it counts lines, and a binary file has almost none, so it returns 1 whether there is one identifier or five.

Batch a folder with the same code the browser runs

ExifTool is the reference implementation, but if you would rather not hand a folder to a binary you cannot read, the cleaner on this site is plain JavaScript and runs under Node from the identical source. This script reports what it would remove and writes nothing until you add --write:

// strip-meta.js  -  node strip-meta.js ./photos          (report only)
//                   node strip-meta.js ./photos --write  (rewrite in place)
// Keep this file next to cleaner.js. In the site's own dev/ folder the require
// line falls back to '../cleaner.js', so it always tests the real core.
const fs = require('fs'), path = require('path');
const CWR = require('./cleaner.js');

const dir = process.argv[2] || '.';
const write = process.argv.includes('--write');
let n = 0, saved = 0;

for (const f of fs.readdirSync(dir)) {
  if (!/\.(jpe?g|png)$/i.test(f)) continue;
  const p = path.join(dir, f);
  const before = fs.readFileSync(p);
  const r = CWR.stripBinaryMeta(new Uint8Array(before), f);
  if (!r.ok) { console.log('skip   ' + f + '  (' + r.reason + ')'); continue; }
  const out = Buffer.from(r.bytes);
  const hit = Object.entries(r.removed || {}).map(([k, v]) => k + (v > 1 ? '\u00d7' + v : '')).join(', ') || 'nothing found';
  n++;
  if (out.length < before.length) saved += before.length - out.length;
  console.log((write && out.length < before.length ? 'strip  ' : 'report ') + f.padEnd(28) +
    String(before.length).padStart(8) + ' -> ' + String(out.length).padStart(8) + '   ' + hit);
  if (write && out.length < before.length) fs.writeFileSync(p, out);
}
console.log('\n' + n + ' image(s) scanned, ' + saved + ' bytes of metadata ' + (write ? 'removed.' : 'would be removed (no --write).'));

Output from a folder holding one JPEG and one PNG — dry run, real run, then dry run again, copied from the terminal:

$ node strip-meta.js ./batch
report rich.jpg                        2079 ->     1327   xmp, c2pa, iptc, exif, comment
report rich.png                         694 ->      482   text×2, c2pa

2 image(s) scanned, 964 bytes of metadata would be removed (no --write).

$ node strip-meta.js ./batch --write
strip  rich.jpg                        2079 ->     1327   xmp, c2pa, iptc, exif, comment
strip  rich.png                         694 ->      482   text×2, c2pa

2 image(s) scanned, 964 bytes of metadata removed.

$ node strip-meta.js ./batch
report rich.jpg                        1327 ->     1327   nothing found
report rich.png                         482 ->      482   nothing found

2 image(s) scanned, 0 bytes of metadata would be removed (no --write).

The third run is the one worth reading. Re-running on a cleaned folder reports nothing found, which is what an idempotent segment-level tool should say — and what a tool that silently re-encodes every time cannot say. If your cleaner keeps reporting work on an already-clean file, it is re-compressing your image.

Pixel-level proof, not a promise

A size drop proves metadata left the file. It does not prove the image survived. The measured section earlier on this page does that check properly, on files where the numbers are large enough to mean something: the picture data hashed identically before and after the strip, while a control file saved again at the same quality did not.

Idempotence was checked the same way. Feeding the tool an already-cleaned file reports removed: {} and returns output byte-for-byte identical to its input — no further shrinkage, no silent re-compression. A tool that re-encodes on every pass cannot make that statement, because its output would differ each time.

To reproduce it yourself, decode both versions and compare the pixel buffers rather than the files. With Pillow installed that is two lines; without it, hash the scan data or the IDAT stream with the commands above. The point is that the comparison is on pixels, because the pixel data is exactly what a segment-level strip is not allowed to touch.

Local or upload? Ask this before pasting an image into a website

A browser cleaner and a server cleaner can produce a byte-for-byte identical result and still be very different propositions, and almost no comparison mentions why.

When you drop a file into a server-side remover, that server receives the original — metadata fully intact, GPS included. The cleaning happens after the thing you wanted to protect has already been transmitted. What the site retains, for how long, and whether the file is used for anything else is described in a privacy policy most people never open.

The second difference is mechanical. Some server-side cleaners re-encode the image while processing it. Re-encoding strips metadata as a side effect, but it also recompresses the pixels, so you pay quality to solve a problem that never required touching the pixels at all. You can spot this from the output: a segment-level strip returns a file whose image data is unchanged, while a re-encode returns a slightly different one every time you run it.

Checking a claim of “runs in your browser” in about fifteen seconds

  1. Open DevTools (F12) and switch to the Network tab.
  2. Clear the log, load an image, then click the button that processes it.
  3. Count what follows. A genuinely local tool produces none beyond the page's own assets — no upload request, no image endpoint, no analytics payload carrying your file.

That check is the reason the cleaner on this site is plain HTML and JavaScript with no backend: there is no server that could receive your file. The core also runs under Node, which is what makes the batch script above readable and testable before you point it at anything real.

The honest limits

One practical habit: keep the original. Strip metadata from a copy, not the only version you have, because EXIF sometimes holds the only record of when and where something was shot.

Related: the two layers: what can and cannot be removed · the FAQ · cleaning invisible characters across a folder

Frequently asked questions

Does removing metadata reduce image quality?

No. Metadata sits in header segments, before the compressed image data, so deleting it never touches a pixel. This is measurable rather than a promise: the scan data of the test JPEG hashed 57f33aa4… before and after the strip, while the same photo saved again at the same quality hashed 5f3a3c6a…. Re-encoding is what costs quality. Removing a header segment is not re-encoding.

How do I remove EXIF without re-encoding the image?

Use a segment-level tool. exiftool -all= -overwrite_original photo.jpg does it in place, and the browser cleaner on this site does it without installing anything. Avoid im.save() in Pillow with exif=b"": that writes a brand new JPEG, which strips the metadata as a side effect but re-compresses your pixels every time you run it.

Which metadata must I keep?

Five things, and the whole reason to check before trusting any cleaner: the JPEG APP14 Adobe colour transform, the PNG iCCP colour profile, PNG gAMA / cHRM / sRGB, PNG pHYs print size, and the EXIF Orientation tag. Drop the first four and colour or print size shifts. Drop Orientation and portrait photos from a phone come back sideways.

Does removing metadata remove a watermark?

No. If a mark is burned into the pixels, it is part of the image and header surgery will not touch it. And Claude's text watermark is not in the file at all — it is a statistical pattern in how the words were chosen, so no file cleaner can reach it. The two layers, explained →

Is it safe to delete the C2PA credential?

It is removable, and it is a genuine trade-off rather than a clear win. C2PA is a signed record of who made or processed the file; deleting it removes the machine's claim, but also your own evidence if you shot or drew the thing yourself. It was never hidden — anyone with a C2PA reader could already see it. Why this differs from a text watermark →

Can I strip metadata from a whole folder?

Yes: exiftool -all= -r -overwrite_original -ext jpg -ext png ./photos walks the tree. Then verify one file with exiftool -a -G1 -s and check the pixels are untouched with the hash commands above. Test on copies first — -overwrite_original does exactly what it says.

Is it safe to upload a photo to an online metadata remover?

Consider the order of events. A server-side tool receives the original file, GPS and all, and cleans it afterwards — the data you wanted to protect has already been transmitted. Whether the file is kept, for how long, and what else it is used for lives in a privacy policy most people never open. You can test any site that claims to run locally in about fifteen seconds: open DevTools, watch the Network tab while you process a file, and count what leaves. This site's cleaner is plain HTML and JavaScript with no backend, so there is no server that could receive your file.