I opened the asset folder for my wallpaper app and stopped.
Hundreds of PNGs. Some generated with Gemini, some drawn by hand years ago, some assembled from stock pieces and exported. The filenames tell me nothing about which is which.
My first thought was: run them through SynthID. Google's generations carry an invisible watermark, so feeding each file through a detector should sort the pile. I started looking into it — and found that the idea itself was built on a wrong assumption.
The detector doesn't answer in black and white.
The question: can SynthID tell you whether an image came from Gemini?
The short answer is only partly — and the part it covers is the opposite of the part you usually want.
When you're sorting a folder, the answer you're reaching for is usually "this one is not AI-generated." That's exactly the answer SynthID cannot give you. It can speak confidently about "this came from Google AI," and almost nothing else.
Miss that asymmetry, and you'll build a ledger that's quietly wrong.
SynthID lives in the pixels, not in the file's metadata
Two families of provenance mechanisms are in play, and they fail in opposite ways.
| Mechanism | Where it lives | Survives a screenshot? | Information carried |
|---|---|---|---|
| C2PA (Content Credentials) | File metadata | No | Rich — origin, timestamp, edit history |
| SynthID | The pixels themselves | Yes | Almost none — closer to presence/absence |
C2PA is a signed record attached to the file. It can say who made it, when, and with which model — but a re-encode or a screenshot strips it clean.
SynthID embeds a signal into the pixels without visibly changing the image. Screenshot it, push it through social media compression, and the signal travels with the picture. What it can't do is carry detail. Assume the watermark says little beyond "a Google AI watermark is here."
When Google and OpenAI announced a combined two-layer approach in May 2026, this is why: the failure modes complement each other neatly. Strip the C2PA data and SynthID still flags AI origin. Keep the C2PA data and you get the full story. Neither layer alone closes the gap.
Before I understood this split, I assumed a watermark meant the problem was solved end to end. It isn't a container for information. It's a mark.
A positive and a negative are not equal
This is the heart of it. Upload an image to the Gemini app and ask whether it was made with Google AI, and you get a result. The two possible results are not symmetrical.
| Result | What it establishes | What it doesn't |
|---|---|---|
| Watermark detected | All or part of the image was created or edited by Google AI | Which model, when, or which region of the image |
| Watermark not detected | You can't positively attribute it to Google AI | That it isn't AI-generated at all |
A negative leaves at least three live possibilities.
One: a human really did make it. Two: another vendor's AI made it — SynthID is Google's scheme, so other systems' output generally won't carry it. Three: it is Google AI output, but your processing weakened the watermark past the point of detection.
The third one is the trap, because your own pipeline is a plausible route into it.
So detection is strong only when positive. A negative settles nothing. The structure mirrors a medical screen — negative doesn't mean healthy. Collapse that distinction and you end up with a ledger that files unmarked images as "hand-made," with no visible sign that it's wrong.
Which pushed me toward keeping the verdict as three states, not two: confirmed, unknown, and recorded-at-source. Never rounding a negative down to "mine" is the minimum price of working with this system honestly.
How much processing can the watermark survive?
Which makes the third possibility worth pricing out, because it collides directly with your export pipeline.
Pulling together what's publicly described, the tendencies look roughly like this.
| Operation | Watermark tendency |
|---|---|
| Moderate JPEG compression | Usually survives |
| Resizing (down to roughly half) | Usually survives |
| Light cropping, color space conversion | Usually survives — the signal is spread across the whole image |
| Mild brightness/contrast adjustment | Usually survives |
| Extreme compression, heavy filtering | May weaken or disappear |
| Style transfer, image-to-image regeneration | May disappear |
| Content-aware fill and other large repaints | May disappear |
The exact thresholds shift with implementation and environment, so treat none of this as a guarantee. What you can actually establish as an indie developer is narrower: what happens after your pipeline runs.
For my wallpaper app, generated images get resized to each device's native resolution and re-encoded to WebP. Those sit on the survives-usually side of the table. But "usually survives" isn't a promise. My older export script also applied fairly aggressive sharpening and noise reduction for file-size reasons, and the table above says nothing about how that interacts with the signal.
Which is why redesigning your export pipeline around the watermark is the wrong instinct. You'd be bending choices made for image quality and delivery size in service of someone else's verification step. The watermark exists so third parties can check later. It was never meant to be the foundation of your own asset records.
Record it at generation time instead of detecting it later
Stepping back, the "run each file through a detector" plan was solving the problem from the wrong end.
Everything I wanted to know was fully available at generation time. Which model, which prompt, what timestamp. I had thrown that away and was trying to reconstruct it probabilistically afterward. I was handing a deterministic fact to a statistical mechanism. Wrong direction.
The minimum fix is a sidecar JSON written next to every generated file.
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
MODEL_ID = "gemini-3.1-flash-lite-image" # use the actual model ID you generated with
def write_sidecar(image_path: Path, prompt: str, model_id: str = MODEL_ID) -> Path:
"""Drop a provenance record beside the image. Call it right after generation."""
image_bytes = image_path.read_bytes()
record = {
"file": image_path.name,
# lets you verify later that the bytes are the same ones you generated
"sha256": hashlib.sha256(image_bytes).hexdigest(),
# hash, not the full prompt — store the text separately if you need it
"prompt_sha256": hashlib.sha256(prompt.encode("utf-8")).hexdigest(),
"model_id": model_id,
"generated_at": datetime.now(timezone.utc).isoformat(),
# three states, so a negative never gets rounded down to "mine"
"origin": "confirmed_generated",
}
sidecar = image_path.with_suffix(image_path.suffix + ".json")
sidecar.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
return sidecarThe reason origin isn't a boolean is the whole point of this article. What you wrote yourself at generation time is confirmed_generated. An older asset with no record is unknown — not not_generated. Even if a detector comes back negative, it stays unknown.
Because when you revisit the ledger months later, you need to tell apart "never checked," "checked and couldn't tell," and "known to be generated." Flatten those into a boolean and the distinction is gone for good.
If you already have a large pile of unlabeled assets, SynthID detection is genuinely useful as a supplement: promote anything that comes back positive to confirmed_generated, and leave negatives at unknown. Decide up front that detection only ever moves records in one direction, and you stop fighting the asymmetry.
For a fuller provenance design, Recording the Provenance of Gemini Output — Design for Reproducibility and Audit covers the foundations. If the August 17 image model shutdown is forcing you to separate regenerable assets from frozen ones, Images From a Model That's Shutting Down Can Never Be Made Again — Managing Regenerability With a Ledger is the more specific piece.
Images aren't the only output that carries a watermark — the audio side is covered in Lyria 3 Pro API Implementation Guide — Generating Full-Length Studio-Quality Music From Text and Images.
One thing you can check today
If you're shipping generated images in an app or service, there's a single useful experiment.
Take a few final outputs from your export pipeline, upload them to the Gemini app, and see whether the watermark is detected.
A positive tells you your pipeline preserves the signal. A negative doesn't mean your pipeline is broken — it means your assets currently sit in a state where no third party can attribute them to Google AI. Either result lands you in the same place on what to do next.
Rather than waiting for a detector to supply the answer, the side that already holds the answer writes it down first. Unglamorous, but it's the part that pays off when you open that asset folder months later.
Thanks for reading.