diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000..62f40d6 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,62 @@ +This tool converts an image containing Unicode glyphs into Rust-compatible bitmap arrays for use in the `img2irc` project. + +## Workflow Overview + +1. **Create input files:** + + * An **image file** with glyphs rendered horizontally, without gaps. Essentially a cropped screenshot of the rendered characters with no spaces in between. + * A **text file** listing the same glyphs separated by spaces. + +2. **Generate JSON bitmap data (`mkbitmap.py`).** + +3. **Convert JSON to Rust source (`mkrust.py`).** + +4. Replace `src/chars.rs` in `img2irc` with the generated Rust file and recompile. + +--- + +## `mkbitmap.py` + +Slices an image into bitmaps and outputs JSON. + +**Arguments:** + +| Argument | Description | +| ------------- | ------------------------------------------------ | +| `chars_file` | Text file containing glyphs separated by spaces. | +| `image_file` | Horizontal image of glyphs (no gaps). | +| `--icw` | Input character width (pixels). | +| `--ich` | Input character height (pixels). | +| `--threshold` | Black/white pixel threshold (0โ€“255). | +| `--obw` | Output bitmap width. | +| `--obh` | Output bitmap height. | +| `--output` | Output JSON filename. | + +**Example:** + +```bash +./mkbitmap.py chars.txt chars.png --icw 20 --ich 40 --threshold 128 --obw 10 --obh 20 --output chars.json +``` + +--- + +## `mkrust.py` + +Converts JSON bitmap data to Rust source code. + +**Arguments:** + +| Argument | Description | +| ------------ | ----------------------------- | +| `input_json` | JSON file from `mkbitmap.py`. | +| `output_rs` | Output Rust source file. | + +**Example:** + +```bash +./mkrust.py chars.json chars.rs +``` + +Replace the resulting `chars.rs` in your `img2irc` project and recompile. + +You can use the included `chars.txt` and `chars.png` as an example. \ No newline at end of file diff --git a/utils/chars.png b/utils/chars.png new file mode 100644 index 0000000..010d4db Binary files /dev/null and b/utils/chars.png differ diff --git a/utils/chars.txt b/utils/chars.txt new file mode 100644 index 0000000..c69a2e3 --- /dev/null +++ b/utils/chars.txt @@ -0,0 +1 @@ +๐Ÿฌ€ ๐Ÿฌ ๐Ÿฌ‚ ๐Ÿฌƒ ๐Ÿฌ„ ๐Ÿฌ… ๐Ÿฌ† ๐Ÿฌ‡ ๐Ÿฌˆ ๐Ÿฌ‰ ๐ŸฌŠ ๐Ÿฌ‹ ๐ŸฌŒ ๐Ÿฌ ๐ŸฌŽ ๐Ÿฌ ๐Ÿฌ ๐Ÿฌ‘ ๐Ÿฌ’ ๐Ÿฌ“ ๐Ÿฌ” ๐Ÿฌ• ๐Ÿฌ– ๐Ÿฌ— ๐Ÿฌ˜ ๐Ÿฌ™ ๐Ÿฌš ๐Ÿฌ› ๐Ÿฌœ ๐Ÿฌ ๐Ÿฌž ๐ŸฌŸ ๐Ÿฌ  ๐Ÿฌก ๐Ÿฌข ๐Ÿฌฃ ๐Ÿฌค ๐Ÿฌฅ ๐Ÿฌฆ ๐Ÿฌง ๐Ÿฌจ ๐Ÿฌฉ ๐Ÿฌช ๐Ÿฌซ ๐Ÿฌฌ ๐Ÿฌญ ๐Ÿฌฎ ๐Ÿฌฏ ๐Ÿฌฐ ๐Ÿฌฑ ๐Ÿฌฒ ๐Ÿฌณ ๐Ÿฌด ๐Ÿฌต ๐Ÿฌถ ๐Ÿฌท ๐Ÿฌธ ๐Ÿฌน ๐Ÿฌบ ๐Ÿฌป ๐Ÿฌผ ๐Ÿฌผ ๐Ÿฌฝ ๐Ÿฌพ ๐Ÿฌฟ ๐Ÿญ€ ๐Ÿญ ๐Ÿญ‚ ๐Ÿญƒ ๐Ÿญ„ ๐Ÿญ… ๐Ÿญ† ๐Ÿญ‡ ๐Ÿญˆ ๐Ÿญ‰ ๐ŸญŠ ๐Ÿญ‹ ๐ŸญŒ ๐Ÿญ ๐ŸญŽ ๐Ÿญ ๐Ÿญ ๐Ÿญ‘ ๐Ÿญ’ ๐Ÿญ“ ๐Ÿญ” ๐Ÿญ• ๐Ÿญ– ๐Ÿญ— ๐Ÿญ˜ ๐Ÿญ™ ๐Ÿญš ๐Ÿญ› ๐Ÿญœ ๐Ÿญ ๐Ÿญž ๐ŸญŸ ๐Ÿญ  ๐Ÿญก ๐Ÿญข ๐Ÿญฃ ๐Ÿญค ๐Ÿญฅ ๐Ÿญฆ ๐Ÿญง ๐Ÿญจ ๐Ÿญฉ ๐Ÿญช ๐Ÿญซ ๐Ÿญฌ ๐Ÿญญ ๐Ÿญฎ ๐Ÿญฏ ๐Ÿญถ ๐Ÿญท ๐Ÿญธ ๐Ÿญน ๐Ÿญบ ๐Ÿญป ๐Ÿญผ ๐Ÿญฝ ๐Ÿญพ ๐Ÿญฟ ๐Ÿฎ€ ๐Ÿฎ ๐Ÿฎ‚ ๐Ÿฎƒ ๐Ÿฎ„ ๐Ÿฎ… ๐Ÿฎ† ๐Ÿฎ‡ ๐Ÿฎˆ ๐Ÿฎ‰ ๐ŸฎŠ ๐Ÿฎ‹ ๐Ÿฎ— ๐Ÿฎš ๐Ÿฎ› โ–€ โ– โ–‚ โ–ƒ โ–„ โ–… โ–† โ–‡ โ–ˆ โ–‰ โ–Š โ–‹ โ–Œ โ– โ–Ž โ– โ– โ–” โ–” โ–• โ–– โ–– โ–— โ–˜ โ–™ โ–š โ–› โ–œ โ– โ–ž โ–Ÿ ๐Ÿ ด ๐Ÿ ต ๐Ÿ ถ ๐Ÿ ท ๐Ÿž€ ๐Ÿž ๐Ÿž‚ ๐Ÿžƒ ๐Ÿž ๐…› ๐…‡ ๐…‰ ๐…‹ ๐… ๐… ๐…‘ ๐…“ โˆŽ โŽ– โด โต โถ โท โน โบ โฝ โ”‚ โ”ƒ โ•ธ โ•ท โ•ธ โ•น โ•ป โ–  โ–ช โ–ฌ โ–ฎ โ–ฐ โ–ฒ โ–ด โ–ถ โ–ธ โ–ผ โ–พ โ—€ โ—‚ โ—† โ— โ—– โ—— โ—ข โ—ฃ โ—ค โ—ฅ โš diff --git a/utils/mkbitmap.py b/utils/mkbitmap.py new file mode 100644 index 0000000..eee1386 --- /dev/null +++ b/utils/mkbitmap.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import argparse +import json +from PIL import Image + +def main(): + p = argparse.ArgumentParser( + description="Slice a row-image of glyphs into bitmaps and output JSON mapping." + ) + p.add_argument('chars_file', help='Space-delimited glyphs text file (UTF-8)') + p.add_argument('image_file', help='Image with glyphs in one row, no gaps') + p.add_argument('--icw', type=int, required=True, help='Input char width (px)') + p.add_argument('--ich', type=int, required=True, help='Input char height (px)') + p.add_argument('--threshold', type=int, required=True, help='B/W threshold (0โ€“255)') + p.add_argument('--obw', type=int, required=True, help='Output bitmap width') + p.add_argument('--obh', type=int, required=True, help='Output bitmap height') + p.add_argument('--output', required=True, help='Output JSON filename') + args = p.parse_args() + + # 1) Read glyphs + with open(args.chars_file, 'r', encoding='utf-8') as f: + glyphs = f.read().strip().split(' ') + + # 2) Load image & sanity-check + im = Image.open(args.image_file).convert('L') + w, h = im.size + if h != args.ich: + raise ValueError(f"Image height {h} โ‰  ich {args.ich}") + count = w // args.icw + if count * args.icw != w: + raise ValueError(f"Image width {w} not a multiple of icw {args.icw}") + if len(glyphs) != count: + raise ValueError(f"{len(glyphs)} glyphs but {count} blocks in image") + + # 3) Build mapping glyph โ†’ grid + mapping = {} + for i, glyph in enumerate(glyphs): + left = i * args.icw + block = im.crop((left, 0, left + args.icw, args.ich)) + block = block.resize((args.obw, args.obh), Image.LANCZOS) + data = list(block.getdata()) + bits = [1 if pix > args.threshold else 0 for pix in data] + grid = [bits[row*args.obw:(row+1)*args.obw] for row in range(args.obh)] + mapping[glyph] = grid + + # 4) Write JSON manually so rows stay inline + with open(args.output, 'w', encoding='utf-8') as out: + out.write('{\n') + total = len(mapping) + for idx, (glyph, grid) in enumerate(mapping.items()): + # properly quote the glyph + key = json.dumps(glyph, ensure_ascii=False) + out.write(f' {key}: [\n') + for ridx, row in enumerate(grid): + row_str = ','.join(str(bit) for bit in row) + comma = ',' if ridx < len(grid) - 1 else '' + out.write(f' [{row_str}]{comma}\n') + out.write(' ]') + out.write(',\n' if idx < total - 1 else '\n') + out.write('}\n') + +if __name__ == '__main__': + main() diff --git a/utils/mkrust.py b/utils/mkrust.py new file mode 100644 index 0000000..66dec12 --- /dev/null +++ b/utils/mkrust.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +import json +import unicodedata +import argparse +import sys + +def get_glyph_name(ch): + """ + Return the official Unicode name of ch, or its codepoint if unnamed. + """ + try: + return unicodedata.name(ch) + except ValueError: + # Fallback to codepoint string + return f"U+{ord(ch):04X}" + +def format_rust_array(matrix): + """ + Given a 2D list of ints, return a Rust-style nested array string. + """ + lines = [] + for row in matrix: + # join numbers with commas, no spaces after commas to match example + line = "[" + ",".join(str(v) for v in row) + "]," + lines.append(" " + line) + return "\n".join(lines) + +def main(): + parser = argparse.ArgumentParser( + description="Convert JSON glyph bitmaps to a Rust source file." + ) + parser.add_argument("input_json", help="Path to input JSON file") + parser.add_argument("output_rs", help="Path to write Rust source file") + args = parser.parse_args() + + # Load JSON + with open(args.input_json, "r", encoding="utf-8") as f: + data = json.load(f) + + if not data: + print("No glyphs found in JSON.", file=sys.stderr) + sys.exit(1) + + # Determine dimensions from first glyph + first_matrix = next(iter(data.values())) + rows = len(first_matrix) + cols = len(first_matrix[0]) if rows > 0 else 0 + + # Begin emitting Rust code + with open(args.output_rs, "w", encoding="utf-8") as out: + out.write("// This file was generated by mkrust.py\n") + out.write(f"pub const GLYPH_BITMAPS: &[(char, [[u8; {cols}]; {rows}])] = &[\n") + + for ch, matrix in data.items(): + name = get_glyph_name(ch) + # comment with the Unicode name + out.write(f" // {name}\n") + out.write(f" ('{ch}', [\n") + out.write(format_rust_array(matrix) + "\n") + out.write(" ]),\n\n") + + out.write("];\n") + +if __name__ == "__main__": + main()