this repo has no description
1import json
2import numpy as np
3from PIL import Image, ImageDraw, ImageFont
4import os
5import argparse
6
7# Load configuration settings from JSON file
8with open("./config/generation.json", "r") as file:
9 config = json.load(file)
10 sky_colours = config["sky_colours"]
11 name = config["name"]
12
13def interpolate_colour(hour):
14 """Interpolate sky colour based on the given hour."""
15 hours = sorted(map(int, sky_colours.keys()))
16
17 for i in range(len(hours) - 1):
18 if hours[i] <= hour <= hours[i + 1]:
19 t = (hour - hours[i]) / (hours[i + 1] - hours[i])
20 c1, c2 = np.array(sky_colours[str(hours[i])]), np.array(sky_colours[str(hours[i + 1])])
21 return tuple((1 - t) * c1 + t * c2) + (255,)
22
23 return tuple(sky_colours[str(hour)]) + (255,)
24
25def create_gradient(hour, width, height):
26 """Generate gradient with fade effect and appropriate RGBA handling."""
27 colour = interpolate_colour(hour)
28
29 average_colour = np.mean(colour[:3])
30 fade_ratio = 0.1 + (0.5 - 0.1) * (1 - average_colour / 255)
31 gradient_height = int(height * fade_ratio)
32
33 monochrome_colour = (int(average_colour), int(average_colour), int(average_colour), 255)
34
35 gradient_rgb = np.vstack([
36 np.full((height - gradient_height, width, 3), colour[:3], dtype=np.uint8),
37 np.linspace(colour[:3], monochrome_colour[:3], gradient_height)
38 .astype(np.uint8)
39 .reshape(gradient_height, 1, 3)
40 .repeat(width, axis=1),
41 ])
42
43 alpha_channel = np.full((gradient_rgb.shape[0], gradient_rgb.shape[1], 1), 255, dtype=np.uint8)
44 gradient_rgba = np.concatenate((gradient_rgb, alpha_channel), axis=2)
45 return gradient_rgba
46
47def get_available_folder(base_folder):
48 """Check if the folder already exists, and if so, create a new folder with a counter."""
49 counter = 1
50 folder = base_folder
51 while os.path.exists(folder):
52 folder = f"{base_folder}_{counter}"
53 counter += 1
54 return folder
55
56def get_max_font_size(draw, text, font_path, max_width, max_height):
57 """Calculate the maximum font size that fits within the image dimensions."""
58 font_size = 1
59 font = ImageFont.truetype(font_path, font_size)
60 text_width, text_height = draw.textbbox((0, 0), text, font=font)[2:4]
61
62 while text_width <= max_width and text_height <= max_height:
63 font_size += 1
64 font = ImageFont.truetype(font_path, font_size)
65 text_width, text_height = draw.textbbox((0, 0), text, font=font)[2:4]
66
67 return font_size - 1
68
69# Parse command-line arguments
70parser = argparse.ArgumentParser(description="Generate profile images or banners.")
71parser.add_argument(
72 "-p", "--profile", action="store_true", help="Generate profile image (400x400)."
73)
74parser.add_argument(
75 "-b", "--banner", action="store_true", help="Generate banner image (1500x500)."
76)
77parser.add_argument(
78 "-c", "--custom", action="store_true", help="Generate custom image with custom width and height."
79)
80parser.add_argument("-w", "--width", type=int, help="Custom image width (required with -c).")
81parser.add_argument("-H", "--height", type=int, help="Custom image height (required with -c).")
82args = parser.parse_args()
83
84# Set image dimensions based on the flags
85if args.profile:
86 width, height = 400, 400
87 output_folder = "./src/profile_pics"
88elif args.banner:
89 width, height = 1500, 500
90 output_folder = "./src/banners"
91elif args.custom:
92 if not args.width or not args.height:
93 print("Error: Custom dimensions (-w and -H) are required with the -c flag.")
94 exit(1)
95 width, height = args.width, args.height
96 output_folder = f"./src/custom_{width}x{height}"
97else:
98 print("Error: You must specify either -p, -b, or -c.")
99 exit(1)
100
101# Define font path
102font_path = "./config/fonts/madecarvingsoft.ttf"
103
104# Create output folder
105output_folder = get_available_folder(output_folder)
106os.makedirs(output_folder)
107
108# Determine which hours need image generation
109images_to_generate = []
110for hour in range(24):
111 image_path = f"{output_folder}/{str(hour).zfill(2)}.png"
112 if not os.path.exists(image_path):
113 images_to_generate.append(hour)
114
115# Generate images for the specified hours
116for hour in images_to_generate:
117 # Create gradient
118 gradient = create_gradient(hour, width, height)
119 # Create image from gradient array
120 img = Image.fromarray(gradient, 'RGBA')
121 draw = ImageDraw.Draw(img)
122
123 # Text color calculation (adjusting based on background)
124 text_colour = (
125 min(255, int(255 - gradient[0][0][0] * 1.2)),
126 min(255, int(255 - gradient[0][0][1] * 1.2)),
127 min(255, int(255 - gradient[0][0][2] * 1.2)),
128 255, # Full opacity for text
129 )
130
131 # Calculate text placement, font size, and draw text
132 horizontal_padding = width * 0.1
133 usable_width = width - 2 * horizontal_padding
134 vertical_padding = height * 0.1
135 usable_height = height - 2 * vertical_padding
136 font_size = get_max_font_size(draw, name, font_path, usable_width, usable_height)
137 font = ImageFont.truetype(font_path, font_size)
138
139 # Get text bounding box and position
140 bbox = draw.textbbox((0, 0), name, font=font)
141 text_width = bbox[2] - bbox[0]
142 text_height = bbox[3] - bbox[1]
143 position_x = (width - text_width) // 2
144 position_y = (height - text_height) // 2
145
146 # Ensure text fits within padding and margins
147 position_x = max(position_x, horizontal_padding)
148 position_y = max(position_y, vertical_padding)
149 min_bottom_margin = 20
150 max_position_y = height - text_height - min_bottom_margin
151 position_y = min(position_y, max_position_y - text_height)
152
153 # Add text to image
154 text_img = Image.new("RGBA", img.size, (255, 255, 255, 0))
155 text_draw = ImageDraw.Draw(text_img)
156 text_draw.text((position_x, position_y), name, font=font, fill=text_colour)
157 img = Image.alpha_composite(img, text_img)
158
159 # Optionally add noise to image
160 noise = np.random.normal(0, 25, (height, width, 3)).astype(np.uint8)
161 noise_img = Image.fromarray(noise, mode="RGB")
162 img = Image.blend(img.convert("RGB"), noise_img, alpha=0.1)
163
164 # Save the generated image
165 image_path = f"{output_folder}/{str(hour).zfill(2)}.png"
166 img.save(image_path)
167
168# Print success message
169print(f"Images generated successfully.")
170print(f"Images saved to: {output_folder}")