The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Sharp to turn each source image into a predictable set of dimension-specific files. Store the renditions you need in a manifest, iterate over every source and target, choose a fit mode deliberately, and write deterministic output names. The workflow below handles orientation, formats, enlargement, failures, and scaling without assuming that one resize policy suits every image.
The core batch pattern
Sharp is a Node.js image-processing library installed with npm. A batch job has two data sets: source files and target specifications. For each source/target pair, create a pipeline, resize it, select an output format, and write the result. Keeping target dimensions in data rather than duplicating code makes it easy to add a new rendition later.
The following complete script creates the output directory, accepts common raster inputs, applies EXIF orientation, prevents accidental upscaling, and records successes and failures independently. It emits WebP files, but the format can be changed per target.
Recommended Free Tools
Install and prepare the project
- Use a Node.js runtime supported by the Sharp version you install. The Sharp project README currently lists Node.js 20.9.0 or newer for runtimes with Node-API v9 support; verify the requirement when you update dependencies.
- Create a project and install Sharp:
mkdir image-batch
cd image-batch
npm init -y
npm install sharp
Use an ES-module file such as generate.mjs. Put source images in ./images; the script creates ./generated automatically.
#1 Best Overall
Runnable batch script
import sharp from 'sharp';
import { readdir, mkdir } from 'node:fs/promises';
import { join, extname, basename } from 'node:path';
const inputDir = './images';
const outputDir = './generated';
const sizes = [
{ name: 'small', width: 320, height: 240, fit: 'inside', format: 'webp', quality: 82 },
{ name: 'card', width: 800, height: 600, fit: 'cover', format: 'webp', quality: 84 },
{ name: 'square', width: 600, height: 600, fit: 'cover', format: 'webp', quality: 84 },
{ name: 'original-bound', width: 1600, height: 1600, fit: 'inside', format: 'jpeg', quality: 86 }
];
const supported = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif', '.tif', '.tiff', '.gif']);
function outputPath(stem, target) {
return join(outputDir, `${stem}-${target.name}.${target.format}`);
}
async function render(inputPath, stem, target) {
let pipeline = sharp(inputPath)
.autoOrient()
.resize({
width: target.width,
height: target.height,
fit: target.fit,
withoutEnlargement: true
});
if (target.format === 'jpeg') pipeline = pipeline.jpeg({ quality: target.quality });
else if (target.format === 'png') pipeline = pipeline.png({ compressionLevel: 9 });
else if (target.format === 'avif') pipeline = pipeline.avif({ quality: target.quality });
else pipeline = pipeline.webp({ quality: target.quality });
await pipeline.toFile(outputPath(stem, target));
}
await mkdir(outputDir, { recursive: true });
const files = await readdir(inputDir);
const imageFiles = files.filter(file => supported.has(extname(file).toLowerCase()));
if (imageFiles.length === 0) {
console.log(`No supported images found in ${inputDir}`);
process.exit(0);
}
const failures = [];
let completed = 0;
for (const file of imageFiles) {
const inputPath = join(inputDir, file);
const stem = basename(file, extname(file));
for (const target of sizes) {
try {
await render(inputPath, stem, target);
completed += 1;
console.log(`Created ${outputPath(stem, target)}`);
} catch (error) {
failures.push({ file, target: target.name, message: error.message });
console.error(`Failed ${file} -> ${target.name}: ${error.message}`);
}
}
}
console.log(`Completed ${completed} rendition(s); ${failures.length} failure(s).`);
if (failures.length) process.exitCode = 1;
Run it with node generate.mjs. A source named hero.jpg produces files such as hero-small.webp and hero-square.webp. The script’s per-output error handling lets the remaining files finish, while a nonzero exit code still signals failure to CI.
Choose the resize policy before writing code
Supplying both width and height does not mean “scale without cropping.” Sharp’s default fit is cover, so understand the alternatives and encode the choice in each manifest entry.
| Fit | Result | Use it when | Trade-off |
|---|---|---|---|
cover |
Preserves aspect ratio and fills both dimensions | A fixed card or thumbnail must have no empty area | Edges may be cropped |
contain |
Preserves the entire image inside the box | Every source detail must remain visible | Letterboxing can appear |
inside |
Keeps both dimensions at or below the requested bounds | You need a maximum bounding box | The output may not equal either requested side |
outside |
Makes the image at least as large as both bounds | A later operation will crop it | One or both sides exceed the target |
fill |
Forces exact dimensions without preserving aspect ratio | Distortion is acceptable for a specialized graphic | People and objects can look stretched |
withoutEnlargement: true prevents upscaling. A 200-pixel source requested as a 1,000-pixel rendition will therefore remain smaller than the requested box. That protects detail, but layouts that require exact dimensions should use a deliberate background, containment, or a separate upscale strategy.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchGenerate several variants from one input efficiently
The simple nested loop is sequential and easy to monitor. When several outputs share one source, Sharp also provides clone() so independent pipelines can share the same input. This is useful when each rendition has a different size or format.
import sharp from 'sharp';
const source = sharp('./images/product.jpg').autoOrient();
await Promise.all([
source.clone().resize(320, 240, { fit: 'inside' }).webp({ quality: 82 }).toFile('./generated/product-small.webp'),
source.clone().resize(800, 600, { fit: 'cover' }).webp({ quality: 84 }).toFile('./generated/product-card.webp'),
source.clone().resize(1200, 1200, { fit: 'inside', withoutEnlargement: true }).jpeg({ quality: 86 }).toFile('./generated/product-large.jpg')
]);
Cloning does not remove the need to bound the total work. For many source files, start with the sequential version, measure runtime and memory on the deployment machine, then add bounded concurrency if throughput matters. The Sharp documentation does not establish a universally correct concurrency value for separate files; image dimensions, formats, CPU, memory, and storage speed all affect the result.
Rank #2
Formats, orientation, and transparency
Apply orientation first
Phone photographs often store the camera angle in EXIF metadata rather than physically rotating pixels. autoOrient() applies that metadata before resizing, so width and height calculations match what a viewer sees. Use it before any dimension-dependent operation.
Select output formats intentionally
Sharp commonly reads JPEG, PNG, WebP, AVIF, TIFF, and SVG, and can write JPEG, PNG, WebP, GIF, and AVIF. Choose based on the asset:
- Use WebP or AVIF for many web photographs when your delivery targets support them.
- Use PNG when lossless output or alpha transparency is required.
- Use JPEG for broad compatibility with photographic images that do not need transparency.
- Keep transparency in mind: converting an image with an alpha channel to JPEG removes that channel.
Quality values are format-specific controls, not a universal visual score. Inspect representative outputs at their actual display size; there is no single quality setting that is optimal for every source.
Make the manifest match the product layout
Name targets for their purpose, not only their dimensions. A social preview, an avatar, and a product card may all be square but need different crop positions, formats, and quality settings. Add a new object to sizes instead of copying another processing block.
- Exact canvas: use
coverand review focal-point crops. - Maximum dimensions: use
insidewithwithoutEnlargement. - Whole image in a frame: use
containand decide whether the background should be transparent or a chosen color. - Different delivery formats: give each target its own
formatand encoder options.
Deterministic names make cache invalidation and deployment simpler. If you change crop rules or encoder settings, include a version in the output directory or filename so old and new assets cannot be confused.
Rank #3
Troubleshooting and failure handling
“Cannot find package sharp”
Run npm install sharp in the same project directory as the script, then confirm that the script is being executed with that project’s Node.js installation. Reinstall dependencies for the target operating system if deployment uses a different platform.
An input is skipped
The sample filters by extension. A file with an unusual extension, uppercase spelling, or no extension will not enter the loop. Normalize extensions, add the format to supported, or attempt processing and catch Sharp’s decode error when extension filtering is not reliable.
“Input buffer contains unsupported image format” or a decode error
The file may be corrupt, mislabeled, or unsupported by the installed build. Open it independently, verify its actual format, and isolate it from the batch. The script records the failed source and target so one bad file does not hide successful outputs.
The result is cropped unexpectedly
That is the expected behavior of cover. Switch to contain or inside when preserving the whole source matters, or adjust the crop position for the subject’s focal point.
The output is smaller than requested
withoutEnlargement deliberately prevents upscaling, and inside preserves the aspect ratio. Remove the enlargement guard only when a larger, potentially softer image is acceptable, and use cover or fill only when their cropping or distortion behavior is intended.
Rank #4
Memory pressure or slow batches
Large originals and many simultaneous pipelines increase memory use. Keep the sequential loop, process files in chunks, reduce concurrency, and measure on the actual deployment hardware before choosing a limit. Write to local or fast temporary storage when possible, then upload completed files.
Validate a batch before publishing it
- Check that every expected source produced every manifest target.
- Open samples from portrait, landscape, transparent, and very small inputs.
- Verify dimensions with an image inspector, especially for
inside,outside, and enlargement-protected outputs. - Check filenames and extensions against the encoded format.
- Record failures and fail the deployment when required renditions are missing.
Or skip the browser setup
If your “source images” are actually pages, dashboards, or rendered web content that must be captured before resizing, ScreenshotNeo provides a single HTTP request for a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether it was billed.
Use the API documentation at https://screenshotneo.com/docs/ for the full option set. This Node.js call saves a screenshot that you can then feed into the Sharp pipeline above:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('page.webp', data));
The equivalent requests are:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
ScreenshotNeo also offers full-page and element captures, dark mode, device presets and custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user-agent, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, OpenAPI, and an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and annual billing provides two months free. Create a free ScreenshotNeo account to try it without a card.
FAQ
Can one source produce different file types in the same run?
Yes. Give each manifest entry its own format and encoder branch, or clone one source pipeline into outputs such as WebP, JPEG, and PNG.
Should I use cover for every thumbnail?
No. Use it only when a full canvas is more important than preserving every edge. For uncropped thumbnails, choose contain or inside and design the surrounding frame accordingly.
Does withoutEnlargement guarantee the requested width?
No. It specifically allows the result to remain smaller when the source cannot reach the requested dimensions without upscaling.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
How should I choose batch concurrency?
There is no documented universal value. Measure CPU, memory, input dimensions, storage, and elapsed time in your own deployment, then impose a bound that leaves headroom for the rest of the application.
Frequently Asked Questions
Can one source produce different file types in the same run?
Yes. Give each manifest entry its own format and encoder branch, or clone one source pipeline into outputs such as WebP, JPEG, and PNG.
Should I use cover for every thumbnail?
No. Use it only when a full canvas is more important than preserving every edge. For uncropped thumbnails, choose contain or inside and design the surrounding frame accordingly.
Does withoutEnlargement guarantee the requested width?
No. It specifically allows the result to remain smaller when the source cannot reach the requested dimensions without upscaling.
How should I choose batch concurrency?
There is no documented universal value. Measure CPU, memory, input dimensions, storage, and elapsed time in your own deployment, then impose a bound that leaves headroom for the rest of the application.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

