add REAL_VIEWFINDER setting to size and center the live preview
Sizes the curses viewfinder box to match the print output's character grid (A4/roll-paper target dims) instead of always filling the terminal, and always centers the box. Defaults to true.master
parent
8ecb5cd558
commit
6528f64594
|
|
@ -18,6 +18,14 @@ NEGATIVE=false
|
|||
FLIP_X=false
|
||||
FLIP_Y=false
|
||||
|
||||
# Size the live-preview viewfinder box to the same character grid as the
|
||||
# final print output (see PRINTER_A4/PRINTER_MAX_LINES/PRINTER_MAX_COLS in
|
||||
# printer.conf) instead of just filling the terminal. The preview frame is
|
||||
# still cropped/converted on its own, upright, before any print-time
|
||||
# rotation - only the box's cols/rows match the output. The viewfinder is
|
||||
# always centered in the terminal either way. (true/false)
|
||||
REAL_VIEWFINDER=true
|
||||
|
||||
# Color and braille output are intentionally not exposed here: both rely on
|
||||
# ANSI/multi-byte Unicode that a parallel-port needle printer can't render.
|
||||
# Only add --color/--braille yourself if you don't plan to print via
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class Config:
|
|||
self.negative = as_bool(ascii_.get("NEGATIVE"))
|
||||
self.flip_x = as_bool(ascii_.get("FLIP_X"))
|
||||
self.flip_y = as_bool(ascii_.get("FLIP_Y"))
|
||||
self.real_viewfinder = as_bool(ascii_.get("REAL_VIEWFINDER"))
|
||||
|
||||
self.printer_a4 = as_bool(printer.get("PRINTER_A4"))
|
||||
self.printer_max_lines = int(printer.get("PRINTER_MAX_LINES", "64") or 64)
|
||||
|
|
@ -177,8 +178,28 @@ def capture_label(cfg):
|
|||
return "space" if cfg.key_capture == " " else cfg.key_capture
|
||||
|
||||
|
||||
def box_dims_for(term_rows, term_cols):
|
||||
return max(term_rows - 2, 3), max(term_cols, 10)
|
||||
def output_target_dims(cfg, inner_cols, inner_rows):
|
||||
"""cols/rows the print/output pipeline sizes a capture to (see prepare_print_frame)."""
|
||||
if cfg.printer_max_lines == 0:
|
||||
return cfg.printer_max_cols, None
|
||||
if cfg.printer_a4:
|
||||
return cfg.printer_max_cols, cfg.printer_max_lines
|
||||
return inner_cols, inner_rows
|
||||
|
||||
|
||||
def box_dims_for(term_rows, term_cols, cfg=None):
|
||||
max_rows, max_cols = max(term_rows - 2, 3), max(term_cols, 10)
|
||||
if cfg is None or not cfg.real_viewfinder:
|
||||
return max_rows, max_cols
|
||||
target_cols, target_rows = output_target_dims(cfg, max_cols - 2, max_rows - 2)
|
||||
rows = max_rows if target_rows is None else max(min(target_rows + 2, max_rows), 3)
|
||||
cols = max(min(target_cols + 2, max_cols), 10)
|
||||
return rows, cols
|
||||
|
||||
|
||||
def box_origin(term_rows, term_cols, box_rows, box_cols):
|
||||
"""Top-left corner that centers a box_rows x box_cols window in the terminal."""
|
||||
return max((term_rows - box_rows) // 2, 0), max((term_cols - box_cols) // 2, 0)
|
||||
|
||||
|
||||
_CHAR_ASPECT = 0.5 # width:height ratio of a typical monospace glyph cell
|
||||
|
|
@ -210,23 +231,21 @@ def prepare_print_frame(frame, cfg, inner_cols, inner_rows):
|
|||
first and mangling already-rendered ascii text, which doesn't account
|
||||
for non-square character cells and reads as visibly distorted.
|
||||
"""
|
||||
if cfg.printer_max_lines == 0:
|
||||
cols, rows = output_target_dims(cfg, inner_cols, inner_rows)
|
||||
if rows is None:
|
||||
# Roll printer: paper feeds continuously, so there's no page height
|
||||
# to crop to - only the carriage width (PRINTER_MAX_COLS) limits the
|
||||
# print. Leave the frame uncropped and pass rows=None downstream so
|
||||
# ascii-image-converter derives height from the image's own aspect
|
||||
# ratio instead of forcing a row count.
|
||||
cols, rows = cfg.printer_max_cols, None
|
||||
if cfg.printer_a4:
|
||||
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
|
||||
elif cfg.printer_a4:
|
||||
cols, rows = cfg.printer_max_cols, cfg.printer_max_lines
|
||||
# Cropping happens before the 90-degree rotation below, which swaps
|
||||
# width and height, so crop to the inverse of the final aspect ratio.
|
||||
frame = crop_to_aspect(frame, 1 / visual_aspect(cols, rows))
|
||||
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
|
||||
else:
|
||||
cols, rows = inner_cols, inner_rows
|
||||
frame = crop_to_aspect(frame, visual_aspect(cols, rows))
|
||||
return frame, cols, rows
|
||||
|
||||
|
|
@ -253,8 +272,9 @@ def main(stdscr):
|
|||
frame_times = []
|
||||
message, message_until = "", 0.0
|
||||
rows, cols = stdscr.getmaxyx()
|
||||
box_dims = box_dims_for(rows, cols)
|
||||
box_win = curses.newwin(box_dims[0], box_dims[1], 0, 0)
|
||||
box_dims = box_dims_for(rows, cols, cfg)
|
||||
box_pos = box_origin(rows, cols, *box_dims)
|
||||
box_win = curses.newwin(*box_dims, *box_pos)
|
||||
|
||||
try:
|
||||
while True:
|
||||
|
|
@ -268,12 +288,14 @@ def main(stdscr):
|
|||
fps = len(frame_times)
|
||||
|
||||
rows, cols = stdscr.getmaxyx()
|
||||
box_rows, box_cols = box_dims_for(rows, cols)
|
||||
box_rows, box_cols = box_dims_for(rows, cols, cfg)
|
||||
inner_rows, inner_cols = max(box_rows - 2, 1), max(box_cols - 2, 1)
|
||||
box_pos_now = box_origin(rows, cols, box_rows, box_cols)
|
||||
|
||||
if (box_rows, box_cols) != box_dims:
|
||||
box_win = curses.newwin(box_rows, box_cols, 0, 0)
|
||||
if (box_rows, box_cols) != box_dims or box_pos_now != box_pos:
|
||||
box_win = curses.newwin(box_rows, box_cols, *box_pos_now)
|
||||
box_dims = (box_rows, box_cols)
|
||||
box_pos = box_pos_now
|
||||
stdscr.erase()
|
||||
|
||||
# Live preview: always upright, cropped to roughly the box's own
|
||||
|
|
@ -306,9 +328,10 @@ def main(stdscr):
|
|||
f"[{cfg.key_reset}] reset [{capture_label(cfg)}] capture "
|
||||
f"[{cfg.key_quit}] quit "
|
||||
)
|
||||
status_y = box_pos[0] + box_rows
|
||||
try:
|
||||
stdscr.addstr(box_rows, 0, status[:cols].ljust(cols))
|
||||
stdscr.addstr(box_rows + 1, 0, help_line[:cols].ljust(cols - 1))
|
||||
stdscr.addstr(status_y, 0, status[:cols].ljust(cols))
|
||||
stdscr.addstr(status_y + 1, 0, help_line[:cols].ljust(cols - 1))
|
||||
except curses.error:
|
||||
pass
|
||||
stdscr.refresh()
|
||||
|
|
|
|||
Loading…
Reference in New Issue