Skip to content

Instantly share code, notes, and snippets.

@hpsaturn
Last active March 22, 2026 17:04
Show Gist options
  • Select an option

  • Save hpsaturn/757f4ffb77f97570b42d355e355120e9 to your computer and use it in GitHub Desktop.

Select an option

Save hpsaturn/757f4ffb77f97570b42d355e355120e9 to your computer and use it in GitHub Desktop.
OSM Map tiles converter to eInk displays (greyscales)
#!/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