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] ```