91 lines
4.3 KiB
Python
91 lines
4.3 KiB
Python
"""Convert the submenu master into the theme's 9-slice parchment.
|
|||
|
|
|
||
|
|
The tile is the master, whole: 1200x450, converted to WebP and otherwise
|
||
|
|
untouched. That sounds like doing nothing, and the reason it is right is worth
|
||
|
|
recording, because two earlier versions of this file did a great deal.
|
||
|
|
|
||
|
|
A 9-slice needs only a 36px border and a small middle, so the obvious cut is
|
||
|
|
328x328 — one tiny file for any panel at any height. But cutting it that narrow
|
||
|
|
means manufacturing seven junctions that do not exist in the artwork, and the
|
||
|
|
master's drop shadow is *directional*: at the same distance from the border the
|
||
|
|
four edges differ by 20-35 luminance levels (left 192 against top 157 at k=12),
|
||
|
|
converging only as they reach the interior. Reused edges and pasted corners
|
||
|
|
therefore cannot agree, and the corners showed it:
|
||
|
|
|
||
|
|
top-left | left slab 41.9 <- worst
|
||
|
|
bottom-left | bottom slab 27.7
|
||
|
|
bottom-left | left slab 24.3
|
||
|
|
top-right | top slab 15.1
|
||
|
|
bottom-right| bottom slab 1.9
|
||
|
|
|
||
|
|
Twenty times worse at one corner than another. Levelling does not rescue it:
|
||
|
|
correcting every rim piece onto one shared cross-edge profile still left the
|
||
|
|
worst junction at 15, and flattened the shadow's direction to get there. Worse,
|
||
|
|
each correction fights the others — wrapping a slab so it tiles moves its ends
|
||
|
|
away from the corners above and below it, which is the same defect again.
|
||
|
|
|
||
|
|
Keeping the master whole removes the problem instead of correcting it. Every
|
||
|
|
piece is contiguous with its neighbours because they were never cut apart, so
|
||
|
|
there is nothing to level, nothing to wrap, and nothing to flatten.
|
||
|
|
|
||
|
|
What this costs, and why it is still the right trade:
|
||
|
|
|
||
|
|
* `border-image-repeat: round` scales a slab so a whole number fits. The
|
||
|
|
middle is 1128x378, and a panel up to about 460px tall needs exactly one
|
||
|
|
repeat — no join anywhere. The demo's panels are 280-460px.
|
||
|
|
* Above roughly 640px the vertical count rounds to 2 and a join appears in
|
||
|
|
the left and right borders. That happens on a phone, where the panels stack
|
||
|
|
and run long. The join is untreated; the alternative was a treated join at
|
||
|
|
every corner of every panel, which is the defect this replaces.
|
||
|
|
* Horizontally, a 1200px panel needs 0.99 repeats: no join at the site width.
|
||
|
|
On a phone the 1128px edge art is squeezed to about 358px, so the tear
|
||
|
|
reads finer. That is a scale change, not a seam.
|
||
|
|
|
||
|
|
WebP at quality 90 rather than lossless: the artwork is a soft shadow over a
|
||
|
|
grain of std 2, which the encoder carries at a third of the size.
|
||
|
|
"""
|
||
|
|
import numpy as np
|
||
|
|
from PIL import Image
|
||
|
|
|
||
|
|
SRC = '/home/jasa/PycharmProjects/wp-migrate/site/wp-content/uploads/2019/10/papyrus_submenu_450_cnd.png'
|
||
|
|
OUT = '/home/jasa/PycharmProjects/tlig_css/src/images/parchment.webp'
|
||
|
|
|
||
|
|
# Must match `border-image-slice` in abstracts/_mixins.scss.
|
||
|
|
SLICE = 36
|
||
|
|
QUALITY = 90
|
||
|
|
|
||
|
|
im = Image.open(SRC).convert('RGBA')
|
||
|
|
W, H = im.size
|
||
|
|
im.save(OUT, quality=QUALITY, method=6)
|
||
|
|
|
||
|
|
import os
|
||
|
|
print('wrote %s %dx%d %.1f KB' % (OUT, W, H, os.path.getsize(OUT) / 1024))
|
||
|
|
|
||
|
|
# Diagnostics. Nothing here is corrected — these are the numbers that would
|
||
|
|
# tell us the master had changed, or that a panel size had moved outside the
|
||
|
|
# range where the middle needs a single repeat.
|
||
|
|
t = np.asarray(Image.open(OUT).convert('RGBA')).astype(np.float32)
|
||
|
|
|
||
|
|
|
||
|
|
def level(band):
|
||
|
|
"""Alpha-weighted mean luminance, so the transparent rim does not count."""
|
||
|
|
w = band[..., 3] / 255.0
|
||
|
|
return float((band[..., :3].mean(axis=2) * w).sum() / (w.sum() + 1e-6))
|
||
|
|
|
||
|
|
|
||
|
|
print(' every piece is contiguous; junction steps are the master\'s own:')
|
||
|
|
for name, a, b in (
|
||
|
|
('top-left | left slab', t[SLICE-4:SLICE, 4:SLICE-4], t[SLICE:SLICE+4, 4:SLICE-4]),
|
||
|
|
('bottom-left | left slab', t[H-SLICE-4:H-SLICE, 4:SLICE-4], t[H-SLICE:H-SLICE+4, 4:SLICE-4]),
|
||
|
|
('top-right | right slab', t[SLICE-4:SLICE, W-SLICE+4:W-4], t[SLICE:SLICE+4, W-SLICE+4:W-4]),
|
||
|
|
('bottom-right | right slab', t[H-SLICE-4:H-SLICE, W-SLICE+4:W-4], t[H-SLICE:H-SLICE+4, W-SLICE+4:W-4]),
|
||
|
|
):
|
||
|
|
print(' %-30s %5.1f' % (name, abs(level(a) - level(b))))
|
||
|
|
|
||
|
|
mid_h, mid_w = H - 2 * SLICE, W - 2 * SLICE
|
||
|
|
print(' middle slab %dx%d — repeats needed by `round`:' % (mid_w, mid_h))
|
||
|
|
for h in (280, 380, 460, 640, 900, 1600):
|
||
|
|
n = max(1, round((h - 2 * SLICE) / mid_h))
|
||
|
|
print(' panel %4dpx tall -> %d repeat%s%s'
|
||
|
|
% (h, n, '' if n == 1 else 's', '' if n == 1 else ' (a join appears)'))
|