|
| 1 | +# Create a Map from an image |
| 2 | +import os |
| 3 | +from pathlib import Path |
| 4 | +import argparse |
| 5 | +from random import uniform as random_float |
| 6 | + |
| 7 | +# Pillow |
| 8 | +from PIL import Image |
| 9 | + |
| 10 | +# pathfinding |
| 11 | +from pathfinding.core.diagonal_movement import DiagonalMovement |
| 12 | +from pathfinding.core.grid import Grid |
| 13 | +from pathfinding.finder.a_star import AStarFinder |
| 14 | + |
| 15 | + |
| 16 | +# image file with the map |
| 17 | +BASE_PATH = Path(os.path.dirname(__file__)) |
| 18 | +MAP_FILE = BASE_PATH / "map.png" |
| 19 | +OUT_FILE = BASE_PATH / "out.png" |
| 20 | + |
| 21 | +# this script searches for specific colors in RGB-format |
| 22 | +COLOR_START = (255, 255, 0) # yellow |
| 23 | +COLOR_END = (255, 0, 0) # red |
| 24 | +COLOR_PATH = (255, 165, 0) # orange |
| 25 | +COLOR_WEIGHT_MAPPING = { |
| 26 | + (0, 62, 178): 10, # deep water |
| 27 | + (9, 82, 198): 3, # water |
| 28 | + (254, 224, 179): 1, # sand |
| 29 | + (9, 120, 93): 2, # grass |
| 30 | + (10, 107, 72): 3, # bushes |
| 31 | + (11, 94, 51): 4, # forest |
| 32 | + (140, 142, 123): 5, # hills |
| 33 | + (160, 162, 143): 10, # alpine |
| 34 | + (53, 54, 68): 15, # steep cliff |
| 35 | + (255, 255, 255): 10, # snow |
| 36 | +} |
| 37 | + |
| 38 | + |
| 39 | +def main( |
| 40 | + filename_map: str = MAP_FILE, filename_out: str = OUT_FILE, |
| 41 | + weight_randomization: float = 0, diagonal_movement: bool = False, |
| 42 | +): |
| 43 | + nodes = [] |
| 44 | + if not Path(filename_map).exists(): |
| 45 | + print(f'File {filename_map} does not exist.') |
| 46 | + return |
| 47 | + |
| 48 | + print('Parsing map..') |
| 49 | + with Image.open(filename_map) as im: |
| 50 | + width, height = im.size |
| 51 | + for y in range(height): |
| 52 | + nodes.append([]) |
| 53 | + for x in range(width): |
| 54 | + pixel = im.getpixel((x, y)) |
| 55 | + weight = COLOR_WEIGHT_MAPPING.get(pixel, 1) |
| 56 | + |
| 57 | + if weight_randomization != 0: |
| 58 | + weight += random_float(0, weight_randomization) |
| 59 | + |
| 60 | + nodes[y].append(weight) |
| 61 | + |
| 62 | + if pixel == COLOR_END: |
| 63 | + _goal = (x, y) |
| 64 | + elif pixel == COLOR_START: |
| 65 | + _start = (x, y) |
| 66 | + |
| 67 | + grid = Grid(matrix=nodes) |
| 68 | + end = grid.node(*_goal) |
| 69 | + start = grid.node(*_start) |
| 70 | + |
| 71 | + print('Finding optimal path..') |
| 72 | + finder = AStarFinder(diagonal_movement=DiagonalMovement.always if diagonal_movement else DiagonalMovement.never) |
| 73 | + path, runs = finder.find_path(start, end, grid) |
| 74 | + |
| 75 | + # print(grid.grid_str(path=path, end=end, start=start)) |
| 76 | + print(f'iterations: {runs:_} path length: {len(path):_}') |
| 77 | + |
| 78 | + print('Saving image..') |
| 79 | + out = im.copy() |
| 80 | + for p in path[1:-1]: |
| 81 | + out.putpixel((p.x, p.y), COLOR_PATH) |
| 82 | + out.save(filename_out) |
| 83 | + |
| 84 | + |
| 85 | +if __name__ == '__main__': |
| 86 | + parser = argparse.ArgumentParser( |
| 87 | + prog='image_pathfinding', |
| 88 | + description='find a path in an image from a yellow pixel (rgb: 255,255,0) to a red one (rgb: 255,0,0) ' |
| 89 | + 'with weighted-tiles') |
| 90 | + parser.add_argument( |
| 91 | + '-i', '--filename_map', |
| 92 | + help='input file', |
| 93 | + default=MAP_FILE) |
| 94 | + parser.add_argument( |
| 95 | + '-o', '--filename_out', |
| 96 | + help='output file', |
| 97 | + default=OUT_FILE) |
| 98 | + parser.add_argument( |
| 99 | + '-r', '--weight-randomization', |
| 100 | + help='how much randomization should be added to the tile-weights (disabled by default)', |
| 101 | + type=float, |
| 102 | + default=0) |
| 103 | + parser.add_argument( |
| 104 | + '-d', '--diagonal-movement', |
| 105 | + help='allow for diagonal movement', |
| 106 | + action='store_true') |
| 107 | + |
| 108 | + main(**vars(parser.parse_args())) |
0 commit comments