raven-image --margin: pin the BG_TOL boundary, the repaint colour, every decoder refusal; drop two guards no behaviour depends on
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
00794e4eee
commit
7b5d8bab1d
3 changed files with 22 additions and 8 deletions
|
|
@ -58,7 +58,7 @@ the prompt sets what the interior tries to match.
|
|||
After the render the client measures the band against its own backdrop (the per-channel median of the band: the VAE
|
||||
shifts a saturated `--bg` by tens of levels, `#12fa5f` came back `#1fef6d`; cream stayed within 2), repaints the whole
|
||||
band with that colour (if the median is more than 48 levels from `--bg` on any channel the band is mostly content, and
|
||||
`--bg` is the reference instead, so a band of subject cannot pass as its own backdrop), and prints it with the per-side count of pixels that were content:
|
||||
`--bg` is the reference instead, so a band of subject cannot pass as its own backdrop; the guarantee is a flat band within 48 levels of `--bg`, so a band that is uniformly 30 levels off, which the VAE can do to a saturated colour, passes), and prints it with the per-side count of pixels that were content:
|
||||
`margin 6.0%: backdrop #fef9ec, cleared 802 of 245760 band px (0.33%; left 646 top 0 right 156 bottom 0)`.
|
||||
If more than `--max-clip` percent of the band (default 0.5) was content the file is still written, already cleared, and
|
||||
the client exits **65**: the subject ran into the frame, so it is cut flat at the seam. Re-seed or ask for more room.
|
||||
|
|
@ -80,7 +80,7 @@ the subject also contains (green in a green-eyed character) is untouched except
|
|||
a rectangle, so the pin cannot ask for a vignette or a round sticker. A subject cannot be asked to stay off the seam: only
|
||||
the exit code tells you it did not.
|
||||
|
||||
Offline test (fake ComfyUI on 127.0.0.1, 146 checks): `python3 tests/test-raven-image.py`.
|
||||
Offline test (fake ComfyUI on 127.0.0.1, 158 checks): `python3 tests/test-raven-image.py`.
|
||||
|
||||
## What it does
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ def png_decode(data):
|
|||
while pos < len(data):
|
||||
n, typ = struct.unpack(">I4s", data[pos:pos + 8])
|
||||
body = data[pos + 8:pos + 8 + n]
|
||||
if len(body) != n or struct.unpack(">I", data[pos + 8 + n:pos + 12 + n])[0] != zlib.crc32(typ + body):
|
||||
if struct.unpack(">I", data[pos + 8 + n:pos + 12 + n])[0] != zlib.crc32(typ + body):
|
||||
raise ValueError("bad chunk length or CRC")
|
||||
pos += 12 + n
|
||||
if typ == b"IHDR":
|
||||
|
|
@ -160,8 +160,6 @@ def png_decode(data):
|
|||
if hdr is None or not idat:
|
||||
raise ValueError("no IHDR or no image data")
|
||||
w, h, depth, ctype, _, _, interlace = hdr
|
||||
if not 0 < w * h <= 2 ** 26:
|
||||
raise ValueError(f"image size {w}x{h} is out of range")
|
||||
if depth != 8 or ctype not in (2, 6) or interlace:
|
||||
raise ValueError(f"only 8-bit non-interlaced RGB/RGBA is handled (got depth {depth}, colour type {ctype}, interlace {interlace})")
|
||||
bpp = 3 if ctype == 2 else 4
|
||||
|
|
|
|||
|
|
@ -292,16 +292,32 @@ for nm, sc, probe in (("vertical", pocket_v, (80, 90)), ("horizontal", pocket_h,
|
|||
_, _, bpp_, rows_, _ = mod2.png_decode(pic)
|
||||
w2, h2, bpp2, rows2, keep2 = mod2.png_decode(mod2.clear_edges(pic, 6, True, 0x1ec85a)[0])
|
||||
check(rows2[probe[1]][probe[0] * 4 + 3] == 0, f"flood fill: backdrop pocket behind a 1-px {nm} corridor is keyed out")
|
||||
# decoder hardening: a flipped byte breaks the CRC, a huge IHDR is refused before allocation
|
||||
# BG_TOL: a flat band within 48 levels of --bg is the pinned colour after the VAE's shift (observed up to 30), repainted as the band's own colour; 49 is content
|
||||
for off, want_clean in ((48, True), (49, False)):
|
||||
flat = (BG[0] + off, BG[1], BG[2])
|
||||
p_, back4, clip4, band4 = mod2.clear_edges(mkpng(160, 160, lambda x, y, f=flat: f), 6, False, 0x1ec85a)
|
||||
check((sum(clip4.values()) == 0) == want_clean and back4 == (flat if want_clean else BG), f"BG_TOL: flat band {off} levels off --bg {'is' if want_clean else 'is not'} a backdrop ({back4}, {sum(clip4.values())} cleared)")
|
||||
if want_clean:
|
||||
r4 = mod2.png_decode(p_)[3]
|
||||
check(bytes(r4[3][3:6]) == bytes(flat), "BG_TOL: a clean band is repainted with its own colour, so the seam stays invisible")
|
||||
# decoder hardening: a flipped byte breaks the CRC, and every size check refuses on its own (each blob is otherwise well-formed)
|
||||
def ck(t, b): return struct.pack(">I", len(b)) + t + b + struct.pack(">I", zlib.crc32(t + b))
|
||||
def blob(w, h, depth=8, ctype=2, idat=None, il=0):
|
||||
return (b"\x89PNG\r\n\x1a\n" + ck(b"IHDR", struct.pack(">IIBBBBB", w, h, depth, ctype, 0, 0, il))
|
||||
+ ck(b"IDAT", idat if idat is not None else zlib.compress(b"\0" * (h * (w * 3 + 1)) if w * h < 10 ** 7 else b"")) + ck(b"IEND", b""))
|
||||
good = mkpng(8, 8, lambda x, y: BG)
|
||||
bad = bytearray(good); bad[40] ^= 1
|
||||
for blob, what in ((bytes(bad), "flipped byte"), (good[:16] + struct.pack(">II", 60000, 60000) + good[24:], "huge IHDR")):
|
||||
try: mod2.png_decode(blob); check(False, f"png_decode refuses {what}")
|
||||
for data, what in ((bytes(bad), "flipped byte (CRC)"), (blob(2 ** 21, 2 ** 21), "a header that promises far more than the data"), (blob(8, 8, idat=zlib.compress(b"\0" * (8 * 25 - 1))), "image data shorter than the header says"),
|
||||
(blob(8, 8, idat=zlib.compress(b"\0" * (8 * 25 + 500))), "image data longer than the header says"), (blob(8, 8, depth=16), "16-bit"),
|
||||
(blob(8, 8, ctype=3), "palette"), (blob(8, 8, il=1), "interlaced"), (good[:-20], "truncated"),
|
||||
(good[:8] + struct.pack(">I", 10 ** 6) + good[12:], "chunk length past the end")):
|
||||
try: mod2.png_decode(data); check(False, f"png_decode refuses {what}")
|
||||
except (ValueError, zlib.error, struct.error): check(True, "")
|
||||
# size and interior validation, before any request
|
||||
refused(["--size", "foo", "cap"], "--size is WxH", "--size foo")
|
||||
refused(["--size", "1000x1024", "cap"], "multiples of 16", "--size not on the grid")
|
||||
refused(["--margin", "40", "--size", "64x64", "cap"], "leaves no room", "margin eating the interior")
|
||||
refused(["--margin", "5", "--size", "32x512", "cap"], "leaves no room", "margin eating one axis only")
|
||||
# -n: a dirty image is not forgotten by a later one; an unreadable render is reported, not fatal to the batch
|
||||
seen["view"] = mkpng(160, 160, lambda x, y: RED if x < 12 else BG)
|
||||
p, g = run("--margin", "6", "--bg", "1ec85a", "-n", "2", "--size", "160x160", "--no-enhance", "cap")
|
||||
|
|
|
|||
Loading…
Reference in a new issue