negpro
v2026.3.17-1
Published
Convert scanned film negatives to positives. Core inversion logic based on negfix8 by jaz99 (https://www.flickr.com/people/jaz99) with many new options and more robust output.
Downloads
1,218
Maintainers
Readme
negpro
Convert scanned 16-bit linear film negatives to positives. Core inversion logic based on negfix8 by jaz99, with many new options and more robust output.
Installation
npm install -g negproYou can also test out a web based version of negpro
Usage
negpro [options] [inputs...]See examples
Arguments
| Argument | Description |
|----------|-------------|
| inputs | Input file(s) and optional output file |
Input/Output
| Option | Description | Default |
|--------|-------------|---------|
| --dir <directory> | Process all TIFFs in directory | |
| --output-dir <directory> | Write output files to this directory | |
Tuning
| Option | Description | Default |
|--------|-------------|---------|
| --gamma <value> | Set image gamma | 2.15 |
| --clip-black <percent> | Clip darkest N% to black during contrast stretch | 0.1 |
| --clip-white <percent> | Clip brightest N% to white during contrast stretch | 0.1 |
| --clip <percent> | Clip both black and white ends by N% during contrast stretch | |
| --no-stretch | Disable contrast stretch | on |
Image Analysis
| Option | Description | Default |
|--------|-------------|---------|
| --per-image | Compute a separate profile for each image instead of sharing | |
| --border-exclude <percent> | Exclude outer N% of image from profiling and contrast stretch | 2 |
| --pixel-rejection-percentage <percent> | Ignore brightest/darkest N% of pixels within a frame when profiling | 0.1 |
| --no-frame-rejection | Disable automatic outlier frame rejection from shared profile | |
Config/Profiles
| Option | Description | Default |
|--------|-------------|---------|
| --save-config | Save current flags as global defaults | |
| --save-profile <name> | Analyze input files, save profile, then exit | |
| --use-profile <name> | Use a previously saved profile | |
Utility
| Option | Description | Default |
|--------|-------------|---------|
| --dry-run | Show what would be done without processing images | |
| --concurrency <n> | Number of images to process in parallel | CPU threads - 1 |
| --examples | Show usage examples | |
| --explanation | Show how negpro works, profiles, and config details | |
How it works
By default, inversion values are calculated from an average of all input files (either named or within the directory when using --dir), which leads to more consistent image balancing throughout a roll of film. This can be disabled with --per-image, which will perform balancing on an image-by-image basis.
Output image data is contrast-stretched to fill the histogram by default. This can be disabled with --no-stretch.
Large dust spots that would normally skew balancing are excluded from calculations by the default --pixel-rejection-percentage value of 0.1% in conjunction with the default contrast stretch and clipping behaviour.
When computing a shared profile across multiple images, frames with unusual channel characteristics (e.g. backlit shots) are automatically detected and excluded from the profile to prevent color casts. Excluded frames are still processed, just not used for profiling. Frame rejection requires at least 6 images to have enough data for reliable detection, and will never reject more than 25% of frames — if too many would be rejected, all frames are kept. This can be disabled with --no-frame-rejection.
Output files or directories are never overwritten. If a file or directory already exists, a numeric suffix (_2, _3, etc.) is appended automatically and a warning is shown.
Slight unexposed/film holder areas around the edge of the frame (2% by default) are excluded from analysis and can be tuned via --border-exclude
If you use --dir to process a directory a new directory will be created with a "_processed" suffix and a negpro-log.txt file will be created in addition to the output files containing relevant options for future reference.
After processing, negpro checks whether the default clipping may have been too aggressive for any images. Negatives with a narrow density range (where the darkest and lightest tones are close together) are particularly sensitive to percentage-based clipping — a fixed 0.1% clip can eat into real tonal detail when the histogram is compressed or accentuate film grain. If this is detected, a warning is shown suggesting you reduce --clip-black and --clip-white to 0.01.
Profiles
Profiles store the inversion parameters computed from analyzing your negatives. Create one from a set of reference frames and reuse it across batches for consistency:
negpro --save-profile portra400 *.tif
negpro --use-profile portra400 --dir scansProfiles are saved to:
- macOS/Linux:
~/.negpro/<name>.json - Windows:
%USERPROFILE%\.negpro\<name>.json
Each profile includes metadata (timestamp, source files, settings) for future reference only — this metadata is not used when applying the profile.
Every run also saves its profile to ~/.negpro/last_run_profile.json. If you like the results, copy it to reuse later:
cp ~/.negpro/last_run_profile.json ~/.negpro/portra400.json
negpro --use-profile portra400 --dir scansConfig file
Every run saves its effective settings to last_run_config.json. If you like the results, copy it to use as your global config:
# macOS/Linux
cp ~/.negpro/last_run_config.json ~/.negpro/config.json
# Windows
copy %USERPROFILE%\.negpro\last_run_config.json %USERPROFILE%\.negpro\config.jsonOr use --save-config with flags to write config.json directly:
negpro --save-config --gamma 2.5 --clip-black 0.5Supported config keys: gamma, stretch, clipBlack, clipWhite, clip, pixelRejectionPercentage, borderExclude, concurrency, perImage, noFrameRejection. CLI flags always override config file values.
Config and profile files are stored in:
- macOS/Linux:
~/.negpro/ - Windows:
%USERPROFILE%\.negpro\
Logs
Every run writes a timestamped log to ~/.negpro/logs/. These logs include full settings, per-file analysis, profile values, and any warnings. The last 20 logs are kept; older ones are automatically pruned.
When using --dir, a negpro-log.txt is also written alongside the output files for convenience.
Examples
Convert a single negative (output: scan_processed.tif in same directory):
negpro scan.tifConvert a single negative to a specific output file:
negpro scan.tif output.tifConvert multiple negatives (shared profile computed automatically):
negpro frame01.tif frame02.tif frame03.tifProcess an entire directory of scans (output: scans_processed/):
negpro --dir scansProcess a directory, writing output to a specific folder:
negpro --dir scans --output-dir positivesPreview what would happen without processing:
negpro --dry-run --dir scansAdjust gamma (higher = brighter midtones):
negpro --gamma 2.5 scan.tifIncrease shadow and highlight clipping to 1% for more contrast:
negpro --clip 1 --dir scansClip shadows aggressively but preserve highlights:
negpro --clip-black 2 --clip-white 0.1 scan.tifDisable contrast stretch (output will be darker/flatter):
negpro --no-stretch scan.tifIncrease outlier rejection to handle dusty scans:
negpro --pixel-rejection-percentage 0.5 --dir scansExclude more of the frame border (useful if film holder edges are visible):
negpro --border-exclude 5 --dir scansForce per-image profiling (each image balanced independently):
negpro --per-image --dir scansDisable automatic frame rejection (include all frames in shared profile):
negpro --no-frame-rejection --dir scansCreate a saved profile from reference frames:
negpro --save-profile portra400 ref01.tif ref02.tif ref03.tifCreate a saved profile from all TIFFs in a directory:
negpro --save-profile portra400 --dir scansApply a saved profile to a batch:
negpro --use-profile portra400 --dir scansSave your preferred settings as global defaults:
negpro --save-config --gamma 2.5 --clip 0.5 --pixel-rejection-percentage 0.3Copy last run's settings as your global config:
# macOS/Linux
cp ~/.negpro/last_run_config.json ~/.negpro/config.json
# Windows
copy %USERPROFILE%\.negpro\last_run_config.json %USERPROFILE%\.negpro\config.jsonProcess with limited parallelism:
negpro --concurrency 2 --dir scansCredits
Inspired by negfix8 by jaz99.
Carried over from negfix8:
- Core inversion algorithm
- Pattern of being able to save a "profile" based on analysis of images and reuse it against other images
New aspects:
- No ImageMagick dependency — just
npm install -g negpro - Parallel processing for faster batch conversion
- Improved performance: typically 4x faster before the parallel processing improvements and 18-20x faster with them
- Defaults to whole-roll calculations for the orange mask, leading to more accurate and consistent color inversion
- Default slight clipping (0.1%) during contrast stretch to prevent dust specks and specular highlights from pulling the stretch endpoints and producing poor results
- Automatic outlier frame rejection — backlit or unusual frames are detected and excluded from shared profiling to prevent color casts
- Tunable outlier rejection (light/dark pixels) so large dust spots don't throw off color balance
- Automatic detection of narrow-density negatives where default clipping may be too aggressive, with a warning and suggested alternative settings
- Independent control of shadow and highlight clip percentages, rather than just contrast stretch on/off
- Persistent global configuration — save your preferred settings once and they apply automatically to all future sessions
- Web-based version for immediate inversion without any installation
