2025-11-30 14:45:55 +01:00
|
|
|
"""Generate random colorful JPEG images with configurable parameters."""
|
|
|
|
|
|
2025-11-30 13:42:16 +01:00
|
|
|
import argparse
|
2025-11-30 16:03:14 +01:00
|
|
|
from dataclasses import dataclass
|
2025-11-30 15:30:25 +01:00
|
|
|
from datetime import datetime, timezone
|
2025-11-30 14:36:13 +01:00
|
|
|
import logging
|
2025-11-30 13:42:16 +01:00
|
|
|
import os
|
2025-11-30 21:20:17 +01:00
|
|
|
import secrets
|
2025-11-30 13:42:16 +01:00
|
|
|
|
|
|
|
|
from PIL import Image
|
|
|
|
|
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger = logging.getLogger(__name__)
|
2025-11-30 14:36:13 +01:00
|
|
|
|
2025-11-30 21:20:17 +01:00
|
|
|
# Use cryptographically secure random number generator
|
|
|
|
|
_rng = secrets.SystemRandom()
|
|
|
|
|
|
2025-11-30 15:01:14 +01:00
|
|
|
MAX_IMAGE_SIZE = 1000
|
|
|
|
|
|
2025-11-30 13:42:16 +01:00
|
|
|
|
2025-11-30 16:03:14 +01:00
|
|
|
@dataclass
|
|
|
|
|
class ImageConfig:
|
|
|
|
|
"""Configuration for generating a bloated JPEG image."""
|
|
|
|
|
|
|
|
|
|
size: int
|
|
|
|
|
color_list: list[str]
|
|
|
|
|
block_size: int
|
|
|
|
|
output_path: str
|
|
|
|
|
quality: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_bloated_jpeg(config: ImageConfig, image_index: int, folder: str) -> str:
|
|
|
|
|
"""Generates a random JPEG image with given configuration.
|
2025-11-30 13:42:16 +01:00
|
|
|
|
|
|
|
|
Args:
|
2025-11-30 16:03:14 +01:00
|
|
|
config: Image generation configuration.
|
2025-11-30 15:49:40 +01:00
|
|
|
image_index: Index of the image for unique naming.
|
|
|
|
|
folder: Folder to save the image.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Path to the generated image.
|
2025-11-30 13:42:16 +01:00
|
|
|
"""
|
2025-11-30 15:01:14 +01:00
|
|
|
# Ensure size is divisible by block_size and does not exceed MAX_IMAGE_SIZE
|
2025-11-30 16:03:14 +01:00
|
|
|
if config.size > MAX_IMAGE_SIZE or config.size % config.block_size != 0:
|
2025-11-30 15:01:14 +01:00
|
|
|
msg = (
|
|
|
|
|
f"Size must be {MAX_IMAGE_SIZE} pixels or less and divisible by block_size"
|
|
|
|
|
)
|
2025-11-30 13:59:21 +01:00
|
|
|
raise ValueError(msg)
|
2025-11-30 13:42:16 +01:00
|
|
|
|
|
|
|
|
# Create a new image
|
2025-11-30 16:03:14 +01:00
|
|
|
image = Image.new("RGB", (config.size, config.size))
|
2025-11-30 13:42:16 +01:00
|
|
|
pixels = image.load()
|
|
|
|
|
|
|
|
|
|
# Convert hex colors to RGB
|
2025-11-30 14:25:35 +01:00
|
|
|
rgb_colors = [
|
2025-11-30 16:03:14 +01:00
|
|
|
tuple(int(color[i : i + 2], 16) for i in (1, 3, 5))
|
|
|
|
|
for color in config.color_list
|
2025-11-30 14:25:35 +01:00
|
|
|
]
|
2025-11-30 13:42:16 +01:00
|
|
|
|
2025-11-30 14:25:35 +01:00
|
|
|
# Fill the image with block_size x block_size pixel squares
|
|
|
|
|
# of random colors from the list
|
2025-11-30 16:03:14 +01:00
|
|
|
for y in range(0, config.size, config.block_size):
|
|
|
|
|
for x in range(0, config.size, config.block_size):
|
2025-11-30 21:20:17 +01:00
|
|
|
color = _rng.choice(rgb_colors)
|
2025-11-30 16:03:14 +01:00
|
|
|
for i in range(config.block_size):
|
|
|
|
|
for j in range(config.block_size):
|
2025-11-30 13:42:16 +01:00
|
|
|
pixels[x + i, y + j] = color
|
|
|
|
|
|
|
|
|
|
# Create the folder if it does not exist
|
|
|
|
|
if not os.path.exists(folder):
|
|
|
|
|
os.makedirs(folder)
|
|
|
|
|
|
|
|
|
|
# Generate unique output path
|
|
|
|
|
unique_output_path = os.path.join(
|
|
|
|
|
folder,
|
2025-11-30 16:03:14 +01:00
|
|
|
f"{os.path.splitext(config.output_path)[0]}_{image_index}"
|
|
|
|
|
f"{os.path.splitext(config.output_path)[1]}",
|
2025-11-30 13:42:16 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Save the image with specified quality to maximize file size
|
2025-11-30 16:03:14 +01:00
|
|
|
image.save(unique_output_path, "JPEG", quality=config.quality, optimize=False)
|
2025-11-30 13:42:16 +01:00
|
|
|
|
|
|
|
|
return unique_output_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-11-30 14:25:35 +01:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description="Generate bloated JPEG images with random colors."
|
|
|
|
|
)
|
2025-11-30 13:42:16 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
"-n",
|
|
|
|
|
"--num_images",
|
|
|
|
|
type=int,
|
|
|
|
|
default=1,
|
|
|
|
|
help="Number of images to generate. Default is 1.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-s",
|
|
|
|
|
"--size",
|
|
|
|
|
type=int,
|
|
|
|
|
default=1000,
|
2025-11-30 14:25:35 +01:00
|
|
|
help=(
|
|
|
|
|
"Size of the images (must be 1000 or less "
|
|
|
|
|
"and divisible by block size). Default is 1000."
|
|
|
|
|
),
|
2025-11-30 13:42:16 +01:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-c",
|
|
|
|
|
"--colors",
|
|
|
|
|
nargs="+",
|
|
|
|
|
default=["#FF5733", "#33FF57", "#3357FF", "#F3FF33", "#FF33F6", "#33FFF6"],
|
2025-11-30 13:59:21 +01:00
|
|
|
help="List of colors in hex format. Uses 6 default colors if not specified.",
|
2025-11-30 13:42:16 +01:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-b",
|
|
|
|
|
"--block_size",
|
|
|
|
|
type=int,
|
|
|
|
|
default=4,
|
2025-11-30 14:25:35 +01:00
|
|
|
help=(
|
|
|
|
|
"Size of the pixel blocks (must divide the "
|
|
|
|
|
"image size evenly). Default is 4."
|
|
|
|
|
),
|
2025-11-30 13:42:16 +01:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-o",
|
|
|
|
|
"--output_path",
|
|
|
|
|
type=str,
|
|
|
|
|
default="bloated_image.jpeg",
|
|
|
|
|
help="Base output path for the JPEG images. Default is 'bloated_image.jpeg'.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-q",
|
|
|
|
|
"--quality",
|
|
|
|
|
type=int,
|
|
|
|
|
default=100,
|
|
|
|
|
help="Quality setting for the JPEG images (0-100). Default is 100.",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
# Create folder named after the current timestamp
|
2025-11-30 15:30:25 +01:00
|
|
|
timestamp = datetime.now(tz=timezone.utc).strftime("%Y%m%d_%H%M%S")
|
2025-11-30 13:42:16 +01:00
|
|
|
folder = f"generated_images_{timestamp}"
|
|
|
|
|
|
|
|
|
|
# Display used parameters
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger.info(
|
|
|
|
|
"Generating %s image(s) with the following parameters:", args.num_images
|
2025-11-30 14:36:13 +01:00
|
|
|
)
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger.info(" Size: %s", args.size)
|
|
|
|
|
_logger.info(" Colors: %s", args.colors)
|
|
|
|
|
_logger.info(" Block size: %s", args.block_size)
|
|
|
|
|
_logger.info(" Base output path: %s", args.output_path)
|
|
|
|
|
_logger.info(" Quality: %s", args.quality)
|
|
|
|
|
_logger.info(" Output folder: %s", folder)
|
2025-11-30 13:42:16 +01:00
|
|
|
|
|
|
|
|
# Generate the specified number of images
|
2025-11-30 16:03:14 +01:00
|
|
|
config = ImageConfig(
|
|
|
|
|
size=args.size,
|
|
|
|
|
color_list=args.colors,
|
|
|
|
|
block_size=args.block_size,
|
|
|
|
|
output_path=args.output_path,
|
|
|
|
|
quality=args.quality,
|
|
|
|
|
)
|
2025-11-30 13:42:16 +01:00
|
|
|
for i in range(1, args.num_images + 1):
|
2025-11-30 16:03:14 +01:00
|
|
|
output_path = generate_bloated_jpeg(config, i, folder)
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger.info("Image %s saved to %s", i, os.path.abspath(output_path))
|