Skip to content

Instantly share code, notes, and snippets.

@stollcri
Last active November 5, 2024 11:24
Show Gist options
  • Save stollcri/29ef6a69ffbb55698d24a89d448e1a2a to your computer and use it in GitHub Desktop.
Save stollcri/29ef6a69ffbb55698d24a89d448e1a2a to your computer and use it in GitHub Desktop.
A script which uses ImageMagick to develop scans of C-41 film negatives into color-corrected positive images
#!/bin/bash
# ====================================================================================================================
#
# Converted for bash by @stollcri (stollcri at gmail dot com), 2020-07-28
#
# Originally downloaded from: https://sites.google.com/site/c41digitallab/the-complexity
#
# ====================================================================================================================
# C41LAB - Version 1.2
#
# Copyright (C) 2013 S.Terland - email: <[email protected]>
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
# Public License as published by the Free Software Foundation, either version 3 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not,
# see <http://www.gnu.org/licenses/>.
#
# ====================================================================================================================
#
# Description:
# C41Lab is a Windows Command Script for developing positive images from C41 negatives.
#
# The script was developed with batch processing in mind, transforming negatives to positive images that
# do not require more post processing than can be expected from images taken by a digital camera. It utilizes
# several techniques to transform the negative and achieves a good white balance most of the time.
# The only thing that seems to disrupt a perfect white balance is when a picture is very biased
# towards a particular color. But this can usually be easily corrected in post processing.
#
# Usage:
# C41Lab <Input negative (TIF)> <Input non-exposed negative (TIF)> <Output image (TIF)>
#
# Make sure you use 16-bit TIF as the input, using 8-bit will give poor results. The non-exposed negative
# reference can be any size, but must be free of all dust particles and exposure. A small crop of
# the non-exposed portion between the actual images on the film strip is usually the best reference.
# Note that you can use the same reference for a whole film, i.e. every 36 or 24 pictures as long as
# the entire film was scanned with the same exposure settings.
#
# Important! Input images must be linear scanned negatives. C41 will not work with logarithmic scans.
#
# Prerequisite:
# Latest version of the free ImageMagick utility installed - see <http://www.imagemagick.org> for details
#
# Remember:
# Old film based cameras where of varying quality resulting in larger exposure variances than what can be
# expected from today's simplest digital cameras. This script cannot improve the underlying quality of
# your negatives. This being said, Adobe PhotoShop, LightRoom and similar products may do wonders in post
# processing for difficult images.
#
# ====================================================================================================================
echo
echo "====================== C41Lab v1.2 ======================"
# Verify that input files do exist
if [ ! -f "$1" ]; then
echo "Source image not found: $1"
exit
fi
SRC_IMAGE=$1
if [ ! -f "$2" ]; then
echo "Reference image not found: $2"
exit
fi
REF_IMAGE=$2
if [ -x "$3" ]; then
echo "Output image not defined: $3"
exit
fi
OUT_IMAGE=$3
# ======================================== DEFINE VARIABLES ==========================================================
# If you want to change the brightness of your images you can change Gamma (this overrides the calculated value)
GAMMA=1.0
# Using negated gamma approach to enhance contrast and improve color saturation
CONTRAST=0.75
# Colorspace is set to sRGB and don't show warnings - Ensures 16-bit color depth in each color channel (RGB)
CSPACE="-set colorspace sRGB -quiet"
# Push the image a bit away from minimum and maximum to minimize clipping.
BUFFER_LOW=100
BUFFER_HIGH=0.99
# Set several values for helper files and directories
TEMP_DIR=$HOME/.temp_C41Lab
TEMP1="$TEMP_DIR/temp1_color_shift.tif"
TEMP2="$TEMP_DIR/temp2_gamma_corrected.tif"
TEMP3="$TEMP_DIR/temp3_white_balance.tif"
BLACK_REF="$TEMP_DIR/black_ref.tif"
# Create the temporary directory
if [ ! -d "$TEMP_DIR" ]; then
mkdir "$TEMP_DIR"
fi
# ====== Analyze the reference image =====
echo "--- Normalize Scanning Exposure of Negative"
# The exposure of the scanned image may vary slightly, calculate necessary corrections to ensure an even result
# across different films. Note! if the exposure is very under- or overexposed the colors of the final image will
# be affected. Note! This operation will not affect the image exposure, it only compensates for the fact that the
# actual film differs in transparency and/or scanning exposure thus affecting the exposure across different films.
convert $REF_IMAGE $CSPACE -compress none $BLACK_REF
L=$(convert $CSPACE $BLACK_REF -colorspace gray -format "mid_exp=%[fx: mean/2]" info:)
mid_exp=$(echo "$L" | head -n1 | awk -F= '{print $2}')
L=$(convert $CSPACE $BLACK_REF -colorspace gray -format "exp_corr_gamma=%[fx: (log(maxima - $mid_exp)/log($mid_exp))]\nmax_exp=%[fx: maxima]" info:)
exp_corr_gamma=$(echo "$L" | head -n1 | awk -F= '{print $2}')
max_exp=$(echo "$L" | tail -n1 | awk -F= '{print $2}')
if [ "$exp_corr_gamma" == "nan" ]; then
exp_corr_gamma=1.0
fi
# Check if any of the color channels of the reference image reaches QuantumRange. If so, clipping is highly likely.
if [ "$max_exp" == "1" ]; then
echo - Warning! - Negative seems to be overexposed.
echo See histogram of negative "${OUT_IMAGE}_ref_hist.jpg" for more details
convert $CSPACE $SRC_IMAGE -define histogram:unique-colors=false histogram:${OUT_IMAGE}_neg_hist.jpg
fi
# ====== Start Image Transformation =====
echo "--- Create Positive and Perform Color Shift"
# Enhance exposure of negative. Based on the provided black-reference (the unexposed negative) move all colors correctly to
# black (Low value => zero) and add a small buffer on low and high end to minimize clipping.
# Must normalize each color channel before applying gamma to maintain color balance. Applying "negated" gamma to enhance colors and contrast.
L=$(convert $BLACK_REF $CSPACE -gamma $exp_corr_gamma -negate -format "red_shift=%[fx: minima.r*QuantumRange]\ngreen_shift=%[fx: minima.g*QuantumRange]\nblue_shift=%[fx: minima.b*QuantumRange] " info:)
red_shift=$(echo "$L" | head -n1 | awk -F= '{print $2}')
green_shift=$(echo "$L" | head -n2 | tail -n1 | awk -F= '{print $2}')
blue_shift=$(echo "$L" | tail -n1 | awk -F= '{print $2}')
convert $SRC_IMAGE $CSPACE -compress none -gamma $exp_corr_gamma -negate \
-channel R -evaluate subtract $red_shift -normalize \
-channel G -evaluate subtract $green_shift -normalize \
-channel B -evaluate subtract $blue_shift -normalize \
-channel RGB,sync -evaluate multiply $BUFFER_HIGH -evaluate add $BUFFER_LOW $TEMP1
echo "--- Perform Color Correction"
# Calculate the gamma correction factors to correct color balance.
L=$(convert $TEMP1 $CSPACE -resize 1x1 -format "Green_GAMMA=%[fx: (log(1/maxima.g)/log(1/maxima.r))]\nBlue_GAMMA=%[fx: (log(1/maxima.b)/log(1/maxima.r))] " info:)
Green_GAMMA=$(echo "$L" | head -n1 | awk -F= '{print $2}')
Blue_GAMMA=$(echo "$L" | tail -n1 | awk -F= '{print $2}')
# Performing gamma correction of colors based on the physical absorption principle of
# "log(actual luminosity) /log(maximum luminosity)" of each color dimension (RGB)
convert $TEMP1 $CSPACE \
-channel G -gamma $Green_GAMMA \
-channel B -gamma $Blue_GAMMA \
-channel RGB,sync \
-gamma $CONTRAST -negate \
-gamma $CONTRAST -negate $TEMP2
echo "--- Enhance White Balance "
# Based on the "Grey World Principle" perform final white balance enhancement of image
L=$(convert $TEMP2 $CSPACE -resize 1x1 -format "green_fact=%[fx: (log(maxima.g)/log(maxima.r))]\n blue_fact=%[fx: (log(maxima.b)/log(maxima.r))] " info:)
green_fact=$(echo "$L" | head -n1 | awk -F= '{print $2}' | xargs)
blue_fact=$(echo "$L" | tail -n1 | awk -F= '{print $2}' | xargs)
if [ "$green_fact" == "nan" ]; then
green_fact=1.0
fi
if [ "$blue_fact" == "nan" ]; then
blue_fact=1.0
fi
convert $TEMP2 $CSPACE -channel G -gamma $green_fact -channel B -gamma $blue_fact -channel RGB,sync $TEMP3
echo --- Enhance Brightness
# The earlier transformations can make the image stray away from its original brightness. Perform necessary gamma correction.
# Find average gray of the original image and calculate the compensation factor for the brightness mean
L=$(convert $TEMP1 $CSPACE -colorspace gray -format "image_mean=%[fx: mean] " info:)
image_mean=$(echo "$L" | awk '{print $1}' | head -n1 | awk -F= '{print $2}')
# Apply compensation factor to the final image
L=$(convert $TEMP3 $CSPACE -colorspace gray -resize 1x1 -format "final_gamma=%[fx: (log(maxima)/log($image_mean))] " info:)
final_gamma=$(echo "$L" | awk '{print $1}' | head -n1 | awk -F= '{print $2}')
if [ "$final_gamma" == "nan" ]; then
final_gamma=1.0
fi
convert $CSPACE $TEMP3 -gamma $final_gamma -gamma $GAMMA -compress Zip $OUT_IMAGE
echo --- Remove Temporary Files
# Delete temporary files
# rm $TEMP1
# rm $TEMP2
# rm $TEMP3
# rm $BLACK_REF
# Make it look nice when finished
echo "--- Done!"
echo "=========================================================="
echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment