EU AI Act Art. 50 · worked example · measured

Before / after: three disclosure layers on one image

Visible label + IPTC DigitalSourceType (XMP) + signed C2PA manifest. Read back with independent tools.

Stand-in images. Both images were made by code (Pillow/numpy); no AI model was used. The labels and metadata show what a real generative edit or AI-generated image must carry. The C2PA signature uses a TEST certificate that is not on the C2PA trust list.

1 · Real photo + AI-style insert

NASA public-domain photo + planet composited by code → labelled as compositeWithTrainedAlgorithmicMedia

Before: composite image without any disclosure
Before · ai-content-label-before-composite.jpg
no label, no metadata, no manifest
After: same image with visible label
After · ai-content-label-after-composite.jpg
label + XMP + C2PA
Check (tool)BeforeAfter
IPTC DigitalSourceType — exiv2 0.27.6✗ not present✓ …/digitalsourcetype/compositeWithTrainedAlgorithmicMedia
IPTC DigitalSourceType — exiftool 12.76✗ not present✓ …/digitalsourcetype/compositeWithTrainedAlgorithmicMedia
C2PA — c2pa-python 0.37.12, default trust✗ none — ManifestNotFound: no JUMBF data found✓ Valid (signingCredential.untrusted)
C2PA — c2pa-python, TEST root as trust anchor✗ none — ManifestNotFound: no JUMBF data found✓ Trusted
C2PA — c2patool 0.26.0, default✗ Error: No claim found◐ Invalid (signingCredential.untrusted)
C2PA — c2patool, TEST root as trust anchor✗ Error: No claim found✓ Trusted
c2pa.actions digitalSourceType—✓ …/digitalsourcetype/compositeWithTrainedAlgorithmicMedia
Visible label✗ none✓ “Edited with AI” · 37 px · contrast 17.74:1

2 · Fully generated image

Fully procedural image → labelled as trainedAlgorithmicMedia (stand-in; the accurate code for this file is algorithmicMedia)

Before: synthetic image without any disclosure
Before · ai-content-label-before-synthetic.png
no label, no metadata, no manifest
After: same image with visible label
After · ai-content-label-after-synthetic.png
label + XMP + C2PA
Check (tool)BeforeAfter
IPTC DigitalSourceType — exiv2 0.27.6✗ not present✓ …/digitalsourcetype/trainedAlgorithmicMedia
IPTC DigitalSourceType — exiftool 12.76✗ not present✓ …/digitalsourcetype/trainedAlgorithmicMedia
C2PA — c2pa-python 0.37.12, default trust✗ none — ManifestNotFound: no JUMBF data found✓ Valid (signingCredential.untrusted)
C2PA — c2pa-python, TEST root as trust anchor✗ none — ManifestNotFound: no JUMBF data found✓ Trusted
C2PA — c2patool 0.26.0, default✗ Error: No claim found◐ Invalid (signingCredential.untrusted)
C2PA — c2patool, TEST root as trust anchor✗ Error: No claim found✓ Trusted
c2pa.actions digitalSourceType—✓ …/digitalsourcetype/trainedAlgorithmicMedia
Visible label✗ none✓ “Created with AI” · 29 px · contrast 17.74:1

EU AI Act Art. 50 · survival test · measured

Which disclosure layer survives a typical publishing pipeline?

Each labelled file was pushed through one step at a time and read back. IPTC = Xmp.iptcExt.DigitalSourceType via exiv2 0.27.6 · C2PA = c2pa-python 0.37.12 validation · Label = label box in frame + tesseract OCR of the label region.

✓ survived◐ partly / degraded✗ lost– not applicable

Pipeline stepComposite · JPEG 1024×1024Synthetic · PNG 1200×800
IPTC DSTC2PAVisible labelIPTC DSTC2PAVisible label
controlUnchanged copy (control)✓ present✓ Trusted*✓ readable (37.0 px)✓ present✓ Trusted*✓ readable (29.0 px)
pillow-resavePillow re-save (default options)✗ stripped✗ removed✓ readable (37.0 px)✗ stripped✗ removed✓ readable (29.0 px)
pillow-resave-keep-xmpPillow re-save, XMP/EXIF passed through explicitly✓ present✗ removed✓ readable (37.0 px)✓ present✗ removed✓ readable (29.0 px)
im-resize-768wImageMagick resize to 768 px wide (WordPress 'medium_large'-like)✓ present✗ broken✓ readable (27.8 px)✓ present✗ removed✓ readable (18.6 px)
im-resize-300ImageMagick resize to fit 300x300 (WordPress 'medium'-like)✓ present✗ broken✓ readable (10.8 px)✓ present✗ removed✓ readable (7.2 px)
im-thumb-150-cropImageMagick 150x150 centre crop (WordPress 'thumbnail'-like)✓ present✗ broken◐ tiny, OCR fail (5.4 px)✓ present✗ removed◐ 38.9% in frame
im-crop-bottom-12pctImageMagick crop: keep top 88% (e.g. re-framing for a social post)✓ present✗ broken✗ cropped out✓ present✗ removed✗ cropped out
cwebp-defaultcwebp -q 80 (default: -metadata none)✗ stripped✗ removed✓ readable (37.0 px)✗ stripped✗ removed✓ readable (29.0 px)
cwebp-metadata-allcwebp -q 80 -metadata all✓ present✗ removed✓ readable (37.0 px)✓ present✗ removed✓ readable (29.0 px)
pillow-webpPillow save as WebP (default options)✗ stripped✗ removed✓ readable (37.0 px)✗ stripped✗ removed✓ readable (29.0 px)
jpegoptim-losslessjpegoptim (default, lossless, keeps markers)✓ present✗ removed✓ readable (37.0 px)– n/a– n/a– n/a
jpegoptim-strip-alljpegoptim --strip-all✗ stripped✗ removed✓ readable (37.0 px)– n/a– n/a– n/a
exiftool-stripexiftool -all= (strip all metadata)✗ stripped✗ removed✓ readable (37.0 px)✗ stripped✗ removed✓ readable (29.0 px)
im-stripImageMagick -strip✗ stripped✗ removed✓ readable (37.0 px)✗ stripped✗ removed✓ readable (29.0 px)
im-png-to-jpgImageMagick PNG -> JPEG conversion– n/a– n/a– n/a✓ present✗ removed✓ readable (29.0 px)
optipng-strip-alloptipng -strip all– n/a– n/a– n/a✗ stripped✗ removed✓ readable (29.0 px)
Survived, of 13 common pipelinesexcl. control and re-sign7/130/1311/137/130/1311/13
im-resize-768w-resign remedy, not a common pipelineImageMagick 768 px + new C2PA manifest signed (c2pa-aware CMS step)✓ present✓ Trusted*✓ readable (27.8 px)✓ present✓ Trusted*✓ readable (18.6 px)

* “Trusted” = c2pa-python validation_state with the TEST root CA supplied as trust anchor; with default trust settings the same files read “Valid” with signingCredential.untrusted. “broken” = APP11/JUMBF bytes still present after ImageMagick resize but the manifest fails to parse/validate (“invalid embedded file box”). OCR accepted “Al” for “AI” (sans-serif capital I). “readable” means tesseract read the label after 4× upscaling; human legibility at small pixel sizes was not tested. WordPress, Instagram, Meta, LinkedIn, X, TikTok and CDNs were NOT tested; the ImageMagick/cwebp/Pillow steps only emulate common operations. Tools: ImageMagick 6.9.12-98 · cwebp 1.3.2 · jpegoptim v1.4.7 · OptiPNG 0.7.8 · exiftool 12.76 · Pillow 12.2.0.


Visible label variants · contrast measured on rendered pixels

“Yapay zekâ ile oluşturuldu” · “Created with AI” · “Mit KI erzeugt”

Opaque badges: 24/24 variants pass (min 17.74:1). Translucent black box (alpha 90/255) over the sun: 0/3 pass (worst 2.48:1) — avoid see-through labels.

Label “Yapay zekâ ile oluşturuldu”, dark on bright background
Yapay zekâ ile oluşturuldu · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Yapay zekâ ile oluşturuldu”, light on dark background
Yapay zekâ ile oluşturuldu · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Yapay zekâ ile düzenlendi”, dark on bright background
Yapay zekâ ile düzenlendi · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Yapay zekâ ile düzenlendi”, light on dark background
Yapay zekâ ile düzenlendi · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Created with AI”, dark on bright background
Created with AI · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Created with AI”, light on dark background
Created with AI · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Edited with AI”, dark on bright background
Edited with AI · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Edited with AI”, light on dark background
Edited with AI · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Mit KI erzeugt”, dark on bright background
Mit KI erzeugt · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Mit KI erzeugt”, light on dark background
Mit KI erzeugt · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Mit KI bearbeitet”, dark on bright background
Mit KI bearbeitet · dark on bright
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Mit KI bearbeitet”, light on dark background
Mit KI bearbeitet · light on dark
✓ PASS 17.74:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Yapay zekâ ile oluşturuldu”, translucent (alpha 90/255) on sun background
Yapay zekâ ile oluşturuldu · translucent (alpha 90/255) on sun
✗ FAIL 2.48:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Created with AI”, translucent (alpha 90/255) on sun background
Created with AI · translucent (alpha 90/255) on sun
✗ FAIL 2.5:1 worst case behind text (WCAG AA needs ≥ 4.5:1)
Label “Mit KI erzeugt”, translucent (alpha 90/255) on sun background
Mit KI erzeugt · translucent (alpha 90/255) on sun
✗ FAIL 2.49:1 worst case behind text (WCAG AA needs ≥ 4.5:1)

Commands

python3 work/build.py      # stand-ins, labels, exiftool XMP write, C2PA signing (c2pa-python)
python3 work/labels.py     # label variants + contrast
python3 work/survival.py   # readback (exiv2, exiftool, c2pa-python, c2patool) + 15 pipeline steps
exiv2 -q -K Xmp.iptcExt.DigitalSourceType -Pv FILE
exiftool -s3 -XMP-iptcExt:DigitalSourceType FILE
c2patool FILE trust --trust_anchors root.pem

Prepared by Ali Karabüyük · alikarabuyuk.com · 6 October 2026. Not legal advice.