3Redraws the given image with 'chunky pixels' – every NxN chunk of pixels
4is replaced by a single colour.
13from PIL import Image, ImageDraw
17def clamp(x: float, /, *, between: tuple[int, int]) -> float:
19 Restrict the value to fit between the lower/upper bounds.
21 lower, upper = between
22 return min([max([x, lower]), upper])
25if __name__ == "__main__":
28 pixel_size = int(sys.argv[2])
29 except (IndexError, ValueError):
30 sys.exit(f"Usage: {__file__} <PATH> <PIXEL_SIZE>")
34 assert im.width % pixel_size == 0
35 assert im.height % pixel_size == 0
39 with tempfile.TemporaryDirectory() as tmpdir:
40 for x_start in tqdm.tqdm(range(0, im.width, pixel_size)):
41 for y_start in range(0, im.height, pixel_size):
43 (x_start, y_start, x_start + pixel_size, y_start + pixel_size)
45 tmp_path = f"{tmpdir}/cropped_{x_start}_{y_start}.png"
46 im_crop.save(tmp_path)
47 colour = subprocess.check_output(
48 ["dominant_colours", tmp_path, "--max-colours=1", "--no-palette"]
50 all_colours[(x_start, y_start)] = (
56 im = Image.new("RGB", im.size)
57 draw = ImageDraw.Draw(im)
59 for (x_start, y_start), (r, g, b) in all_colours.items():
60 hue, lightness, saturation = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
61 lightness = clamp(lightness * random.uniform(0.9, 1.1), between=(0, 1))
62 saturation = clamp(saturation * random.uniform(0.9, 1.1), between=(0, 1))
64 r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation)
66 [(x_start, y_start), (x_start + pixel_size), (y_start + pixel_size)],
67 fill=(int(r * 255), int(g * 255), int(b * 255)),
70 base, extension = path.rsplit(".", 1)
72 out_path = f"{base}_{pixel_size}.{extension}"