Image Resizer and Thumbnail Generator (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-19
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
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
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/javascript-t1 #19 · read the task the agent was given
Claude Code wrote this javascript 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 JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json 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.
package.json
{
"name": "image-resizer",
"version": "1.0.0",
"description": "Image Resizer and Thumbnail Generator - resizes images with multiple algorithms, crop modes, and batch processing",
"main": "resizer.js",
"bin": {
"image-resizer": "resizer.js"
},
"scripts": {
"start": "node resizer.js",
"resize": "node resizer.js resize",
"thumbnail": "node resizer.js thumbnail",
"batch": "node resizer.js batch",
"batch-thumbnails": "node resizer.js batch-thumbnails"
},
"keywords": [
"image",
"resize",
"thumbnail",
"sharp",
"batch",
"lanczos",
"bicubic",
"bilinear"
],
"license": "MIT",
"dependencies": {
"sharp": "0.33.2"
}
}
README.md
# Image Resizer and Thumbnail Generator (JavaScript / sharp)
A Node.js command-line tool for resizing images and generating thumbnails using
the sharp library with multiple resampling algorithms, crop modes, and batch processing.
## Dependencies
- **sharp 0.33.2** - High-performance image processing library built on libvips.
## Installation
```bash
npm install
```
## Usage
### Resize a single image
```bash
node resizer.js resize input.jpg output.jpg 800 600 -a lanczos
```
### Generate a thumbnail
```bash
node resizer.js thumbnail input.jpg thumb.jpg -s 150
```
### Batch resize
```bash
node resizer.js batch ./input_dir ./output_dir 1024 768 -a bicubic
```
### Batch thumbnails
```bash
node resizer.js batch-thumbnails ./input_dir ./thumbs -s 200
```
## Algorithms
| Algorithm | Description |
|------------|------------------------------------|
| `nearest` | Nearest-neighbor (fastest) |
| `bilinear` | Bilinear interpolation |
| `bicubic` | Bicubic interpolation |
| `lanczos` | Lanczos3 resampling (best quality) |
## Crop Modes
| Mode | Description |
|----------------|------------------------------------------|
| `center` | Crop from the center |
| `top-left` | Crop from the top-left corner |
| `top-right` | Crop from the top-right corner |
| `bottom-left` | Crop from the bottom-left corner |
| `bottom-right` | Crop from the bottom-right corner |
| `smart` | Content-aware smart crop |
## Programmatic Usage
```javascript
const { resizeImage, generateThumbnail } = require('./resizer');
await resizeImage({
inputPath: 'photo.jpg',
outputPath: 'resized.jpg',
width: 800,
height: 600,
algorithm: 'lanczos',
cropMode: 'center',
});
```
resizer.js
#!/usr/bin/env node
/**
* Image Resizer and Thumbnail Generator
*
* Resizes images using multiple algorithms (bilinear, bicubic, Lanczos),
* supports crop modes, and batch processing of directories.
*
* Dependencies: sharp 0.33.2
*/
const sharp = require("sharp");
const path = require("path");
const fs = require("fs");
// Supported image extensions
const SUPPORTED_EXTENSIONS = new Set([
".jpg",
".jpeg",
".png",
".bmp",
".tiff",
".tif",
".webp",
".gif",
]);
// Algorithm mapping to sharp kernel options
const ALGORITHMS = {
nearest: sharp.kernel.nearest,
bilinear: sharp.kernel.cubic, // sharp uses cubic as bilinear equivalent
bicubic: sharp.kernel.cubic,
lanczos: sharp.kernel.lanczos3,
};
// Crop position mapping for sharp gravity
const CROP_POSITIONS = {
center: sharp.gravity.centre,
"top-left": sharp.gravity.northwest,
"top-right": sharp.gravity.northeast,
"bottom-left": sharp.gravity.southwest,
"bottom-right": sharp.gravity.southeast,
smart: sharp.strategy.attention,
};
/**
* Get all supported image files from a directory.
* @param {string} dirPath - Directory to scan.
* @returns {string[]} Sorted array of image file paths.
*/
function getSupportedImages(dirPath) {
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
throw new Error(`Not a directory: ${dirPath}`);
}
const files = fs.readdirSync(dirPath);
return files
.filter((f) => SUPPORTED_EXTENSIONS.has(path.extname(f).toLowerCase()))
.sort()
.map((f) => path.join(dirPath, f));
}
/**
* Format bytes into human-readable size.
* @param {number} bytes - File size in bytes.
* @returns {string} Formatted size string.
*/
function formatSize(bytes) {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
/**
* Resize a single image.
*
* @param {Object} options - Resize options.
* @param {string} options.inputPath - Path to source image.
* @param {string} options.outputPath - Path for output image.
* @param {number} options.width - Target width.
* @param {number} options.height - Target height.
* @param {string} [options.algorithm='lanczos'] - Resampling algorithm.
* @param {string} [options.cropMode=null] - Crop mode for exact dimensions.
* @param {boolean} [options.maintainAspect=true] - Keep aspect ratio.
* @param {number} [options.quality=90] - Output quality (1-100).
* @returns {Promise<Object>} Result with dimensions and sizes.
*/
async function resizeImage({
inputPath,
outputPath,
width,
height,
algorithm = "lanczos",
cropMode = null,
maintainAspect = true,
quality = 90,
}) {
if (!ALGORITHMS[algorithm]) {
throw new Error(
`Unsupported algorithm '${algorithm}'. Choose from: ${Object.keys(ALGORITHMS).join(", ")}`
);
}
const inputStat = fs.statSync(inputPath);
const originalSize = inputStat.size;
const metadata = await sharp(inputPath).metadata();
const origWidth = metadata.width;
const origHeight = metadata.height;
let pipeline = sharp(inputPath);
if (cropMode) {
// Resize to cover the target area, then crop
const position = CROP_POSITIONS[cropMode];
if (!position) {
throw new Error(
`Unknown crop mode '${cropMode}'. Choose from: ${Object.keys(CROP_POSITIONS).join(", ")}`
);
}
pipeline = pipeline.resize(width, height, {
fit: "cover",
position: position,
kernel: ALGORITHMS[algorithm],
});
} else if (maintainAspect) {
pipeline = pipeline.resize(width, height, {
fit: "inside",
withoutEnlargement: false,
kernel: ALGORITHMS[algorithm],
});
} else {
pipeline = pipeline.resize(width, height, {
fit: "fill",
kernel: ALGORITHMS[algorithm],
});
}
// Set output quality based on format
const ext = path.extname(outputPath).toLowerCase();
if (ext === ".jpg" || ext === ".jpeg") {
pipeline = pipeline.jpeg({ quality, mozjpeg: true });
} else if (ext === ".png") {
pipeline = pipeline.png({ compressionLevel: 6 });
} else if (ext === ".webp") {
pipeline = pipeline.webp({ quality });
}
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
fs.mkdirSync(outputDir, { recursive: true });
const outputInfo = await pipeline.toFile(outputPath);
const newSize = fs.statSync(outputPath).size;
return {
input: inputPath,
output: outputPath,
originalDimensions: { width: origWidth, height: origHeight },
newDimensions: { width: outputInfo.width, height: outputInfo.height },
originalSize,
newSize,
algorithm,
};
}
/**
* Generate a square thumbnail from an image.
*/
async function generateThumbnail({
inputPath,
outputPath,
size = 150,
algorithm = "lanczos",
quality = 85,
}) {
return resizeImage({
inputPath,
outputPath,
width: size,
height: size,
algorithm,
cropMode: "center",
quality,
});
}
/**
* Batch resize all images in a directory.
*/
async function batchResize({
inputDir,
outputDir,
width,
height,
algorithm = "lanczos",
cropMode = null,
maintainAspect = true,
quality = 90,
outputFormat = null,
}) {
const images = getSupportedImages(inputDir);
if (images.length === 0) {
console.log("No supported images found in the input directory.");
return [];
}
console.log(`Found ${images.length} image(s) to process.`);
const results = [];
let successCount = 0;
let errorCount = 0;
for (const imgPath of images) {
try {
let outName = path.basename(imgPath);
if (outputFormat) {
outName =
path.basename(imgPath, path.extname(imgPath)) + "." + outputFormat;
}
const outPath = path.join(outputDir, outName);
const result = await resizeImage({
inputPath: imgPath,
outputPath: outPath,
width,
height,
algorithm,
cropMode,
maintainAspect,
quality,
});
results.push(result);
successCount++;
console.log(
` Resized: ${path.basename(imgPath)} ` +
`(${result.originalDimensions.width}x${result.originalDimensions.height} -> ` +
`${result.newDimensions.width}x${result.newDimensions.height})`
);
} catch (err) {
errorCount++;
console.error(` Error: ${path.basename(imgPath)}: ${err.message}`);
}
}
console.log(`\nDone! ${successCount} succeeded, ${errorCount} failed.`);
return results;
}
/**
* Batch generate thumbnails for all images in a directory.
*/
async function batchThumbnails({
inputDir,
outputDir,
size = 150,
algorithm = "lanczos",
quality = 85,
}) {
const images = getSupportedImages(inputDir);
if (images.length === 0) {
console.log("No supported images found.");
return [];
}
console.log(`Generating thumbnails for ${images.length} image(s)...`);
const results = [];
for (const imgPath of images) {
try {
const outPath = path.join(
outputDir,
"thumb_" + path.basename(imgPath)
);
const result = await generateThumbnail({
inputPath: imgPath,
outputPath: outPath,
size,
algorithm,
quality,
});
results.push(result);
console.log(
` Thumbnail: ${path.basename(imgPath)} ` +
`(${result.newDimensions.width}x${result.newDimensions.height})`
);
} catch (err) {
console.error(` Error: ${path.basename(imgPath)}: ${err.message}`);
}
}
console.log(`\nGenerated ${results.length} thumbnail(s) in ${outputDir}`);
return results;
}
// ============================================================================
// CLI
// ============================================================================
function printUsage() {
console.log(`Image Resizer and Thumbnail Generator
Usage:
node resizer.js resize <input> <output> <width> <height> [options]
node resizer.js thumbnail <input> <output> [options]
node resizer.js batch <input-dir> <output-dir> <width> <height> [options]
node resizer.js batch-thumbnails <input-dir> <output-dir> [options]
Algorithms: nearest, bilinear, bicubic, lanczos (default)
Crop modes: center, top-left, top-right, bottom-left, bottom-right, smart
Options:
--algorithm, -a Resampling algorithm (default: lanczos)
--crop, -c Crop mode for exact dimensions
--quality, -q Output quality 1-100 (default: 90)
--size, -s Thumbnail size (default: 150)
--format, -f Output format (jpg, png, webp)
--no-aspect Stretch to exact dimensions
`);
}
function parseArgs(args) {
const opts = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "--algorithm" || arg === "-a") opts.algorithm = args[++i];
else if (arg === "--crop" || arg === "-c") opts.cropMode = args[++i];
else if (arg === "--quality" || arg === "-q")
opts.quality = parseInt(args[++i]);
else if (arg === "--size" || arg === "-s") opts.size = parseInt(args[++i]);
else if (arg === "--format" || arg === "-f")
opts.outputFormat = args[++i];
else if (arg === "--no-aspect") opts.maintainAspect = false;
}
return opts;
}
async function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
printUsage();
process.exit(0);
}
const command = args[0];
const positional = args.slice(1).filter((a) => !a.startsWith("-"));
const opts = parseArgs(args.slice(1));
try {
switch (command) {
case "resize": {
if (positional.length < 4) {
console.error(
"Usage: resize <input> <output> <width> <height> [options]"
);
process.exit(1);
}
const result = await resizeImage({
inputPath: positional[0],
outputPath: positional[1],
width: parseInt(positional[2]),
height: parseInt(positional[3]),
...opts,
});
console.log(`Resized: ${result.input}`);
console.log(
` ${result.originalDimensions.width}x${result.originalDimensions.height} -> ` +
`${result.newDimensions.width}x${result.newDimensions.height}`
);
console.log(
` Size: ${formatSize(result.originalSize)} -> ${formatSize(result.newSize)}`
);
console.log(` Algorithm: ${result.algorithm}`);
break;
}
case "thumbnail": {
if (positional.length < 2) {
console.error("Usage: thumbnail <input> <output> [options]");
process.exit(1);
}
const result = await generateThumbnail({
inputPath: positional[0],
outputPath: positional[1],
size: opts.size || 150,
algorithm: opts.algorithm || "lanczos",
quality: opts.quality || 85,
});
console.log(`Thumbnail created: ${result.output}`);
console.log(
` Size: ${result.newDimensions.width}x${result.newDimensions.height}`
);
break;
}
case "batch": {
if (positional.length < 4) {
console.error(
"Usage: batch <input-dir> <output-dir> <width> <height> [options]"
);
process.exit(1);
}
await batchResize({
inputDir: positional[0],
outputDir: positional[1],
width: parseInt(positional[2]),
height: parseInt(positional[3]),
...opts,
});
break;
}
case "batch-thumbnails": {
if (positional.length < 2) {
console.error(
"Usage: batch-thumbnails <input-dir> <output-dir> [options]"
);
process.exit(1);
}
await batchThumbnails({
inputDir: positional[0],
outputDir: positional[1],
size: opts.size || 150,
algorithm: opts.algorithm || "lanczos",
quality: opts.quality || 85,
});
break;
}
default:
console.error(`Unknown command: ${command}`);
printUsage();
process.exit(1);
}
} catch (err) {
console.error(`Error: ${err.message}`);
process.exit(1);
}
}
// Export functions for library use
module.exports = {
resizeImage,
generateThumbnail,
batchResize,
batchThumbnails,
getSupportedImages,
};
// Run CLI if executed directly
if (require.main === module) {
main();
}