Image Resizer and Thumbnail Generator (cpp, written by Codex)
envgap__codex__cpp-t1-19
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
OpenCV headers not found - missing target_include_directories
Not a benchmark task.
- It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #19 · read the task the agent was given
Codex wrote this cpp project from the task below. It does not run 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 C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.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.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22) project(image_resizer_thumbnail_generator_cpp VERSION 1.0.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) include(FetchContent) FetchContent_Declare( opencv URL https://github.com/opencv/opencv/archive/refs/tags/4.10.0.zip ) set(BUILD_LIST "core,imgproc,imgcodecs" CACHE STRING "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_TESTS OFF CACHE BOOL "" FORCE) set(BUILD_PERF_TESTS OFF CACHE BOOL "" FORCE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(opencv) add_executable(image_resizer src/main.cpp) target_link_libraries(image_resizer PRIVATE opencv_core opencv_imgproc opencv_imgcodecs)
README.md
# Image Resizer and Thumbnail Generator (C++) Resizes images with multiple interpolation algorithms, fit/fill/stretch modes, optional padding, and thumbnail generation with batch directory processing. ## Requirements - Ubuntu 22.04 - G++ 12+ - CMake 3.22+ ## Dependencies (Pinned) - OpenCV `4.10.0` (fetched by CMake with `FetchContent`) ## Build ```bash cmake -S . -B build cmake --build build -j ``` ## Run ```bash ./build/image_resizer input.png --size 1920x1080 --algorithm lanczos --mode fit ./build/image_resizer input.png --scale 50 --algorithm bicubic --mode stretch ./build/image_resizer input.png --thumbnails --thumb-sizes 64x64,128x128,256x256 ./build/image_resizer ./images --batch --recursive --size 800x600 --output ./resized ./build/image_resizer ``` ## Features - `--size WxH` and `--scale <percent>` - Algorithms: `nearest`, `bilinear`, `bicubic`, `lanczos` - Modes: `fit`, `fill`, `stretch` - Standard thumbnails via `--thumbnails`: `150x150`, `300x300`, `600x600` - Custom thumbnails via `--thumb-sizes 64x64,128x128,...` - Optional border via `--padding` and `--border-color #RRGGBB` - Batch processing via `--batch` with optional `--recursive` - Default output directory: `resized/` - No-arg demo creates `sample_2000x1500.png` and generates outputs for all algorithms/modes
src/main.cpp
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <chrono>
#include <cmath>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace fs = std::filesystem;
struct Size2D {
int width;
int height;
};
struct CliOptions {
std::map<std::string, std::string> kv;
std::vector<std::string> positional;
};
struct Result {
std::string input;
std::string output;
std::string original;
std::string resized;
std::string algorithm;
std::string mode;
double ms;
};
constexpr long long kMaxPixels = 120LL * 1000LL * 1000LL;
const std::set<std::string> kSupportedExts = {
".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff", ".webp"
};
CliOptions parseArgs(int argc, char** argv) {
CliOptions options;
for (int i = 1; i < argc; ++i) {
std::string token = argv[i];
if (token.rfind("--", 0) == 0) {
std::string key = token.substr(2);
if (i + 1 < argc) {
std::string next = argv[i + 1];
if (next.rfind("--", 0) != 0) {
options.kv[key] = next;
++i;
continue;
}
}
options.kv[key] = "true";
} else {
options.positional.push_back(token);
}
}
return options;
}
Size2D parseSize(const std::string& raw) {
const auto x = raw.find('x');
if (x == std::string::npos) {
throw std::invalid_argument("Invalid size format. Use WxH.");
}
int w = std::stoi(raw.substr(0, x));
int h = std::stoi(raw.substr(x + 1));
if (w <= 0 || h <= 0) {
throw std::invalid_argument("Size must be positive.");
}
return {w, h};
}
std::vector<Size2D> parseThumbSizes(const std::string& raw) {
std::vector<Size2D> sizes;
std::stringstream ss(raw);
std::string token;
while (std::getline(ss, token, ',')) {
if (!token.empty()) {
sizes.push_back(parseSize(token));
}
}
return sizes;
}
std::string lower(std::string value) {
for (char& c : value) {
if (c >= 'A' && c <= 'Z') {
c = static_cast<char>(c - 'A' + 'a');
}
}
return value;
}
int interpolationFromAlgorithm(const std::string& algorithm) {
const std::string algo = lower(algorithm);
if (algo == "nearest") return cv::INTER_NEAREST;
if (algo == "bilinear") return cv::INTER_LINEAR;
if (algo == "bicubic") return cv::INTER_CUBIC;
if (algo == "lanczos") return cv::INTER_LANCZOS4;
throw std::invalid_argument("Invalid --algorithm. Use nearest|bilinear|bicubic|lanczos.");
}
cv::Scalar parseHexColor(const std::string& value) {
std::string s = value;
if (s.empty()) s = "#ffffff";
if (s[0] == '#') s = s.substr(1);
if (s.size() != 6) {
throw std::invalid_argument("Invalid --border-color. Use #RRGGBB.");
}
const int r = std::stoi(s.substr(0, 2), nullptr, 16);
const int g = std::stoi(s.substr(2, 2), nullptr, 16);
const int b = std::stoi(s.substr(4, 2), nullptr, 16);
return cv::Scalar(b, g, r);
}
Size2D computeTargetSize(const cv::Mat& input, const CliOptions& options) {
if (options.kv.count("size")) {
return parseSize(options.kv.at("size"));
}
if (options.kv.count("scale")) {
const double scale = std::stod(options.kv.at("scale"));
if (scale <= 0.0) {
throw std::invalid_argument("Scale must be > 0.");
}
int w = std::max(1, static_cast<int>(std::llround(input.cols * (scale / 100.0))));
int h = std::max(1, static_cast<int>(std::llround(input.rows * (scale / 100.0))));
return {w, h};
}
return {input.cols, input.rows};
}
cv::Mat resizeWithMode(const cv::Mat& input, Size2D target, const std::string& mode, int interpolation) {
const std::string m = lower(mode);
if (m != "fit" && m != "fill" && m != "stretch") {
throw std::invalid_argument("Invalid --mode. Use fit|fill|stretch.");
}
if (m == "stretch") {
cv::Mat out;
cv::resize(input, out, cv::Size(target.width, target.height), 0, 0, interpolation);
return out;
}
const double sx = static_cast<double>(target.width) / static_cast<double>(input.cols);
const double sy = static_cast<double>(target.height) / static_cast<double>(input.rows);
const double scale = (m == "fit") ? std::min(sx, sy) : std::max(sx, sy);
const int rw = std::max(1, static_cast<int>(std::llround(input.cols * scale)));
const int rh = std::max(1, static_cast<int>(std::llround(input.rows * scale)));
cv::Mat resized;
cv::resize(input, resized, cv::Size(rw, rh), 0, 0, interpolation);
if (m == "fit") {
return resized;
}
const int x = std::max(0, (rw - target.width) / 2);
const int y = std::max(0, (rh - target.height) / 2);
const int cw = std::min(target.width, resized.cols - x);
const int ch = std::min(target.height, resized.rows - y);
cv::Rect roi(x, y, cw, ch);
return resized(roi).clone();
}
cv::Mat addPadding(const cv::Mat& image, int padding, const cv::Scalar& color) {
if (padding <= 0) {
return image;
}
cv::Mat out;
cv::copyMakeBorder(image, out, padding, padding, padding, padding, cv::BORDER_CONSTANT, color);
return out;
}
bool isSupportedImage(const fs::path& file) {
if (!file.has_extension()) return false;
std::string ext = lower(file.extension().string());
return kSupportedExts.count(ext) > 0;
}
std::vector<fs::path> collectImages(const fs::path& root, bool recursive) {
std::vector<fs::path> files;
if (!fs::exists(root) || !fs::is_directory(root)) {
throw std::invalid_argument("Batch path is not a directory: " + root.string());
}
if (recursive) {
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (entry.is_regular_file() && isSupportedImage(entry.path())) {
files.push_back(entry.path());
}
}
} else {
for (const auto& entry : fs::directory_iterator(root)) {
if (entry.is_regular_file() && isSupportedImage(entry.path())) {
files.push_back(entry.path());
}
}
}
return files;
}
fs::path outputDir(const CliOptions& options) {
auto it = options.kv.find("output");
if (it != options.kv.end()) {
return fs::absolute(it->second);
}
return fs::absolute("resized");
}
Result processOne(const fs::path& inputPath, const CliOptions& options) {
auto start = std::chrono::high_resolution_clock::now();
cv::Mat input = cv::imread(inputPath.string(), cv::IMREAD_UNCHANGED);
if (input.empty()) {
throw std::runtime_error("Unsupported or unreadable image: " + inputPath.string());
}
const long long pixels = static_cast<long long>(input.cols) * static_cast<long long>(input.rows);
if (pixels > kMaxPixels) {
throw std::runtime_error("Image too large for safe processing: " + inputPath.string());
}
const std::string algorithm = options.kv.count("algorithm") ? options.kv.at("algorithm") : "lanczos";
const std::string mode = options.kv.count("mode") ? options.kv.at("mode") : "fit";
const int interpolation = interpolationFromAlgorithm(algorithm);
const Size2D target = computeTargetSize(input, options);
cv::Mat out = resizeWithMode(input, target, mode, interpolation);
int padding = 0;
if (options.kv.count("padding")) {
padding = std::stoi(options.kv.at("padding"));
if (padding < 0) throw std::invalid_argument("Padding cannot be negative.");
}
const cv::Scalar borderColor = parseHexColor(options.kv.count("border-color") ? options.kv.at("border-color") : "#ffffff");
out = addPadding(out, padding, borderColor);
const fs::path dir = outputDir(options);
fs::create_directories(dir);
const fs::path outputPath = dir / inputPath.filename();
if (!cv::imwrite(outputPath.string(), out)) {
throw std::runtime_error("Failed to write output: " + outputPath.string());
}
auto end = std::chrono::high_resolution_clock::now();
const double ms = std::chrono::duration<double, std::milli>(end - start).count();
std::ostringstream orig;
orig << input.cols << "x" << input.rows;
std::ostringstream resz;
resz << out.cols << "x" << out.rows;
return {
fs::absolute(inputPath).string(),
outputPath.string(),
orig.str(),
resz.str(),
lower(algorithm),
lower(mode),
ms
};
}
std::vector<Result> generateThumbnails(const fs::path& inputPath, const CliOptions& options) {
std::vector<Size2D> sizes;
if (options.kv.count("thumbnails")) {
sizes.push_back({150, 150});
sizes.push_back({300, 300});
sizes.push_back({600, 600});
}
if (options.kv.count("thumb-sizes")) {
auto custom = parseThumbSizes(options.kv.at("thumb-sizes"));
sizes.insert(sizes.end(), custom.begin(), custom.end());
}
if (sizes.empty()) {
return {};
}
cv::Mat input = cv::imread(inputPath.string(), cv::IMREAD_UNCHANGED);
if (input.empty()) {
return {};
}
const std::string algorithm = options.kv.count("algorithm") ? options.kv.at("algorithm") : "lanczos";
const int interpolation = interpolationFromAlgorithm(algorithm);
const fs::path dir = outputDir(options);
fs::create_directories(dir);
std::vector<Result> results;
for (const auto& s : sizes) {
auto start = std::chrono::high_resolution_clock::now();
cv::Mat thumb = resizeWithMode(input, s, "fill", interpolation);
fs::path outPath = dir / (inputPath.stem().string() + "_" + std::to_string(s.width) + "x" +
std::to_string(s.height) + inputPath.extension().string());
if (!cv::imwrite(outPath.string(), thumb)) {
throw std::runtime_error("Failed to write thumbnail: " + outPath.string());
}
auto end = std::chrono::high_resolution_clock::now();
const double ms = std::chrono::duration<double, std::milli>(end - start).count();
std::ostringstream orig;
orig << input.cols << "x" << input.rows;
std::ostringstream resz;
resz << s.width << "x" << s.height;
results.push_back({
fs::absolute(inputPath).string(),
outPath.string(),
orig.str(),
resz.str(),
lower(algorithm),
"thumbnail",
ms
});
}
return results;
}
void printResult(const Result& r) {
std::cout << "Input: " << r.input << "\n";
std::cout << "Output: " << r.output << "\n";
std::cout << "Dimensions: " << r.original << " -> " << r.resized << "\n";
std::cout << "Algorithm: " << r.algorithm << " | Mode: " << r.mode << "\n";
std::cout << "Processing time: " << std::fixed << std::setprecision(2) << r.ms << " ms\n\n";
}
fs::path createSampleImage() {
const int width = 2000;
const int height = 1500;
cv::Mat sample(height, width, CV_8UC3);
for (int y = 0; y < height; ++y) {
const double t = static_cast<double>(y) / static_cast<double>(height - 1);
const int r = static_cast<int>((1.0 - t) * 40 + t * 245);
const int g = static_cast<int>((1.0 - t) * 55 + t * 130);
const int b = static_cast<int>((1.0 - t) * 240 + t * 130);
cv::line(sample, cv::Point(0, y), cv::Point(width - 1, y), cv::Scalar(b, g, r), 1, cv::LINE_AA);
}
for (int x = 0; x < width; x += 50) {
cv::line(sample, cv::Point(x, 0), cv::Point(x, height - 1), cv::Scalar(255, 255, 255), 1, cv::LINE_AA);
}
for (int y = 0; y < height; y += 50) {
cv::line(sample, cv::Point(0, y), cv::Point(width - 1, y), cv::Scalar(0, 0, 0), 1, cv::LINE_AA);
}
cv::putText(sample, "Resizer Test Card", cv::Point(80, 180), cv::FONT_HERSHEY_DUPLEX, 2.0, cv::Scalar(255, 255, 255), 3, cv::LINE_AA);
cv::putText(sample, "Medium text for clarity checks", cv::Point(80, 260), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255), 2, cv::LINE_AA);
cv::putText(sample, "Small text 1234567890 abcdefghijklmnopqrstuvwxyz", cv::Point(80, 320), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(255, 255, 255), 1, cv::LINE_AA);
fs::path out = fs::absolute("sample_2000x1500.png");
if (!cv::imwrite(out.string(), sample)) {
throw std::runtime_error("Failed to write sample image.");
}
return out;
}
void runDemo() {
fs::path sample = createSampleImage();
std::vector<std::string> algorithms = {"nearest", "bilinear", "bicubic", "lanczos"};
std::vector<std::string> modes = {"fit", "fill", "stretch"};
for (const auto& algorithm : algorithms) {
for (const auto& mode : modes) {
CliOptions opts;
opts.kv["size"] = "800x600";
opts.kv["algorithm"] = algorithm;
opts.kv["mode"] = mode;
opts.kv["output"] = "demo_resized";
printResult(processOne(sample, opts));
}
}
}
int main(int argc, char** argv) {
try {
CliOptions options = parseArgs(argc, argv);
const bool batchMode = options.kv.count("batch") > 0;
const bool recursive = options.kv.count("recursive") > 0;
if (!batchMode && options.positional.empty()) {
runDemo();
return 0;
}
if (batchMode) {
if (options.positional.empty()) {
throw std::invalid_argument("Batch mode requires a directory argument.");
}
fs::path root = fs::absolute(options.positional.front());
for (const auto& file : collectImages(root, recursive)) {
Result r = processOne(file, options);
printResult(r);
for (const auto& t : generateThumbnails(file, options)) {
printResult(t);
}
}
return 0;
}
fs::path input = fs::absolute(options.positional.front());
if (!fs::exists(input) || !fs::is_regular_file(input)) {
throw std::invalid_argument("Input file not found: " + input.string());
}
Result r = processOne(input, options);
printResult(r);
for (const auto& t : generateThumbnails(input, options)) {
printResult(t);
}
return 0;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 1;
}
}