Last active
March 22, 2026 17:04
-
-
Save hpsaturn/757f4ffb77f97570b42d355e355120e9 to your computer and use it in GitHub Desktop.
OSM Map tiles converter to eInk displays (greyscales)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Convert OpenStreetMap tiles to grayscale or black‑and‑white for e‑ink displays. | |
| Usage: | |
| python osm_tile_to_eink.py input_dir output_dir [--mode {gray,bw}] [--no-dither] | |
| """ | |
| import os | |
| import argparse | |
| from pathlib import Path | |
| from PIL import Image | |
| # Supported image extensions | |
| IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.tif', '.tiff', '.bmp'} | |
| def process_image(input_path, output_path, mode, dither): | |
| """ | |
| Convert a single image to grayscale or black‑and‑white and save it. | |
| Args: | |
| input_path (Path): Path to the input image. | |
| output_path (Path): Where to save the converted image. | |
| mode (str): 'gray' for 8‑bit grayscale, 'bw' for 1‑bit black‑and‑white. | |
| dither (bool): If True, use Floyd‑Steinberg dithering for bw mode. | |
| If False, use a simple threshold (128) without dithering. | |
| """ | |
| # Ensure the output directory exists | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| with Image.open(input_path) as img: | |
| # Convert to RGB if the image has transparency or is in a different mode | |
| if img.mode in ('RGBA', 'LA', 'P'): | |
| img = img.convert('RGB') | |
| if mode == 'gray': | |
| # Convert to 8‑bit grayscale | |
| img = img.convert('L') | |
| else: # mode == 'bw' | |
| if dither: | |
| # Convert to 1‑bit with Floyd‑Steinberg dithering | |
| img = img.convert('1') | |
| else: | |
| # Convert to grayscale first, then apply a threshold | |
| gray = img.convert('L') | |
| # 128 is the threshold (0‑255); you can change it if needed | |
| img = gray.point(lambda x: 0 if x < 128 else 255, '1') | |
| # Save with the same format as the original (or you can force PNG) | |
| img.save(output_path) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="Convert OpenStreetMap tiles to e‑ink friendly colors." | |
| ) | |
| parser.add_argument('input_dir', type=Path, | |
| help='Directory containing the downloaded tiles') | |
| parser.add_argument('output_dir', type=Path, | |
| help='Directory where converted tiles will be saved') | |
| parser.add_argument('--mode', choices=['gray', 'bw'], default='gray', | |
| help="'gray' for 8‑bit grayscale, 'bw' for 1‑bit black‑and‑white") | |
| parser.add_argument('--no-dither', action='store_true', | |
| help='Disable dithering when using bw mode (use simple threshold instead)') | |
| args = parser.parse_args() | |
| input_dir = args.input_dir.resolve() | |
| output_dir = args.output_dir.resolve() | |
| mode = args.mode | |
| dither = not args.no_dither | |
| if not input_dir.is_dir(): | |
| print(f"Error: Input directory '{input_dir}' does not exist.") | |
| return 1 | |
| # Walk through all files in input_dir | |
| for root, _, files in os.walk(input_dir): | |
| for file in files: | |
| ext = Path(file).suffix.lower() | |
| if ext not in IMAGE_EXTENSIONS: | |
| continue | |
| input_path = Path(root) / file | |
| # Compute relative path to keep folder structure | |
| rel_path = input_path.relative_to(input_dir) | |
| output_path = output_dir / rel_path | |
| # Change extension if necessary? Keep original for now. | |
| # You can force a specific format by changing the suffix. | |
| # For e‑ink, PNG is a good choice. | |
| # Example: output_path = output_path.with_suffix('.png') | |
| try: | |
| process_image(input_path, output_path, mode, dither) | |
| print(f"Processed: {input_path} -> {output_path}") | |
| except Exception as e: | |
| print(f"Error processing {input_path}: {e}") | |
| print("Conversion finished.") | |
| return 0 | |
| if __name__ == '__main__': | |
| exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment