Auto-select digit template font
This commit is contained in:
parent
3216f4a7a2
commit
53d1077a22
2 changed files with 94 additions and 15 deletions
|
|
@ -29,6 +29,8 @@ make
|
|||
|
||||
No sudo install is required for the default Portal/PipeWire capture path.
|
||||
|
||||
The digit matcher uses ImageMagick to render reference digits. By default it auto-selects an installed font, preferring Roboto Condensed when present and falling back to common system fonts such as DejaVu Sans.
|
||||
|
||||
## Use
|
||||
|
||||
Preferred command:
|
||||
|
|
@ -61,6 +63,12 @@ Save the captured input image while debugging:
|
|||
uv run reforger_queue_read.py --portal-window --save-input /tmp/apt-portal-window.png --show-crop --debug
|
||||
```
|
||||
|
||||
If font matching is weird on a specific machine, pass an installed ImageMagick font explicitly:
|
||||
|
||||
```bash
|
||||
uv run reforger_queue_read.py --portal-window --font DejaVu-Sans-Condensed --show-crop --debug
|
||||
```
|
||||
|
||||
## Optional KDE/KWin Mode
|
||||
|
||||
KWin mode can automatically find the Arma window by KWin metadata and can rebind after the game restarts. It is KDE-specific and requires installing an authorized helper.
|
||||
|
|
|
|||
|
|
@ -16,8 +16,20 @@ import uuid
|
|||
|
||||
DEFAULT_REFERENCE_SIZE = (2560, 1440)
|
||||
DEFAULT_REFERENCE_CROP = (1050, 620, 100, 60)
|
||||
DEFAULT_FONT = "Roboto-Condensed"
|
||||
DEFAULT_FONT = "auto"
|
||||
DEFAULT_POINTSIZE = 43
|
||||
AUTO_FONT_CANDIDATES = (
|
||||
"Roboto-Condensed",
|
||||
"RobotoCondensed-Regular",
|
||||
"Roboto-Condensed-Regular",
|
||||
"Noto-Sans-Condensed",
|
||||
"NotoSans-Condensed",
|
||||
"Liberation-Sans-Narrow",
|
||||
"DejaVu-Sans-Condensed",
|
||||
"DejaVu-Sans",
|
||||
"Arial",
|
||||
"Helvetica",
|
||||
)
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SYSTEM_KWIN_HELPER = "/usr/local/bin/reforger-kwin-capture"
|
||||
DEFAULT_PORTAL_RESTORE_TOKEN = os.path.expanduser(
|
||||
|
|
@ -507,8 +519,7 @@ def save_obs_scene_screenshot_with_setup(
|
|||
time.sleep(0.5)
|
||||
|
||||
|
||||
def ppm_from_magick(args):
|
||||
data = run_bytes(["magick", *args, "-alpha", "off", "-depth", "8", "ppm:-"])
|
||||
def parse_ppm(data):
|
||||
idx = 0
|
||||
|
||||
def next_token():
|
||||
|
|
@ -540,6 +551,44 @@ def ppm_from_magick(args):
|
|||
return width, height, data[idx:]
|
||||
|
||||
|
||||
def ppm_from_magick(args):
|
||||
return parse_ppm(run_bytes(["magick", *args, "-alpha", "off", "-depth", "8", "ppm:-"]))
|
||||
|
||||
|
||||
def try_ppm_from_magick(args):
|
||||
command = ["magick", *args, "-alpha", "off", "-depth", "8", "ppm:-"]
|
||||
try:
|
||||
result = subprocess.run(command, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except FileNotFoundError:
|
||||
sys.exit("missing required command: magick")
|
||||
|
||||
if result.returncode != 0:
|
||||
return None, result.stderr.decode("utf-8", "replace").strip()
|
||||
|
||||
try:
|
||||
return parse_ppm(result.stdout), ""
|
||||
except Exception as exc:
|
||||
return None, str(exc)
|
||||
|
||||
|
||||
def magick_fonts():
|
||||
fonts = set()
|
||||
for line in run_text(["magick", "-list", "font"]).splitlines():
|
||||
match = re.match(r"\s*Font:\s*(\S+)", line)
|
||||
if match:
|
||||
fonts.add(match.group(1))
|
||||
return fonts
|
||||
|
||||
|
||||
def template_font_candidates(font):
|
||||
if font != "auto":
|
||||
return [font]
|
||||
|
||||
available = magick_fonts()
|
||||
candidates = [candidate for candidate in AUTO_FONT_CANDIDATES if candidate in available]
|
||||
return [*candidates, None]
|
||||
|
||||
|
||||
def image_size(image_path):
|
||||
output = run_bytes(["magick", "identify", "-format", "%w %h", image_path]).decode("ascii")
|
||||
width, height = output.split()
|
||||
|
|
@ -657,24 +706,46 @@ def template_distance(left, right):
|
|||
return hamming, iou
|
||||
|
||||
|
||||
def build_templates(font, pointsize):
|
||||
width, height, raw = ppm_from_magick(
|
||||
def build_template_args(font, pointsize):
|
||||
args = [
|
||||
"-background",
|
||||
"black",
|
||||
"-fill",
|
||||
"white",
|
||||
]
|
||||
if font:
|
||||
args.extend(["-font", font])
|
||||
args.extend(
|
||||
[
|
||||
"-background",
|
||||
"black",
|
||||
"-fill",
|
||||
"white",
|
||||
"-font",
|
||||
font,
|
||||
"-pointsize",
|
||||
str(pointsize),
|
||||
"label:0123456789",
|
||||
]
|
||||
)
|
||||
components = connected_components(white_mask(width, height, raw))
|
||||
if len(components) < 10:
|
||||
sys.exit(f"template rendering produced only {len(components)} digit components")
|
||||
return {str(index): normalize(component) for index, component in enumerate(components[:10])}
|
||||
return args
|
||||
|
||||
|
||||
def build_templates(font, pointsize):
|
||||
errors = []
|
||||
for candidate in template_font_candidates(font):
|
||||
ppm, error = try_ppm_from_magick(build_template_args(candidate, pointsize))
|
||||
if ppm is None:
|
||||
errors.append(f"{candidate or 'ImageMagick default'}: {error}")
|
||||
continue
|
||||
|
||||
width, height, raw = ppm
|
||||
components = connected_components(white_mask(width, height, raw))
|
||||
if len(components) < 10:
|
||||
errors.append(f"{candidate or 'ImageMagick default'}: rendered only {len(components)} digit components")
|
||||
continue
|
||||
return {str(index): normalize(component) for index, component in enumerate(components[:10])}
|
||||
|
||||
if font == "auto":
|
||||
sys.exit("could not render digit templates with any available ImageMagick font:\n " + "\n ".join(errors))
|
||||
sys.exit(
|
||||
f"could not render digit templates with font {font!r}. "
|
||||
"Install that font, use --font auto, or pass another ImageMagick font name."
|
||||
)
|
||||
|
||||
|
||||
def classify(component, templates):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue