AI Metadata Preservation Test: XMP Before and After PNG Resizing
Rewarx practical lab · November 3, 2026 · Synthetic fixtures
AI Metadata Preservation Test: XMP Before and After PNG Resizing
We created one synthetic PNG with a deliberately seeded IPTC-XMP DigitalSourceType field and EXIF Artist value, then ran three local transformations using Python 3.9.6 and Pillow 11.3.0. Default save and resize lost both tested fields. Explicitly supplying the XMP and EXIF during resize preserved them. This is a reproducible local fixture test, not a marketplace study or a Shopify CDN test.
Actual results
| Output | Dimensions | IPTC-XMP field | EXIF Artist | C2PA |
|---|---|---|---|---|
| synthetic-source.png | 600 × 600 | Present | Synthetic metadata fixture | Not tested |
| reencoded-default.png | 600 × 600 | Absent | Absent | Not tested |
| resized-default.png | 500 × 500 | Absent | Absent | Not tested |
| resized-preserved.png | 500 × 500 | Present | Synthetic metadata fixture | Not tested |
The sample pixels are programmatically generated. The trainedAlgorithmicMedia value is intentionally seeded test data; it is not a claim that this synthetic fixture was created by a trained model. Never infer genuine provenance from the mere presence of this writable field.
Three different concepts
| Concept | What this experiment checks |
|---|---|
| EXIF | A separate metadata structure; Artist was used only as a control value. |
| IPTC-XMP | A DigitalSourceType property inside an XMP packet; XML parsing checked the actual property. |
| C2PA | Signed provenance assertions and validation. No C2PA manifest, signature or trust chain was created or tested. |
An EXIF field does not stand in for IPTC-XMP. Copying an XMP packet does not create or validate C2PA credentials. A modification can require a provenance-aware workflow; do not claim a signature remained valid just because metadata bytes survived.
Why preserve source disclosures
Google Merchant Center requires AI-generated images to retain the relevant IPTC DigitalSourceType metadata. It explicitly says not to remove embedded AI source tags. Read the official AI-generated content policy. This experiment demonstrates a loss mode and a local preservation control; it is not a recipe for removing disclosure from product assets.
Use accurate source classifications for real assets. The fixture below is isolated test material. Do not copy a synthetic label into unrelated real photography or overwrite an asset’s authentic source information.
Reproduce the local experiment
Save this as metadata_preservation_lab.py, install Pillow, and choose a new output directory. The script refuses to overwrite an existing synthetic source. It creates a source, default re-encode, default resize and explicit preservation variant, then parses the outputs and writes results.json.
python -m pip install Pillow==11.3.0
python metadata_preservation_lab.py fresh-metadata-testComplete experiment source
"""Synthetic local PNG XMP/EXIF experiment. Not a C2PA or Shopify CDN test.
python metadata_preservation_lab.py OUTPUT_DIRECTORY
Requires Pillow. Never run over a real product source directory.
"""
import json,sys,platform,xml.etree.ElementTree as ET
from pathlib import Path
import PIL
from PIL import Image,PngImagePlugin
TAG='http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia'
XMP=('<?xpacket begin=""?><x:xmpmeta xmlns:x="adobe:ns:meta/">'
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'
'<rdf:Description xmlns:d="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" '
'd:DigitalSourceType="'+TAG+'"/></rdf:RDF></x:xmpmeta><?xpacket end="w"?>')
def read_tags(path):
with Image.open(path) as im:
packet=im.info.get('XML:com.adobe.xmp','')
root=ET.fromstring(packet) if packet else None
values=[]
if root is not None:
for node in root.iter():
values.extend(v for k,v in node.attrib.items() if k.endswith('}DigitalSourceType'))
return {'file':Path(path).name,'dimensions':list(im.size),'xmp_present':bool(packet),
'digital_source_type':values,'exif_artist':im.getexif().get(315),'c2pa_tested':False}
def run(out):
out=Path(out);out.mkdir(parents=True,exist_ok=True)
src=out/'synthetic-source.png'
if src.exists():raise ValueError('Use a fresh output directory to protect previous runs')
# This is a metadata fixture, not a claim that these programmatic pixels were generated by a trained AI.
info=PngImagePlugin.PngInfo();info.add_itxt('XML:com.adobe.xmp',XMP)
exif=Image.Exif();exif[315]='Synthetic metadata fixture'
Image.new('RGB',(600,600),(38,95,82)).save(src,pnginfo=info,exif=exif)
with Image.open(src) as im:
im.save(out/'reencoded-default.png')
im.resize((500,500)).save(out/'resized-default.png')
keep=PngImagePlugin.PngInfo();keep.add_itxt('XML:com.adobe.xmp',im.info['XML:com.adobe.xmp'])
im.resize((500,500)).save(out/'resized-preserved.png',pnginfo=keep,exif=im.getexif())
rows=[read_tags(out/n) for n in ['synthetic-source.png','reencoded-default.png','resized-default.png','resized-preserved.png']]
result={'scope':'Synthetic PNG fixture with deliberately seeded XMP; no real product or Shopify CDN tested','python':platform.python_version(),'pillow':PIL.__version__,'rows':rows}
(out/'results.json').write_text(json.dumps(result,indent=2));return result
if __name__=='__main__':print(json.dumps(run(sys.argv[1]),indent=2))
Actual machine-readable results
{
"scope": "Synthetic PNG fixture with deliberately seeded XMP; no real product or Shopify CDN tested",
"python": "3.9.6",
"pillow": "11.3.0",
"rows": [
{
"file": "synthetic-source.png",
"dimensions": [
600,
600
],
"xmp_present": true,
"digital_source_type": [
"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
],
"exif_artist": "Synthetic metadata fixture",
"c2pa_tested": false
},
{
"file": "reencoded-default.png",
"dimensions": [
600,
600
],
"xmp_present": false,
"digital_source_type": [],
"exif_artist": null,
"c2pa_tested": false
},
{
"file": "resized-default.png",
"dimensions": [
500,
500
],
"xmp_present": false,
"digital_source_type": [],
"exif_artist": null,
"c2pa_tested": false
},
{
"file": "resized-preserved.png",
"dimensions": [
500,
500
],
"xmp_present": true,
"digital_source_type": [
"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
],
"exif_artist": "Synthetic metadata fixture",
"c2pa_tested": false
}
]
}What the experiment does not establish
- It does not measure metadata survival across a real merchant catalog.
- It does not test JPEG, WebP, AVIF or every Pillow version. Current documentation can describe newer behavior; rerun for your deployed version.
- It does not upload to Shopify, test Shopify image transformations or establish any marketplace stripping policy.
- It does not measure C2PA signature validity or anti-tampering properties.
- It does not prove that preserving these two fields makes an asset compliant with all platform requirements.
Apply the method to your own pipeline
Keep an original in a separate archive. Make a staging copy and record the exact tool versions and transformation command. Inspect the required field on the output, then repeat after each authorized export or delivery stage. For signed provenance, use appropriate C2PA validation rather than this XML-property check. If a stage loses a required disclosure, repair the workflow before publishing and verify again.
Related practical guides
- Feed image dimension preflight
- Variant image CSV preflight
- IPTC source type reference
- Product image accuracy and disclaimers
Sources checked November 3, 2026
- Google AI-generated image policy
- IPTC Digital Source Type vocabulary
- Pillow image format documentation
- C2PA 2.2 specification, a versioned technical reference
Rewarx workflow entry
Inspect the Rewarx background-removal entry page. Its primary button leads to Studio authentication. The validators and metadata lab here run locally; this article does not claim the Studio performs these checks or preserves metadata automatically.