Restructure documentation, validate metadata removal, consolidate assets

This commit is contained in:
Victor Kuznetsov
2026-07-25 21:08:04 -07:00
parent 214c9bb3e7
commit 03cd00f132
172 changed files with 2356 additions and 2863 deletions
+85
View File
@@ -0,0 +1,85 @@
# Doubao visible watermark capture
> **Status (captured 2026-05-29; asset rebuilt 2026-05-31):** the
> black and gray captures are committed here. The generated alpha
> asset is now a detection silhouette and mask-shaping input only. Current
> removal localizes the mark and sends its footprint to the shared fill backend.
> Reverse-alpha recovery was retired. The details below preserve the historical
> capture rationale.
Goal: capture the Doubao "豆包AI生成" visible watermark over known flat backgrounds so we can
rebuild and validate the detection silhouette used by
`src/remove_ai_watermarks/doubao_engine.py`.
## Historical findings from the capture work
- Blend model: **alpha compositing with a white logo** `watermarked = a*logo + (1-a)*original`,
`logo = (255,255,255)`. Inversion: `original = (watermarked - a*logo) / (1-a)`.
Confirmed by two independent sources (an open-source remover's algorithm doc + aiwatermarkremover.dev,
both say "alpha map"). One commercial blog (pixelcleanai) claims "screen blend" instead; the gray
capture below settles it empirically.
- Position: **bottom-right corner**, small margins (right ~8-20px, bottom ~5px), scales with image size.
Confirmed by our sample `data/fixtures/provenance/doubao-1.png` (2048x2048) plus three sources.
- Size **scales with resolution**. Third-party numbers (~90x18 at <=1024, ~180x40 at >1024) are
approximate and calibrated for ~1024-1280 outputs; at 2048 the strip is much larger. A shipped
third-party alpha map is only 120x20, too small for our 2K/4K target -> capture fresh.
- The original implementation used reverse-alpha recovery. That path was later
removed in favor of localize then fill because the latter also handles moved
and re-rendered marks.
## Use doubao.com specifically
The "豆包AI生成" mark is Doubao's. Jimeng / Dreamina use a different mark. Generate on doubao.com so
the captured template matches our target.
## How to capture (image-edit path, most reliable)
For each locally generated solid-color seed:
1. Open Doubao image generation, use the image-edit / reference mode, upload the seed.
2. Prompt (Chinese preferred):
`请完全按照原图重新生成这张图片,保持完全一致,不要添加或修改任何内容`
(English: `Recreate this image exactly as it is, keep it identical, do not add or change anything`)
3. Download the ORIGINAL output file (not a screenshot). Do not crop / edit / re-save.
Prior art confirms uploading a pure-black image and letting Doubao stamp it works.
If edit mode is unavailable and text-to-image refuses a solid color, fall back to generating 10-12
normal-content images at one fixed resolution; the mark is the only constant across them and can be
extracted by per-pixel min/median.
## What to capture (priority top to bottom)
| Aspect | black | white | gray128 | why |
|--------|-------|-------|---------|-----|
| 1:1 | 3 | 1 | 1 | primary alpha map + confirm the stamp is pixel-identical across runs + settle blend mode |
| 16:9 | 2 | 1 | 1 | anchor rule in landscape |
| 9:16 | 2 | 1 | 1 | anchor rule in portrait |
| 4:3, 3:4 | 1 each | - | - | optional, refines anchor rule |
- 3 blacks on 1:1: if the first two are byte-identical in the watermark region, the third is optional.
- gray128 is the blend-mode test: predict the gray result from the black capture under alpha vs screen;
whichever matches the real gray output is the true blend.
- If the UI offers multiple output resolutions (1K / 2K / 4K), capture one black per resolution on 1:1 -
needed to learn how the watermark scales.
- Also grab 3-5 normal-content images on 1:1 for end-to-end removal validation.
## Hygiene
- Original download, never a screenshot. PNG preferred; if Doubao only gives JPEG, note it.
- No crop / edit / re-save. Default settings, watermark left ON.
## Committed inputs
```
doubao_black_1x1_1.png
doubao_gray_1x1_1.png
```
The black and gray captures feed `scripts/visible_alpha_solve.py doubao`.
## Also report back
1. Which resolutions and aspect ratios the Doubao UI actually offers.
2. Whether there is a watermark on/off toggle in the UI.
3. Download format (PNG or JPEG).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 KiB

+46
View File
@@ -0,0 +1,46 @@
# Gemini (Nano Banana) visible sparkle capture
> **Status (captured 2026-05-31):** the required black capture is committed
> here. The sparkle-on-black assets
> `gemini_bg_{96,48}.png` are rebuilt by `scripts/visible_alpha_solve.py gemini`.
> They support detection and footprint geometry. Current pixel recovery uses the
> shared fill backend, not reverse-alpha inversion.
Google Gemini (Nano Banana) stamps a four-point sparkle icon in the bottom-right corner
via alpha compositing: `watermarked = a*logo + (1-a)*original`. Unlike the Doubao/Jimeng
text marks, the sparkle is captured over a **pure-black** background, where
`watermarked = a*255` (logo is near-white), so the alpha reads directly off the capture
(`alpha = max(R,G,B)/255`) -- no background fit needed. This is the "golden" capture case.
## What the captures confirmed (2026-05-31)
- The sparkle at a 2048-wide image is **96x96 px** (width_frac ~0.047), bottom-right,
margins ~0.031, alpha max ~0.51 -- matching the engine's existing 96px asset.
- Our own controlled capture matches the previously third-party-sourced
`gemini_bg_96.png` to **NCC 0.9998**, so the bundled asset is validated and now
reproducible from our own capture.
## How to capture (image-edit path)
For each locally generated black, gray, or white 2048 px seed:
1. Open Gemini image generation, image-edit / reference mode, upload the seed.
2. Prompt: `Recreate this image exactly as it is, keep it identical, do not add or change anything`
3. Download the ORIGINAL output (not a screenshot). Do not crop / edit / re-save.
Black is the key one (sparkle on black -> exact alpha). Gray/white cross-check.
## Committed input
```
gemini_black_2048.png # the key capture (sparkle on black)
```
The gray and white captures were calibration cross-checks and are not needed to
rebuild the assets. Synthetic seeds and real-content validation downloads stay
local.
Rebuild the assets with:
```
uv run python scripts/visible_alpha_solve.py gemini # or: all
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

+68
View File
@@ -0,0 +1,68 @@
# Jimeng (即梦AI) visible watermark capture
> **Status (completed 2026-05-30):** solid black and gray Jimeng captures were
> obtained and the detection asset was solved. The asset now supports detection
> and mask geometry only. Current removal localizes the wordmark and uses the
> shared fill backend. The text below records the capture and calibration work.
Goal: capture the Jimeng / Dreamina "★ 即梦AI" visible wordmark over known flat
backgrounds so we can rebuild and validate the silhouette used by
`src/remove_ai_watermarks/jimeng_engine.py`.
## What we learned (verified from the captures, 2026-05-30)
- Mark: a four-point sparkle icon followed by the "即梦AI" characters, near-white
semi-transparent overlay, bottom-right corner.
- Blend model: **alpha compositing with a pure-white logo** `watermarked =
a*255 + (1-a)*original`, confirmed in sRGB (a linear-light solve made the
black/gray cross-residual much worse, so the compositing is plain sRGB). An
L-pair-solve (independent of the L assumption) lands at ~254.6, confirming white.
- **Alpha is solved from the GRAY capture**, not black: `a = (I - B)/(255 - B)`
with B a per-capture CUBIC background fit over the non-glyph pixels, averaged
over channels, at FULL halo extent (down to a~0.02) and UNBLURRED. Gray (bg ~132)
is the best proxy for real content (the mark sits on bright photo areas, not on
black). This careful build drops the gray self-residual to ~1.3; an earlier
max-channel / quadratic-bg / blurred / halo-truncated build (and a black-dominated
least-squares solve) left a visible outline -- the mask quality, not the method,
was the limit.
- Geometry (fraction of image WIDTH, at the captured 2048): asset width ~0.211,
height ~0.068, right margin ~0.023, bottom margin ~0.023. The mark scales with
width; a real 1440-wide download matched width_frac ~0.21.
- **Per-image render variation:** the alpha maps solved independently from the
black and the gray capture correlate 0.998 but not 1.0 (mean |Δa| ~0.02). Jimeng
re-rasterizes the mark per generation and jitters its position a few pixels.
This was one reason the old reverse-alpha path needed alignment and residual
inpainting. The current path instead detects the silhouette, localizes the
glyph footprint, and fills that footprint.
## How to capture (image-edit path, most reliable)
For each solid-color seed:
1. Open Jimeng image generation, use the image-edit / reference mode, upload the seed.
2. Prompt (Chinese preferred):
`请完全按照原图重新生成这张图片,保持完全一致,不要添加或修改任何内容`
3. Download the ORIGINAL output file (not a screenshot). Do not crop / edit / re-save.
The black capture is the key one (white logo on black -> `captured ~= a*255`); the
gray capture refines the alpha at mid-tones; the white capture confirms the logo is
pure white (the mark is nearly invisible on white, as expected).
## Hygiene
- Original download, never a screenshot. PNG preferred; if Jimeng only gives JPEG, note it.
- No crop / edit / re-save. Default settings, watermark left ON.
## Committed inputs
```
jimeng_cap_A.png # black seed run through Jimeng
jimeng_cap_C.png # gray seed
```
The A and C captures feed
`scripts/visible_alpha_solve.py jimeng`. Rebuild the detection asset with:
```
uv run python scripts/visible_alpha_solve.py jimeng # or: all
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+61
View File
@@ -0,0 +1,61 @@
# Samsung Galaxy AI visible watermark capture
> **Status (built 2026-06-05):** flat black and gray Samsung Galaxy AI captures
> were obtained and the detection asset was solved. The asset now supports
> detection and mask geometry only. Current removal localizes the wordmark and
> uses the shared fill backend. The text below records the capture plan and its
> locale and resolution limits.
Goal: capture the Samsung Galaxy AI "✦ Contenuti generati dall'AI" visible wordmark
over known flat backgrounds so we can rebuild and validate the silhouette used
by `src/remove_ai_watermarks/samsung_engine.py`.
## What we learned (verified from the captures, 2026-06-05)
- Mark: a sparkle icon followed by the locale string "Contenuti generati dall'AI"
(Italian), a light low-opacity (peak alpha ~0.38) semi-transparent **white**
overlay, anchored **bottom-LEFT** (Doubao/Jimeng are bottom-right). The string is
locale-specific, so the alpha template is per-locale; this build is the Italian
variant. Other locales need their own captured template.
- Blend model: alpha compositing with a pure-white logo, `watermarked =
a*255 + (1-a)*original`, solved from the GRAY capture (same careful recipe as
Doubao/Jimeng: cubic-background fit, mean over channels, full halo extent,
unblurred). The white capture confirms the logo is white; on white the mark is
white-on-white and not detectable (no contrast), which is fine -- there is nothing
to recover there.
- Geometry (fraction of image WIDTH): asset width ~0.32, height ~0.038, left margin
~0.011, bottom margin ~0.006. The mark scales with width: a 1086-wide flat capture
and a 2958-wide real photo both measure width_frac ~0.31.
- **Resolution caveat (open quality follow-up):** the flat black/gray/white captures
arrived at the phone's flat-edit size (1086 wide and a landscape 1920 set), while
the real photos are ~3000 wide, so the captured glyph (~334 px) is ~2.7x smaller
than on a real photo (~900 px). The alpha is solved at the capture size and
width-scaled + NCC-aligned per image, which removes the mark cleanly (verified on a
real 2958-wide photo: re-detect 0.79 -> 0.00, no readable text or outline), but a
flat capture taken at the real photo resolution (~3000 wide) would let the alpha be
pixel-sharp instead of upscaled. Not a blocker; a quality upgrade if a full-res
flat capture is provided.
## Capture protocol (to re-capture or add a locale)
On a Samsung Galaxy AI device (set the UI language to the target locale):
1. Run the AI edit (Generative Edit / Sketch to Image) on a solid black image, so
the overlay lands on a flat black background. Download the ORIGINAL output file
(not a screenshot, no crop or re-save).
2. Repeat over solid white and solid gray (those pin the exact glyph color).
3. Ideally run all three flat edits at the same resolution as real photos (~3000
wide) so the alpha map is pixel-sharp rather than upscaled.
4. Plus 3-5 real outputs with the visible mark over normal content for validation.
## Files
- `samsung_black_1.png` and `samsung_gray_1.png` are the retained solver inputs.
- White, secondary-resolution, and real-content captures were validation
material and are not required to rebuild the asset.
Rebuild the detection asset with:
```
uv run python scripts/visible_alpha_solve.py samsung
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 KiB