Image Resizer and Thumbnail Generator (python, written by Codex)
envgap__codex__python-t1-19
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
requirements.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/python-t1 #19 · read the task the agent was given
Codex wrote this python project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Image Resizer and Thumbnail Generator Write a program that resizes images and generates thumbnails at multiple configurable sizes, supporting different resampling algorithms, crop modes, and batch processing of entire directories. FUNCTIONAL REQUIREMENTS: - Accept an image file path as a command-line argument - Resize to exact dimensions via --size flag (WxH format, e.g., --size 1920x1080) - Resize by percentage via --scale flag (e.g., --scale 50 for 50% of original size) - Support multiple resampling algorithms selectable via --algorithm flag: nearest neighbor, bilinear, bicubic, and Lanczos - Support three resize modes via --mode flag: fit (scale within bounds preserving aspect ratio), fill (scale to cover bounds then center-crop), and stretch (distort to exact dimensions) - Generate a set of standard thumbnails via --thumbnails flag: small (150x150), medium (300x300), large (600x600), all center-cropped squares - Support custom thumbnail sizes via --thumb-sizes flag (comma-separated, e.g., --thumb-sizes 64x64,128x128,256x256) - Add optional padding/border around resized images via --padding flag (pixels) and --border-color flag (hex color) - Support batch processing of all images in a directory via --batch flag, with --recursive to include subdirectories - Print processing details to console: original dimensions, new dimensions, algorithm used, output file path, processing time per image - Save resized images to a directory specified by --output flag (default: resized/ subdirectory) - If no input is given, generate a sample 2000x1500 test image with fine details (grid lines, text at various sizes, gradient bands), then resize it using each algorithm and each mode to demonstrate the visual differences - Handle errors: invalid dimensions, unsupported image formats, memory limits for very large images Create a complete Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include: - Source code - requirements.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
README.md
# Image Resizer and Thumbnail Generator (Python) Resizes images with selectable algorithms/modes, generates thumbnails, supports padding, and batch processing. ## Requirements - Ubuntu 22.04 - Python 3.10+ ## Dependencies (Pinned) - `Pillow==10.4.0` ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Run ```bash python src/main.py input.png --size 1920x1080 --algorithm lanczos --mode fit python src/main.py input.png --scale 50 --algorithm bicubic --mode stretch python src/main.py input.png --thumbnails --thumb-sizes 64x64,128x128,256x256 python src/main.py input.png --padding 10 --border-color "#222222" python src/main.py ./images --batch --recursive --size 800x600 --output ./resized python src/main.py ``` ## Features - Resize by exact size (`--size`) or percent (`--scale`) - Algorithms: nearest, bilinear, bicubic, lanczos - Modes: fit, fill, stretch - Standard thumbnails (`--thumbnails`) + custom (`--thumb-sizes`) - Optional padding (`--padding`) and border color (`--border-color`) - Batch mode with recursive traversal - Processing details per output - Default output directory: `resized/` - No-args demo generates test image and runs algorithm/mode combinations
requirements.txt
Pillow==10.4.0
src/main.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import time
from pathlib import Path
from typing import Iterable
from PIL import Image, ImageDraw, ImageOps
RESAMPLE = {
"nearest": Image.Resampling.NEAREST,
"bilinear": Image.Resampling.BILINEAR,
"bicubic": Image.Resampling.BICUBIC,
"lanczos": Image.Resampling.LANCZOS,
}
MODES = {"fit", "fill", "stretch"}
SUPPORTED_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff", ".webp"}
def parse_size(value: str) -> tuple[int, int]:
try:
w, h = value.lower().split("x", 1)
return int(w), int(h)
except Exception as exc:
raise ValueError("Invalid size. Use WxH.") from exc
def parse_thumb_sizes(value: str | None) -> list[tuple[int, int]]:
if not value:
return []
return [parse_size(v.strip()) for v in value.split(",") if v.strip()]
def bool_opt(v: str | None, default: bool = True) -> bool:
if v is None:
return default
return v.lower() not in {"false", "0", "no"}
def output_dir(args: argparse.Namespace) -> Path:
return Path(args.output or "resized").resolve()
def fit_mode(img: Image.Image, size: tuple[int, int], mode: str, algorithm: int) -> Image.Image:
if mode == "fit":
out = img.copy()
out.thumbnail(size, algorithm)
return out
if mode == "fill":
return ImageOps.fit(img, size, method=algorithm, centering=(0.5, 0.5))
return img.resize(size, algorithm)
def apply_padding(img: Image.Image, padding: int, border_color: str) -> Image.Image:
if padding <= 0:
return img
return ImageOps.expand(img, border=padding, fill=border_color)
def process_one(path: Path, args: argparse.Namespace) -> dict:
t0 = time.perf_counter()
with Image.open(path) as img:
original = img.size
algorithm = RESAMPLE.get(args.algorithm, Image.Resampling.LANCZOS)
mode = args.mode
if mode not in MODES:
raise ValueError("Invalid mode. Use fit|fill|stretch.")
if args.size:
size = parse_size(args.size)
elif args.scale:
scale = float(args.scale) / 100.0
if scale <= 0:
raise ValueError("Scale must be > 0.")
size = (max(1, int(img.width * scale)), max(1, int(img.height * scale)))
else:
size = img.size
out_img = fit_mode(img, size, mode, algorithm)
out_img = apply_padding(out_img, int(args.padding or 0), args.border_color)
out_dir = output_dir(args)
out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / path.name
out_img.save(out_path)
ms = (time.perf_counter() - t0) * 1000.0
return {
"input": str(path.resolve()),
"output": str(out_path),
"original_dimensions": f"{original[0]}x{original[1]}",
"new_dimensions": f"{out_img.width}x{out_img.height}",
"algorithm": args.algorithm,
"mode": mode,
"processing_ms": round(ms, 2),
}
def make_thumbs(path: Path, args: argparse.Namespace) -> list[dict]:
sizes = []
if args.thumbnails:
sizes.extend([(150, 150), (300, 300), (600, 600)])
sizes.extend(parse_thumb_sizes(args.thumb_sizes))
if not sizes:
return []
out = []
with Image.open(path) as img:
algorithm = RESAMPLE.get(args.algorithm, Image.Resampling.LANCZOS)
out_dir = output_dir(args)
out_dir.mkdir(parents=True, exist_ok=True)
for w, h in sizes:
t0 = time.perf_counter()
thumb = ImageOps.fit(img, (w, h), method=algorithm, centering=(0.5, 0.5))
out_path = out_dir / f"{path.stem}_{w}x{h}{path.suffix}"
thumb.save(out_path)
ms = (time.perf_counter() - t0) * 1000.0
out.append({
"input": str(path.resolve()),
"output": str(out_path),
"original_dimensions": f"{img.width}x{img.height}",
"new_dimensions": f"{w}x{h}",
"algorithm": args.algorithm,
"mode": "thumbnail",
"processing_ms": round(ms, 2),
})
return out
def print_row(r: dict) -> None:
print(f"Input: {r['input']}")
print(f"Output: {r['output']}")
print(f"Dimensions: {r['original_dimensions']} -> {r['new_dimensions']}")
print(f"Algorithm: {r['algorithm']} | Mode: {r['mode']}")
print(f"Processing time: {r['processing_ms']} ms")
print()
def iter_images(root: Path, recursive: bool) -> Iterable[Path]:
if recursive:
yield from [p for p in root.rglob("*") if p.is_file() and p.suffix.lower() in SUPPORTED_EXTS]
else:
yield from [p for p in root.iterdir() if p.is_file() and p.suffix.lower() in SUPPORTED_EXTS]
def generate_sample() -> Path:
out = Path("sample_2000x1500.png").resolve()
w, h = 2000, 1500
img = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(img)
for y in range(h):
r = int(255 * y / h)
b = 255 - r
draw.line((0, y, w, y), fill=(r, 80, b))
for x in range(0, w, 50):
draw.line((x, 0, x, h), fill=(255, 255, 255, 60))
for y in range(0, h, 50):
draw.line((0, y, w, y), fill=(0, 0, 0, 40))
draw.text((80, 80), "Resizer Test Card", fill=(255, 255, 255))
draw.text((80, 130), "Small text 1234567890 abcdefghijklmnopqrstuvwxyz", fill=(255, 255, 255))
img.save(out, format="PNG")
return out
def run_demo() -> int:
sample = generate_sample()
for algo in ("nearest", "bilinear", "bicubic", "lanczos"):
for mode in ("fit", "fill", "stretch"):
args = argparse.Namespace(
size="800x600", scale=None, algorithm=algo, mode=mode,
thumbnails=False, thumb_sizes=None, padding=0, border_color="#ffffff",
output="demo_resized"
)
print_row(process_one(sample, args))
return 0
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description="Image Resizer and Thumbnail Generator")
p.add_argument("input", nargs="?")
p.add_argument("--size")
p.add_argument("--scale", type=float)
p.add_argument("--algorithm", default="lanczos", choices=list(RESAMPLE.keys()))
p.add_argument("--mode", default="fit", choices=list(MODES))
p.add_argument("--thumbnails", action="store_true")
p.add_argument("--thumb-sizes")
p.add_argument("--padding", type=int, default=0)
p.add_argument("--border-color", default="#ffffff")
p.add_argument("--batch", action="store_true")
p.add_argument("--recursive", action="store_true")
p.add_argument("--output")
return p
def main() -> int:
args = build_parser().parse_args()
if not args.input and not args.batch:
return run_demo()
if not args.input:
raise ValueError("Input path required.")
input_path = Path(args.input).resolve()
if args.batch:
if not input_path.is_dir():
raise ValueError("--batch requires a directory input.")
for p in iter_images(input_path, args.recursive):
print_row(process_one(p, args))
for t in make_thumbs(p, args):
print_row(t)
return 0
if not input_path.is_file():
raise ValueError(f"Input file not found: {input_path}")
print_row(process_one(input_path, args))
for t in make_thumbs(input_path, args):
print_row(t)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(f"Error: {exc}")
raise SystemExit(1)