← All tasks
cppclaude-code/cpp-t3 #21Not a task: not reproduced

Image Histogram Analyzer (cpp, written by Claude Code)

envgap__claude-code__cpp-t3-21

Written by a coding agent; not on GitHubWritten 2026-02-28

01 / FAILURE SIGNATURE

As the study recorded it

png.h: No such file or directory
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

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

claude-code/cpp-t3 #21 · read the task the agent was given
Claude Code 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 Histogram Analyzer

Write a program that computes and analyzes color histograms of images, providing statistical analysis of color distribution, channel comparisons, and similarity scoring between images.

FUNCTIONAL REQUIREMENTS:
- Accept an image file path as a command-line argument
- Compute per-channel histograms (Red, Green, Blue) with 256 bins each, plus a luminance/grayscale histogram
- Calculate statistics for each channel: mean intensity, median, standard deviation, skewness, dominant intensity ranges, and dynamic range (difference between darkest and brightest used values)
- Detect if an image is overexposed (high mean, clipped highlights), underexposed (low mean, clipped shadows), or low contrast (narrow histogram spread)
- Support histogram comparison between two images via --compare flag: compute correlation, chi-squared distance, intersection, and Bhattacharyya distance between their histograms
- Support cumulative histogram computation for each channel via --cumulative flag
- Generate a histogram data output as a CSV file with columns (bin, red_count, green_count, blue_count, luminance_count) via --export flag
- Support analyzing specific regions of an image via --crop flag (x,y,width,height)
- Print a text-based summary to console: per-channel statistics, exposure assessment, contrast assessment, and color balance analysis
- Save the full analysis as JSON with --output flag (default: histogram_analysis.json)
- Support batch analysis of multiple images via --batch flag with a summary comparison table
- If no input is given, generate three sample images (one overexposed, one underexposed, one well-balanced), analyze each, and display comparative results
- Handle errors: unsupported image formats, corrupted files, grayscale images (single-channel analysis)

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.16)
project(HistogramAnalyzer VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# Fetch CImg
FetchContent_Declare(
    cimg
    GIT_REPOSITORY https://github.com/GreycLab/CImg.git
    GIT_TAG v.3.3.3
)
FetchContent_MakeAvailable(cimg)

# Fetch RapidJSON
FetchContent_Declare(
    rapidjson
    GIT_REPOSITORY https://github.com/Tencent/rapidjson.git
    GIT_TAG v1.1.0
)
FetchContent_MakeAvailable(rapidjson)

# Find image libraries for CImg support
find_package(PNG QUIET)
find_package(JPEG QUIET)

# Main executable
add_executable(histogram_analyzer main.cpp)

target_include_directories(histogram_analyzer PRIVATE
    ${cimg_SOURCE_DIR}
    ${rapidjson_SOURCE_DIR}/include
)

# Link image format libraries if available
if(PNG_FOUND)
    target_link_libraries(histogram_analyzer PRIVATE PNG::PNG)
    target_compile_definitions(histogram_analyzer PRIVATE cimg_use_png)
endif()

if(JPEG_FOUND)
    target_link_libraries(histogram_analyzer PRIVATE JPEG::JPEG)
    target_compile_definitions(histogram_analyzer PRIVATE cimg_use_jpeg)
endif()

target_compile_definitions(histogram_analyzer PRIVATE cimg_display=0)

# Installation
install(TARGETS histogram_analyzer DESTINATION bin)
main.cpp
/**
 * Image Histogram Analyzer
 * Computes per-channel color histograms, detects exposure issues,
 * and compares histograms between images.
 *
 * Dependencies: CImg 3.3.3, rapidjson 1.1.0
 */

#define cimg_display 0
#define cimg_use_png
#define cimg_use_jpeg
#include "CImg.h"

#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <iomanip>
#include <algorithm>
#include <stdexcept>
#include <cstring>

using namespace cimg_library;
using namespace rapidjson;

struct ChannelHistogram {
    int red[256] = {0};
    int green[256] = {0};
    int blue[256] = {0};
    int luminance[256] = {0};
    int totalPixels = 0;
    int width = 0;
    int height = 0;
};

struct ChannelStats {
    double mean;
    int median;
    int mode;
    double stdDev;
    int totalPixels;
};

struct ExposureAnalysis {
    std::string severity;
    std::vector<std::string> issues;
    double shadowFraction;
    double midtoneFraction;
    double highlightFraction;
    double shadowClipping;
    double highlightClipping;
};

struct ComparisonMetrics {
    double correlation;
    double chiSquared;
    double bhattacharyya;
    double earthMoversDistance;
    double intersection;
};

/**
 * Compute per-channel color histograms for an image using CImg.
 */
ChannelHistogram computeHistogram(const std::string& imagePath) {
    CImg<unsigned char> image(imagePath.c_str());

    ChannelHistogram result;
    result.width = image.width();
    result.height = image.height();
    result.totalPixels = result.width * result.height;

    int spectrum = image.spectrum();

    for (int y = 0; y < result.height; y++) {
        for (int x = 0; x < result.width; x++) {
            unsigned char r = image(x, y, 0, 0);
            unsigned char g = (spectrum >= 3) ? image(x, y, 0, 1) : r;
            unsigned char b = (spectrum >= 3) ? image(x, y, 0, 2) : r;

            result.red[r]++;
            result.green[g]++;
            result.blue[b]++;

            int luminance = static_cast<int>(0.2126 * r + 0.7152 * g + 0.0722 * b);
            luminance = std::min(255, std::max(0, luminance));
            result.luminance[luminance]++;
        }
    }

    return result;
}

/**
 * Compute statistical measures for a histogram.
 */
ChannelStats computeStatistics(const int* hist, int totalPixels) {
    ChannelStats stats;
    stats.totalPixels = totalPixels;

    // Mean
    double mean = 0;
    for (int i = 0; i < 256; i++) {
        mean += static_cast<double>(i) * hist[i];
    }
    mean /= totalPixels;
    stats.mean = mean;

    // Variance and standard deviation
    double variance = 0;
    for (int i = 0; i < 256; i++) {
        variance += std::pow(i - mean, 2) * hist[i];
    }
    variance /= totalPixels;
    stats.stdDev = std::sqrt(variance);

    // Median
    double cumulative = 0;
    stats.median = 0;
    for (int i = 0; i < 256; i++) {
        cumulative += hist[i];
        if (cumulative >= totalPixels / 2.0) {
            stats.median = i;
            break;
        }
    }

    // Mode
    stats.mode = 0;
    int maxCount = 0;
    for (int i = 0; i < 256; i++) {
        if (hist[i] > maxCount) {
            maxCount = hist[i];
            stats.mode = i;
        }
    }

    return stats;
}

/**
 * Detect exposure issues based on luminance histogram.
 */
ExposureAnalysis detectExposureIssues(const int* lumHist, int totalPixels,
                                       double threshold = 0.25) {
    ExposureAnalysis result;
    result.severity = "normal";

    double shadowPixels = 0, midtonePixels = 0, highlightPixels = 0;
    double shadowClipping = 0, highlightClipping = 0;

    for (int i = 0; i < 256; i++) {
        double fraction = static_cast<double>(lumHist[i]) / totalPixels;
        if (i < 64) shadowPixels += fraction;
        else if (i < 192) midtonePixels += fraction;
        else highlightPixels += fraction;

        if (i < 5) shadowClipping += fraction;
        if (i >= 251) highlightClipping += fraction;
    }

    result.shadowFraction = shadowPixels;
    result.midtoneFraction = midtonePixels;
    result.highlightFraction = highlightPixels;
    result.shadowClipping = shadowClipping;
    result.highlightClipping = highlightClipping;

    if (shadowPixels > threshold && highlightPixels < 0.1) {
        result.issues.push_back("Image appears underexposed (heavy shadows)");
        result.severity = "underexposed";
    }
    if (highlightPixels > threshold && shadowPixels < 0.1) {
        result.issues.push_back("Image appears overexposed (heavy highlights)");
        result.severity = "overexposed";
    }
    if (shadowClipping > 0.05) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Shadow clipping detected (%.1f%% of pixels)",
                 shadowClipping * 100);
        result.issues.push_back(buf);
    }
    if (highlightClipping > 0.05) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Highlight clipping detected (%.1f%% of pixels)",
                 highlightClipping * 100);
        result.issues.push_back(buf);
    }

    // Contrast check
    double mean = 0;
    for (int i = 0; i < 256; i++) {
        mean += static_cast<double>(i) * lumHist[i];
    }
    mean /= totalPixels;

    double variance = 0;
    for (int i = 0; i < 256; i++) {
        variance += std::pow(i - mean, 2) * lumHist[i];
    }
    variance /= totalPixels;
    double stdDev = std::sqrt(variance);

    if (stdDev < 30) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Low contrast detected (std dev: %.1f)", stdDev);
        result.issues.push_back(buf);
        if (result.severity == "normal") result.severity = "low_contrast";
    }
    if (stdDev > 80) {
        char buf[128];
        snprintf(buf, sizeof(buf), "High contrast detected (std dev: %.1f)", stdDev);
        result.issues.push_back(buf);
        if (result.severity == "normal") result.severity = "high_contrast";
    }

    if (result.issues.empty()) {
        result.issues.push_back("No exposure issues detected");
    }

    return result;
}

/**
 * Compare two histograms using multiple metrics.
 */
ComparisonMetrics compareHistograms(const int* h1, const int* h2,
                                     int total1, int total2) {
    ComparisonMetrics metrics;

    double h1Norm[256], h2Norm[256];
    for (int i = 0; i < 256; i++) {
        h1Norm[i] = static_cast<double>(h1[i]) / (total1 + 1e-10);
        h2Norm[i] = static_cast<double>(h2[i]) / (total2 + 1e-10);
    }

    // Correlation
    double mean1 = 0, mean2 = 0;
    for (int i = 0; i < 256; i++) {
        mean1 += h1Norm[i];
        mean2 += h2Norm[i];
    }
    mean1 /= 256;
    mean2 /= 256;

    double num = 0, denom1 = 0, denom2 = 0;
    for (int i = 0; i < 256; i++) {
        double d1 = h1Norm[i] - mean1;
        double d2 = h2Norm[i] - mean2;
        num += d1 * d2;
        denom1 += d1 * d1;
        denom2 += d2 * d2;
    }
    metrics.correlation = num / (std::sqrt(denom1 * denom2) + 1e-10);

    // Chi-squared
    metrics.chiSquared = 0;
    for (int i = 0; i < 256; i++) {
        double diff = h1Norm[i] - h2Norm[i];
        metrics.chiSquared += diff * diff / (h1Norm[i] + h2Norm[i] + 1e-10);
    }

    // Bhattacharyya
    double bc = 0;
    for (int i = 0; i < 256; i++) {
        bc += std::sqrt(h1Norm[i] * h2Norm[i]);
    }
    metrics.bhattacharyya = -std::log(bc + 1e-10);

    // Earth Mover's Distance
    metrics.earthMoversDistance = 0;
    double cdf1 = 0, cdf2 = 0;
    for (int i = 0; i < 256; i++) {
        cdf1 += h1Norm[i];
        cdf2 += h2Norm[i];
        metrics.earthMoversDistance += std::abs(cdf1 - cdf2);
    }

    // Intersection
    metrics.intersection = 0;
    for (int i = 0; i < 256; i++) {
        metrics.intersection += std::min(h1Norm[i], h2Norm[i]);
    }

    return metrics;
}

/**
 * Print analysis report to console.
 */
void printReport(const std::string& imagePath, const ChannelHistogram& histData) {
    std::cout << "\n" << std::string(60, '=') << std::endl;
    std::cout << "Image Histogram Analysis Report" << std::endl;
    std::cout << "File: " << imagePath << std::endl;
    std::cout << "Size: " << histData.width << "x" << histData.height << std::endl;
    std::cout << std::string(60, '=') << std::endl;

    std::cout << "\nChannel Statistics:" << std::endl;
    std::cout << std::left << std::setw(12) << "Channel"
              << std::right << std::setw(8) << "Mean"
              << std::setw(8) << "Median"
              << std::setw(8) << "Mode"
              << std::setw(8) << "StdDev" << std::endl;
    std::cout << std::string(48, '-') << std::endl;

    const int* hists[] = {histData.red, histData.green, histData.blue, histData.luminance};
    const char* names[] = {"Red", "Green", "Blue", "Luminance"};

    for (int ch = 0; ch < 4; ch++) {
        ChannelStats stats = computeStatistics(hists[ch], histData.totalPixels);
        std::cout << std::left << std::setw(12) << names[ch]
                  << std::right << std::fixed << std::setprecision(1)
                  << std::setw(8) << stats.mean
                  << std::setw(8) << stats.median
                  << std::setw(8) << stats.mode
                  << std::setw(8) << stats.stdDev << std::endl;
    }

    ExposureAnalysis exposure = detectExposureIssues(histData.luminance, histData.totalPixels);

    std::cout << "\nExposure Analysis:" << std::endl;
    std::cout << "  Severity: " << exposure.severity << std::endl;
    for (const auto& issue : exposure.issues) {
        std::cout << "  - " << issue << std::endl;
    }
    std::cout << std::fixed << std::setprecision(1);
    std::cout << "  Shadows:    " << exposure.shadowFraction * 100
              << "% (clipping: " << exposure.shadowClipping * 100 << "%)" << std::endl;
    std::cout << "  Midtones:   " << exposure.midtoneFraction * 100 << "%" << std::endl;
    std::cout << "  Highlights: " << exposure.highlightFraction * 100
              << "% (clipping: " << exposure.highlightClipping * 100 << "%)" << std::endl;
}

/**
 * Export analysis to JSON using rapidjson.
 */
void exportAnalysisToJson(const std::string& imagePath,
                           const ChannelHistogram& histData,
                           const std::string& outputPath) {
    Document doc;
    doc.SetObject();
    auto& alloc = doc.GetAllocator();

    doc.AddMember("file", Value(imagePath.c_str(), alloc), alloc);
    doc.AddMember("width", histData.width, alloc);
    doc.AddMember("height", histData.height, alloc);
    doc.AddMember("total_pixels", histData.totalPixels, alloc);

    const int* hists[] = {histData.red, histData.green, histData.blue, histData.luminance};
    const char* names[] = {"Red", "Green", "Blue", "Luminance"};

    Value statistics(kObjectType);
    for (int ch = 0; ch < 4; ch++) {
        ChannelStats stats = computeStatistics(hists[ch], histData.totalPixels);
        Value chObj(kObjectType);
        chObj.AddMember("mean", stats.mean, alloc);
        chObj.AddMember("median", stats.median, alloc);
        chObj.AddMember("mode", stats.mode, alloc);
        chObj.AddMember("std_dev", stats.stdDev, alloc);
        statistics.AddMember(Value(names[ch], alloc), chObj, alloc);
    }
    doc.AddMember("statistics", statistics, alloc);

    ExposureAnalysis exposure = detectExposureIssues(histData.luminance, histData.totalPixels);
    Value exposureObj(kObjectType);
    exposureObj.AddMember("severity", Value(exposure.severity.c_str(), alloc), alloc);

    Value issuesArr(kArrayType);
    for (const auto& issue : exposure.issues) {
        issuesArr.PushBack(Value(issue.c_str(), alloc), alloc);
    }
    exposureObj.AddMember("issues", issuesArr, alloc);
    exposureObj.AddMember("shadow_fraction", exposure.shadowFraction, alloc);
    exposureObj.AddMember("midtone_fraction", exposure.midtoneFraction, alloc);
    exposureObj.AddMember("highlight_fraction", exposure.highlightFraction, alloc);
    doc.AddMember("exposure", exposureObj, alloc);

    StringBuffer buffer;
    PrettyWriter<StringBuffer> writer(buffer);
    doc.Accept(writer);

    if (!outputPath.empty()) {
        std::ofstream outFile(outputPath);
        outFile << buffer.GetString();
        std::cout << "JSON saved to: " << outputPath << std::endl;
    } else {
        std::cout << buffer.GetString() << std::endl;
    }
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        std::cout << "Image Histogram Analyzer" << std::endl;
        std::cout << "Usage:" << std::endl;
        std::cout << "  " << argv[0] << " analyze <image> [--json output.json]" << std::endl;
        std::cout << "  " << argv[0] << " compare <image1> <image2> [--json output.json]" << std::endl;
        return 0;
    }

    std::string command = argv[1];

    try {
        if (command == "analyze") {
            std::string imagePath = argv[2];
            ChannelHistogram histData = computeHistogram(imagePath);

            std::string jsonOutput;
            for (int i = 3; i < argc - 1; i++) {
                if (std::string(argv[i]) == "--json") {
                    jsonOutput = argv[i + 1];
                    break;
                }
            }

            if (!jsonOutput.empty()) {
                exportAnalysisToJson(imagePath, histData, jsonOutput);
            } else {
                printReport(imagePath, histData);
            }

        } else if (command == "compare") {
            if (argc < 4) {
                std::cerr << "Error: compare requires two image paths" << std::endl;
                return 1;
            }

            std::string imagePath1 = argv[2];
            std::string imagePath2 = argv[3];

            ChannelHistogram hist1 = computeHistogram(imagePath1);
            ChannelHistogram hist2 = computeHistogram(imagePath2);

            printReport(imagePath1, hist1);
            printReport(imagePath2, hist2);

            std::cout << "\n" << std::string(60, '=') << std::endl;
            std::cout << "Histogram Comparison Metrics" << std::endl;
            std::cout << std::string(60, '=') << std::endl;

            const int* h1s[] = {hist1.red, hist1.green, hist1.blue, hist1.luminance};
            const int* h2s[] = {hist2.red, hist2.green, hist2.blue, hist2.luminance};
            const char* names[] = {"Red", "Green", "Blue", "Luminance"};

            for (int ch = 0; ch < 4; ch++) {
                ComparisonMetrics metrics = compareHistograms(
                    h1s[ch], h2s[ch], hist1.totalPixels, hist2.totalPixels);

                std::cout << "\n  " << names[ch] << ":" << std::endl;
                std::cout << std::fixed << std::setprecision(4);
                std::cout << "    Correlation:           " << metrics.correlation << std::endl;
                std::cout << "    Chi-Squared Distance:  " << metrics.chiSquared << std::endl;
                std::cout << "    Bhattacharyya Dist:    " << metrics.bhattacharyya << std::endl;
                std::cout << "    Earth Mover's Dist:    " << metrics.earthMoversDistance << std::endl;
                std::cout << "    Intersection:          " << metrics.intersection << std::endl;
            }

            // Export comparison JSON if requested
            for (int i = 4; i < argc - 1; i++) {
                if (std::string(argv[i]) == "--json") {
                    Document doc;
                    doc.SetObject();
                    auto& alloc = doc.GetAllocator();

                    Value compObj(kObjectType);
                    for (int ch = 0; ch < 4; ch++) {
                        ComparisonMetrics metrics = compareHistograms(
                            h1s[ch], h2s[ch], hist1.totalPixels, hist2.totalPixels);

                        Value chObj(kObjectType);
                        chObj.AddMember("correlation", metrics.correlation, alloc);
                        chObj.AddMember("chi_squared", metrics.chiSquared, alloc);
                        chObj.AddMember("bhattacharyya", metrics.bhattacharyya, alloc);
                        chObj.AddMember("earth_movers_distance", metrics.earthMoversDistance, alloc);
                        chObj.AddMember("intersection", metrics.intersection, alloc);
                        compObj.AddMember(Value(names[ch], alloc), chObj, alloc);
                    }
                    doc.AddMember("comparison", compObj, alloc);

                    StringBuffer buffer;
                    PrettyWriter<StringBuffer> writer(buffer);
                    doc.Accept(writer);

                    std::ofstream outFile(argv[i + 1]);
                    outFile << buffer.GetString();
                    std::cout << "\nJSON saved to: " << argv[i + 1] << std::endl;
                    break;
                }
            }

        } else {
            std::cerr << "Unknown command: " << command << std::endl;
            return 1;
        }
    } catch (const CImgIOException& e) {
        std::cerr << "Image I/O Error: " << e.what() << std::endl;
        return 1;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
README.md
# Image Histogram Analyzer (C++ - CImg / RapidJSON)

Computes per-channel color histograms, detects exposure issues, and compares histograms between images.

## Dependencies
- CImg 3.3.3
- rapidjson 1.1.0

## Build
```bash
mkdir build && cd build
cmake ..
make
```

## Usage
```bash
./histogram_analyzer analyze <image> [--json output.json]
./histogram_analyzer compare <image1> <image2> [--json output.json]
```