Illustrations from X keep their origin record 0.1% of the time (49,649 files measured)

AI・自動化
スポンサーリンク

Illustrations that came through X keep their origin record 0.1% of the time (I read 49,649 of them)

I run Fudemark, a scoring tool that helps tell hand-drawn illustration apart from AI illustration. I have been telling people that once an image has been through social media, the file no longer says where it came from. I had never actually counted it, so I counted it.

The short version:

  • Of 23,809 illustrations taken from X, 12 still carried a record of their origin (0.1%)
  • Of 17,008 taken from pixiv, 4,965 did (29.2%)

These are pictures from the same period, collected on the same site. The same kind of artists, broadly the same tools. The only thing that differs is which route the file travelled.

スポンサーリンク

Why this matters if you stream

Character art, thumbnails, emotes, overlay assets. If you stream, a lot of the images you work with are commissioned and delivered to you.

Which is where the question comes up: “wasn’t that drawn by an AI?” If you open the file to check, and you saved that file off X, there is almost certainly nothing inside it. That is not because you checked it wrong. The contents were dropped when the image went through X.

It hurts in the other direction too. Art you drew and posted to X carries no record of your tools anywhere it travels afterwards. When you find it reposted without permission, the inside of the file is not going to back you up.

What I measured

Danbooru, an illustration board, has a field where the uploader writes down where they found the picture. The file it stores is the full-size one. That means I can take one population and split it by the route each file travelled. That is the whole trick here.

  • Subject: 49,649 illustrations posted from 2026 onward, with no AI tag on them
  • Read: C2PA signatures, creation-tool records, camera make and model, capture time, XMP, resolution
  • The images themselves are not kept. I keep the values I read and throw the file away
  • For comparison I also read AI-generated images, photographs, and originals from my own machine. 66,209 files in total

I counted “has an origin record” two ways. Showing only the generous one would flatter the result, so here are both.

  • Origin: a C2PA signature, a creation-tool name, a camera make or model, or a capture time. Something that says what produced this file
  • Anything at all: the above, plus a resolution field on its own, or XMP on its own. Merely “the metadata area is not empty”

The numbers in this article are the strict count. On the generous count, 7,767 of the 49,649 (15.6%) have something.

Survival by route

Taken fromFilesHas origin recordRateShare JPEGMean size
x.com23,809120.1%96.9%603 KB
pixiv (i.pximg.net)17,0084,96529.2%55.9%3,717 KB
Xiaohongshu7368010.9%54.1%3,626 KB
twitter.com71720.3%95.7%634 KB
bilibili66512719.1%70.5%4,560 KB
Bluesky55810.2%97.7%1,063 KB
Weibo23600.0%99.6%3,136 KB
DeviantArt2106932.9%59.5%1,265 KB
Instagram10800.0%84.3%373 KB
misskey.io571933.3%3.5%766 KB
Skeb5211.9%28.8%372 KB

Across all routes, 6,162 of 49,649 (12.4%). If you do not split by route, that 12.4% average hides the fact that it is made of a 0.1% road and a 29.2% road.

X carried the most pictures, and X is where the least survived. So the largest single flow of circulating art is the one dropping the records.

One caveat on the Skeb row. The image on a public Skeb page is not the delivered file, so that 1.9% does not tell you anything about what a client actually receives.

Why it disappears

The two right-hand columns are the answer.

  • Files that came through X: 96.9% JPEG, 603 KB on average
  • Files that came through pixiv: 55.9% JPEG, 3,717 KB on average (six times larger)

X re-encodes what you upload into JPEG. Re-encode a file and the text fields inside it do not come along. pixiv serves the file you uploaded, so whatever was written in it stays written.

There was one more nice detail. Looking at the 12 survivors from X one by one, 11 of them are PNG: seven say CLIP STUDIO PAINT, three say Photoshop, one says GIMP. Exactly one JPEG survived. Only the files that were not rebuilt made it through.

The official industry mark is not helping yet

There is a standard for proving origin called C2PA. It embeds a signed statement of who made the file. Adobe and Microsoft are behind it, and it is serious work.

Of my 66,209 files, 664 carried C2PA. Split by what the signature actually says:

What the C2PA saysCountShare
An AI made this61993.2%
A human-operated tool (Photoshop, Lightroom, …)71.1%
Says neither385.7%

By the tool that wrote it: OpenAI 457, ChatGPT 66, Google 47, Grok 19. Laid out like that, it is nearly all generation services.

And of the 49,649 illustrations posted from 2026 onward, 9 carried C2PA. Seven of those nine came from Adobe tools and two from OpenAI.

This was the part that surprised me most. I had understood C2PA as a mark that says “this is genuine.” In the files actually circulating, it has so far spread as a mark that says “an AI made this.” The hand-drawn side is barely signing anything.

I had been thinking “adopt the standard and the problem is solved.” There turned out to be nothing on the hand-drawn side to adopt yet.

What actually survived was CLIP STUDIO PAINT

So what is written inside the 6,162 files that did keep a record? Counting them, the answer is not a standard.

Creation-tool record foundCountOf 49,649
CLIP STUDIO PAINT4,8779.8%
Photoshop (Save for Web)3060.6%
Adobe Photoshop CS5.1 Windows890.2%
Adobe Photoshop CC 2019 (Windows)870.2%
Adobe Photoshop CS6 (Windows)670.1%

The most common origin record on circulating illustration is CLIP STUDIO PAINT. Not a signature, not a certificate: one line of text the exporter quietly writes. That is, right now, the most widely surviving statement of origin there is.

I also read originals from my own machine, which cuts the other way. Of 35 originals drawn in a tablet painting app (ibisPaint), not one carried any record. Freshly exported originals, completely empty. Depending on what you draw with, there is nothing in the file to begin with.

Photographs behave completely differently. Of 408 shots from phones and cameras, all 408 kept their records (100%). The camera always writes them. It is only pictures that go out naked.

Check it yourself

I wrote a short version that reads 150 files, and ran it. Python and Pillow only. It reads each image and discards it, so nothing accumulates on your disk.

"""Count how many circulating illustrations still carry a record of where they came from,
grouped by the platform they were taken from. Images are read and thrown away, never stored.

    python where_did_it_go.py 200
"""
importcollections,io,sys,time,urllib.parse,urllib.request
fromPILimport Image

UA = {"User-Agent": "metadata-survival-check/0.1 (non-commercial research)"}
API = "https://danbooru.donmai.us/posts.json?"


defget(url, binary=False):
    for i in range(3):
        try:
            with urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=120) as r:
                return r.read() if binary else r.read().decode("utf-8")
        except Exception:
            if i == 2:
                return None
            time.sleep(5 * (i + 1))


deforigin_record(data):
   """Does the file say which tool or camera produced it?"""
    try:
        im = Image.open(io.BytesIO(data))
    except Exception:
        return None
    tool = im.info.get("Software") or ""
    for key in ("parameters", "Comment", "Description"):          # PNG text chunks
        tool = tool or (im.info.get(key) or "")[:40]
    exif = {}
    try:
        exif = {k: v for k, v in (im.getexif() or {}).items()}
    except Exception:
        pass
    has_c2pa = b"jumb" in data[:65536] or b"c2pa" in data[:65536]
    camera = exif.get(271) or exif.get(272)                        # Make / Model
    software = exif.get(305) or tool                               # Software
    return {"fmt": im.format, "kb": len(data) // 1024,
            "found": bool(has_c2pa or camera or software),
            "what": str(software or camera or ("C2PA" if has_c2pa else ""))[:34]}


defmain(want=200):
    importjson
    seen, page, rows = 0, None, []
    while seen < want:
        q = {"tags": "date:>=2026-01-01", "limit": 100}
        if page:
            q["page"] = page
        posts = json.loads(get(API + urllib.parse.urlencode(q)) or "[]")
        if not posts:
            break
        page = f"b{min(p['id']forpinposts)}"
        for p in posts:
            if seen >= want or p.get("rating") not in ("g", "s") or not p.get("file_url"):
                continue
            data = get(p["file_url"], binary=True)
            if not data:
                continue
            got = origin_record(data)
            del data
            if not got:
                continue
            seen += 1
            rows.append((urllib.parse.urlparse(p.get("source") or "").netloc.lower() or "(none)", got))
            time.sleep(0.5)

    by = collections.defaultdict(list)
    for dom, got in rows:
        by[dom].append(got)
    print(f"\n{'taken from':26}{'n':>5}{'has record':>11}{'rate':>7}{'JPEG':>6}{'avg KB':>7}")
    for dom, gs in sorted(by.items(), key=lambda kv: -len(kv[1])):
        f = sum(g["found"] for g in gs)
        j = sum(g["fmt"] == "JPEG" for g in gs)
        print(f"{dom:26}{len(gs):5}{f:11}{f/len(gs)*100:6.1f}%{j/len(gs)*100:5.0f}% "
              f"{sum(g['kb']forgings)/len(gs):7,.0f}")
    total = sum(g["found"] for gs in by.values() for g in gs)
    print(f"\n{'TOTAL':26}{len(rows):5}{total:11}{total/max(len(rows),1)*100:6.1f}%")


if __name__ == "__main__":
    main(int(sys.argv[1]) if len(sys.argv) > 1 else 200)

Here is what the 150-file run printed:

taken from                     n  has record    rate   JPEG  avg KB
x.com                         82           0    0.0%    96%     580
i.pximg.net                   47          14   29.8%    53%   4,182
www.xiaohongshu.com            4           0    0.0%    75%   1,620
www.weibo.com                  3           0    0.0%   100%     722
www.bilibili.com               2           0    0.0%     0%   1,358
bsky.app                       1           0    0.0%   100%   1,775

TOTAL                        150          17   11.3%

At 150 files: pixiv 29.8%, X 0.0%. Against 29.2% and 0.1% from the 49,649-file run, that is the same answer. So this is not an artifact of my particular sample, and you should get something close if you run it yourself.

Suggestions, for both sides of a commission

If you commission art. Take delivery by a route that does not pass through social media: a file transfer service, a shared cloud folder, a pixiv message. Anything that will not re-encode. If the copy you keep is one you right-click-saved from X, you are holding a file from the 0.1% side, and there will be nothing in it the day you want to establish who drew it.

If you draw and post. Exporting as PNG carries the creation-tool record with it (CLIP STUDIO PAINT does write one). The copy you post to X will be rebuilt regardless, so the practical shape is to keep a second home for the original outside X: pixiv, your own site, your own cloud. One copy for circulation, one copy as the original.

If you are trying to verify something. Deciding hand-drawn versus AI from the inside of a file you found on social media is not going to work. A file with no record is telling you about the road it travelled, not about the person who drew it. Please do not treat an empty file as evidence of anything. 23,797 of those 23,809 are in exactly that state.

What these numbers cannot tell you

  • Danbooru’s “where I found it” is typed in by the uploader. Some of it is wrong and some is blank (670 files had nothing in the field)
  • “No record” does not mean “drawn by an AI.” Go through X and a hand-drawn piece loses it too. That is what this article counted
  • Some uploaders re-save the file themselves after downloading it. I cannot separate that effect from the platform’s
  • The subject is the kind of art that collects on Danbooru. Read it as a statement about all illustration and you are reading past the sample

I published both the strict and the generous count because I did not want to show only the one that suits me. If you would define it differently, origin_record in the code above is the place to change.

In Fudemark I read the records in a submitted image and produce a score. A 50 means “cannot determine,” and as counted here, pictures that came through X collect there. Making the tool better will not let it read a record that is not present. I would rather write that down plainly.

Karashiya Karashi

コメント

タイトルとURLをコピーしました