Last active
November 5, 2024 11:24
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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