1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
#!/usr/bin/env python3
import sys
import os
import argparse
from PIL import Image, ImagePalette
def generate_ansi_256():
palette = []
palette.extend([
(0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0),
(0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192),
(128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0),
(0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255),
])
levels = [0, 95, 135, 175, 215, 255]
for r in levels:
for g in levels:
for b in levels:
palette.append((r, g, b))
for i in range(24):
gray = 8 + i * 10
palette.append((gray, gray, gray))
return palette[:256]
def create_palette(colors):
palette = []
for r, g, b in colors:
palette.extend([r, g, b])
while len(palette) < 768:
palette.extend([0, 0, 0])
return palette
def pixelate_and_reduce_colors(image, width, block_scale, dither, palette_type):
aspect = image.size[1] / image.size[0]
height = int(width * aspect)
block_size = max(1, int(width * (block_scale / 100.0)))
block_width = max(1, width // block_size)
block_height = max(1, height // block_size)
image = image.resize((block_width, block_height), Image.NEAREST).convert('RGB')
if palette_type == 'full':
return image.resize((block_width * block_size, block_height * block_size), Image.NEAREST).convert('RGB')
if palette_type == 'ansi256':
colors = generate_ansi_256()
pal_image = Image.new('P', (1, 1))
pal_image.putpalette(create_palette(colors))
dither_mode = {'floyd': Image.FLOYDSTEINBERG, 'ordered': Image.NONE}[dither]
image = image.quantize(colors=len(colors), palette=pal_image, method=Image.LIBIMAGEQUANT, dither=dither_mode)
else:
raise ValueError(f'[err] unknown palette type: {palette_type}')
return image.resize((block_width * block_size, block_height * block_size), Image.NEAREST).convert('RGB')
def main():
parser = argparse.ArgumentParser(description='pixelate an image and reduce colors to a selected palette')
parser.add_argument('input_image', help='input image file (e.g., png, jpg)')
parser.add_argument('-o', '--output', default=None, help='output png file (default: same filename in "output" directory)')
parser.add_argument('--width', type=int, default=800, help='output width in pixels (default: 800)')
parser.add_argument('--block-scale', type=float, default=1.0,
help='block size as a percentage of width (0.1-10.0, default: 1.0)')
parser.add_argument('--dither', default='floyd', choices=['floyd', 'ordered'],
help='dithering method (default: floyd)')
parser.add_argument('--palette', default='ansi256', choices=['ansi256', 'full'],
help='color palette: ansi256 (256 colors), full (16m colors, default: ansi256)')
args = parser.parse_args()
if not os.path.isfile(args.input_image):
print(f'[err] "{args.input_image}" does not exist')
sys.exit(1)
if not 0.1 <= args.block_scale <= 10.0:
print('[err] --block-scale must be between 0.1 and 10.0')
sys.exit(1)
if args.width < 1:
print('[err] --width must be at least 1')
sys.exit(1)
try:
image = Image.open(args.input_image)
if image.format not in ['PNG', 'JPEG', 'BMP', 'GIF']:
print(f'[wrn] "{image.format}" may not be fully supported; use png or jpeg for best results')
except Exception as e:
print(f'[err] failed to open image: {e}')
sys.exit(1)
output_dir = 'output'
try:
os.makedirs(output_dir, exist_ok=True)
except Exception as e:
print(f'[err] failed to create output directory "{output_dir}": {e}')
sys.exit(1)
if args.output:
output_file = args.output
else:
output_file = os.path.join(output_dir, os.path.basename(args.input_image))
try:
result = pixelate_and_reduce_colors(
image, args.width, args.block_scale, args.dither, args.palette
)
except Exception as e:
print(f'[err] failed to process image: {e}')
sys.exit(1)
try:
result.save(output_file)
print(f'[inf] saved pixelated image to {output_file}')
except Exception as e:
print(f'[err] failed to save image: {e}')
sys.exit(1)
if __name__ == '__main__':
main()
|