merge(thumbs): thumbnails sized for the tile's width at 2x, and a cache that cannot be planted
Operator-approved 2026-09-23 ("fix it, one bigger thumbnail"), after his
report that sindra-nude-final looked "blurry until selected". c2b1454 sizes
thumbnails at 768 wide (the widest desktop tile, doubled for a 2x screen) and
caps them at 4096 tall. A browser test holds the number against the rendered
grid. c19d8c9 folds the heid bug-hunt (4/4 arms, five seat-executed probes):
cache hits must be regular files carrying the source's exact mtime, the cache
dirs never follow a link, the temp file is mkstemp, palette alpha and EXIF
orientation survive, and there is a 64 MP decode budget. 828 passed on the
branch; thumbs.toml 14/14.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# Thumbnails sized for the tile's WIDTH at 2x density, not 512 on the longest
|
||||
# side. The operator on sindra-nude-final, 2026-09-23: "the images look blurry
|
||||
# until they're selected and blown up". Every row is a change
|
||||
# tests/test_thumbs.py or tests/test_thumbs_browser.py claims to forbid.
|
||||
|
||||
unit = "thumbnails sized for the tile"
|
||||
|
||||
[[mutation]]
|
||||
label = "the old rule: bound the longest side at 512 (portraits get 256px of width)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_portrait_keeps_its_full_width"
|
||||
old = '''
|
||||
im.thumbnail((THUMB_WIDTH, THUMB_HEIGHT_MAX))'''
|
||||
new = '''
|
||||
im.thumbnail((512, 512))'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the width bound is below what the desktop tile needs at 2x"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs_browser.py::test_a_thumbnail_covers_its_tile_at_2x_density"
|
||||
old = '''
|
||||
THUMB_WIDTH = 768'''
|
||||
new = '''
|
||||
THUMB_WIDTH = 640'''
|
||||
|
||||
[[mutation]]
|
||||
label = "no height bound: a long screenshot goes through at full height"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_an_extremely_tall_image_is_bounded_by_height_too"
|
||||
old = '''
|
||||
im.thumbnail((THUMB_WIDTH, THUMB_HEIGHT_MAX))'''
|
||||
new = '''
|
||||
im.thumbnail((THUMB_WIDTH, 10 ** 6))'''
|
||||
|
||||
[[mutation]]
|
||||
label = "fitting in pixels is taken as light in bytes (the megabyte portrait is served whole)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_tile_width_image_that_is_heavy_still_gets_a_thumbnail"
|
||||
old = '''
|
||||
if fits and (s_stat.st_size <= THUMB_LIGHT_BYTES or getattr(im, "is_animated", False)):'''
|
||||
new = '''
|
||||
if fits:'''
|
||||
|
||||
[[mutation]]
|
||||
label = "an already small, light image gets a cache entry that saves nothing"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_an_already_small_image_gets_no_thumbnail"
|
||||
old = '''
|
||||
if fits and (s_stat.st_size <= THUMB_LIGHT_BYTES or getattr(im, "is_animated", False)):'''
|
||||
new = '''
|
||||
if fits and getattr(im, "is_animated", False):'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a heavy animated GIF that fits is flattened to one frame"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_an_animated_gif_that_fits_is_served_as_itself"
|
||||
old = '''
|
||||
if fits and (s_stat.st_size <= THUMB_LIGHT_BYTES or getattr(im, "is_animated", False)):'''
|
||||
new = '''
|
||||
if fits and s_stat.st_size <= THUMB_LIGHT_BYTES:'''
|
||||
|
||||
[[mutation]]
|
||||
label = "an unversioned cache name: a thumbnail cut to the old rule is served forever"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_thumbnail_cut_to_the_old_rule_is_not_served"
|
||||
old = '''
|
||||
return booth / THUMB_DIR / f"{rel}.{rule}.webp"'''
|
||||
new = '''
|
||||
return booth / THUMB_DIR / (rel + ".webp")'''
|
||||
|
||||
# ---- the heid bug-hunt on this change (4/4 arms), folded ------------------------
|
||||
|
||||
[[mutation]]
|
||||
label = "a cache hit trusts the name and the mtime (a planted directory is served)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_planted_directory_at_the_cache_path_is_not_served"
|
||||
old = '''
|
||||
return stat.S_ISREG(o.st_mode) and o.st_mtime_ns == s_stat.st_mtime_ns'''
|
||||
new = '''
|
||||
return o.st_mtime_ns >= s_stat.st_mtime_ns'''
|
||||
|
||||
[[mutation]]
|
||||
label = "freshness is 'at least as new' (a cp -p'd older source pins the old thumbnail)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_source_replaced_with_an_older_mtime_is_rebuilt"
|
||||
old = '''
|
||||
return stat.S_ISREG(o.st_mode) and o.st_mtime_ns == s_stat.st_mtime_ns'''
|
||||
new = '''
|
||||
return stat.S_ISREG(o.st_mode) and o.st_mtime_ns >= s_stat.st_mtime_ns'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the cache dirs are made by following links (a planted .thumbs link escapes the booth)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_symlinked_cache_dir_is_never_written_through"
|
||||
old = '''
|
||||
if not _cache_dir(booth, out.parent):
|
||||
return None'''
|
||||
new = '''
|
||||
out.parent.mkdir(parents=True, exist_ok=True)'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a predictable temp name the encoder writes through"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_planted_link_at_the_old_temp_name_cannot_redirect_the_write"
|
||||
old = '''
|
||||
fd, tmp = tempfile.mkstemp(prefix=".", suffix=".tmp", dir=out.parent)
|
||||
try:
|
||||
with os.fdopen(fd, "wb") as fh:
|
||||
im.save(fh, "WEBP", quality=THUMB_QUALITY, method=4)'''
|
||||
new = '''
|
||||
tmp = str(out) + f".{os.getpid()}.tmp"
|
||||
try:
|
||||
im.save(tmp, "WEBP", quality=THUMB_QUALITY, method=4)'''
|
||||
|
||||
[[mutation]]
|
||||
label = "RGBA chosen by getbands() alone (palette transparency baked opaque)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_palette_transparency_survives_the_thumbnail"
|
||||
old = '''
|
||||
alpha = "A" in im.getbands() or "transparency" in im.info'''
|
||||
new = '''
|
||||
alpha = "A" in im.getbands()'''
|
||||
|
||||
[[mutation]]
|
||||
label = "EXIF orientation ignored (a camera portrait tiled sideways)"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_a_camera_portrait_is_sized_and_saved_upright"
|
||||
old = '''
|
||||
orientation = im.getexif().get(0x0112, 1)'''
|
||||
new = '''
|
||||
orientation = 1'''
|
||||
|
||||
[[mutation]]
|
||||
label = "no pixel budget: whatever the header claims is decoded"
|
||||
file = "booth/thumbs.py"
|
||||
test = "tests/test_thumbs.py::test_an_image_past_the_pixel_budget_is_never_decoded"
|
||||
old = '''
|
||||
if w * h > THUMB_MAX_PIXELS:
|
||||
return None'''
|
||||
new = '''
|
||||
if False:
|
||||
return None'''
|
||||
Reference in New Issue
Block a user