Add native resolution width/height specifier

This commit is contained in:
Mark Qvist
2026-09-05 23:13:30 +02:00
parent 2250f2a71c
commit 815ff99e12
2 changed files with 47 additions and 6 deletions
+38 -1
View File
@@ -255,7 +255,7 @@ def test_webp_conversion():
with os.fdopen(fd, "wb") as f:
f.write(fixture)
d = ImageData(webp_path)
check(not d.ok and "converted image too large" in d.error,
check(not d.ok and "Converted image too large" in d.error,
"converted payload capped at %d MiB"
% (MAX_CONVERTED_PAYLOAD_BYTES // (1024 * 1024)))
os.unlink(webp_path)
@@ -414,6 +414,43 @@ def test_widget_layout(image_path):
check((c, r) == (27, 10), "height-only after reset -> (27, 10), got (%d, %d)" % (c, r))
w.close()
# native resolution specification ("n"): display at the image's own
# pixel size, quantized to the terminal cell grid. Cell = (1, 2),
# small fixture 64x32 -> native 64 cols, 16 rows.
from nomadnet.ui.textui.images import _webp
small_png = _webp._encode_png_rgba(bytes(64 * 32 * 4), 64, 32)
fd, small_path = tempfile.mkstemp(suffix=".png")
with os.fdopen(fd, "wb") as f:
f.write(small_png)
try:
wn = ImageWidget(small_path, width="n")
wn.render((80,)) # sets _ti_cols/_ti_rows
check((wn._ti_cols, wn._ti_rows) == (64, 16),
"width='n' displays native size (64, 16), got (%d, %d)"
% (wn._ti_cols, wn._ti_rows))
wn.close()
hn = ImageWidget(small_path, height="n")
hn.render((80,))
check((hn._ti_cols, hn._ti_rows) == (64, 16),
"height='n' displays native size (64, 16), got (%d, %d)"
% (hn._ti_cols, hn._ti_rows))
hn.close()
bn = ImageWidget(small_path, width="n", height="n")
bn.render((80,))
check((bn._ti_cols, bn._ti_rows) == (64, 16),
"width='n' height='n' displays native size (64, 16), got (%d, %d)"
% (bn._ti_cols, bn._ti_rows))
bn.close()
big = ImageWidget(image_path, width="n") # 1280x960, overflows
c, r = big._display_size(80)
check((c, r) == (80, 30),
"native of a large image clamps to available width (80, 30), got (%d, %d)" % (c, r))
big.close()
check(raises(lambda: ImageWidget(image_path, width="x"), ValueError),
"width='x' raises ValueError")
finally:
os.unlink(small_path)
# alignment: placement column of a narrowed image inside its canvas
w = ImageWidget(image_path, width=40) # default center
w.render((80,)) # sets _ti_cols/_ti_rows
+9 -5
View File
@@ -33,6 +33,7 @@ def _check_align(value):
def _check_size_spec(value, name):
# None = auto, int = absolute cells/rows, "NN%" = percent, "n" = native resolution
if value is None: return None
if isinstance(value, int):
@@ -40,9 +41,10 @@ def _check_size_spec(value, name):
return value
if isinstance(value, str):
if value.lower() == "n": return "n"
match = _SIZE_SPEC_RE.fullmatch(value)
if not match or float(match.group(1)) <= 0:
raise ValueError("invalid %s specification %r (expected a positive integer or a percent string like '50%%')" % (name, value))
raise ValueError("invalid %s specification %r (expected a positive integer, a percent string like '50%%' or 'n' for native resolution)" % (name, value))
return value
raise TypeError("invalid type for %s (got %s)" % (name, type(value).__name__))
@@ -140,8 +142,8 @@ class ImageWidget(urwid.Widget):
width = property(
lambda self: self._ti_width,
doc="""Display width specification: None (full width), a positive
int (columns) or a percent string ("NN%") of the available layout
width.""",
int (columns), a percent string ("NN%") of the available layout
width, or "n" for native resolution.""",
)
@width.setter
@@ -152,8 +154,8 @@ class ImageWidget(urwid.Widget):
height = property(
lambda self: self._ti_height,
doc="""Display height specification: None (derived, aspect
preserving), a positive int (rows) or a percent string ("NN%") of
the visible page height.""",
preserving), a positive int (rows), a percent string ("NN%") of
the visible page height, or "n" for native resolution.""",
)
@height.setter
@@ -185,11 +187,13 @@ class ImageWidget(urwid.Widget):
def _raw_width():
if w_spec is None: return float(maxcol)
if isinstance(w_spec, int): return float(w_spec)
if w_spec == "n": return iw / cell_w
return maxcol * _pct(w_spec)
def _raw_height():
if h_spec is None: return None
if isinstance(h_spec, int): return float(h_spec)
if h_spec == "n": return ih / cell_h
columns, lines = _termlib.get_terminal_size()
page = max(1, lines - UI_MARGIN)
return page * _pct(h_spec)