Show the generator what to copy, not what to replace
The reference sheet is what it imitates, so building that sheet out of the very sprites I was trying to change is why the front-facing player came back front-facing and the side-on runner came back side-on. A batch can now name `ref_only` — the frames it looks at — separately from the frames it replaces: the back view is drawn from the one genuine back-view frame, the charging runner from a front-facing zombie, and each brute wound pair from its own A frame so the injury cannot wander between the two. Also stopped colour-matching a REPOSE. Fitting a differently-posed frame onto the old one's colour statistics is what greyed out the serve shorts; when the pose changes the generator's own palette is the better answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
01498eddad
commit
67a410e707
4 files changed
+96
-16
No files matched your search
+19
-6
@@ -61,10 +61,18 @@ def density(path):
|
||||
return round(float(((d > 40) & solid[:, :-1]).sum() / max(1, solid.sum())), 3)
|
||||
|
||||
def touches_edge(sheet):
|
||||
"""A CLIPPED figure, not a speck of noise: a run of 8+ ink pixels along a border. The strict
|
||||
version rejected sheets over single stray pixels and cost several regenerations."""
|
||||
a = np.array(Image.open(sheet).convert('RGB')).astype(int)
|
||||
bg = a[0, 0]
|
||||
fg = (((a - bg) ** 2).sum(2) > 3600) & ~(np.minimum(a[..., 0], a[..., 2]) > a[..., 1] + 40)
|
||||
return bool(fg[0].any() or fg[-1].any() or fg[:, 0].any() or fg[:, -1].any())
|
||||
def run(line):
|
||||
n = best = 0
|
||||
for v in line:
|
||||
n = n + 1 if v else 0
|
||||
best = max(best, n)
|
||||
return best
|
||||
return max(run(fg[0]), run(fg[-1]), run(fg[:, 0]), run(fg[:, -1])) >= 8
|
||||
|
||||
def colour_match(src, dst_name):
|
||||
"""Fit the new frame onto the palette statistics of the sprite it replaces, so a redrawn body
|
||||
@@ -83,7 +91,9 @@ def run(name, spec, dry=False):
|
||||
os.makedirs(SCRATCH, exist_ok=True)
|
||||
sprites = spec['sprites']
|
||||
lead = spec.get('ref') # generated for scale, thrown away after
|
||||
ref = ref_sheet(([lead] if lead else []) + sprites, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0))
|
||||
shown = spec.get('ref_only') or (([lead] if lead else []) + sprites)
|
||||
if spec.get('ref_only'): lead = None # ref_only sheets return exactly the frames asked for
|
||||
ref = ref_sheet(shown, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0))
|
||||
sheet = f'{SCRATCH}/{name}_hi.png'
|
||||
prompt = f"{spec['subject']} {STYLE}"
|
||||
if dry:
|
||||
@@ -106,12 +116,15 @@ def run(name, spec, dry=False):
|
||||
if lead: outs = outs[1:] # the scale reference is not installed
|
||||
before = [density(os.path.join(HERE, s + '.png')) for s in sprites]
|
||||
after = [density(o) for o in outs]
|
||||
# 1.25x, not 1.5x: a sheet that already has blood on it starts with a high edge density, so a
|
||||
# genuinely finer redraw of a wounded body scores a smaller gain than a clean one.
|
||||
if sum(after) / len(after) < sum(before) / len(before) * 1.25:
|
||||
# Two jobs, two bars. A coarse->fine REDRAW has to be measurably finer (1.25x, not 1.5x: a sheet
|
||||
# that already has blood on it starts high, so a genuinely finer wounded body gains less). A
|
||||
# REPOSE — turning the player around, pointing the runner at the camera, matching a wound between
|
||||
# two frames — is already fine art and only has to come back no worse.
|
||||
bar = 1.25 if spec.get('check', 'finer') == 'finer' else 0.85
|
||||
if sum(after) / len(after) < sum(before) / len(before) * bar:
|
||||
print(f'[{name}] REJECTED: not finer than what it replaces {before} -> {after}'); return False
|
||||
for o, s in zip(outs, sprites):
|
||||
colour_match(o, s)
|
||||
if spec.get('check', 'finer') == 'finer': colour_match(o, s)
|
||||
Image.open(o).save(os.path.join(HERE, s + '.png'))
|
||||
print(f'[{name}] installed. detail {before} -> {after}')
|
||||
return True
|
||||
|
||||
Reference in new issue
Block a user