#!/bin/bash # # Developed by Fred Weinhaus 5/2/2010 .......... 9/12/2015 # # ------------------------------------------------------------------------------ # # Licensing: # # Copyright © Fred Weinhaus # # My scripts are available free of charge for non-commercial use, ONLY. # # For use of my scripts in commercial (for-profit) environments or # non-free applications, please contact me (Fred Weinhaus) for # licensing arrangements. My email address is fmw at alink dot net. # # If you: 1) redistribute, 2) incorporate any of these scripts into other # free applications or 3) reprogram them in another scripting language, # then you must contact me for permission, especially if the result might # be used in a commercial or for-profit environment. # # My scripts are also subject, in a subordinate manner, to the ImageMagick # license, which can be found at: http://www.imagemagick.org/script/license.php # # ------------------------------------------------------------------------------ # #### # # USAGE: huemap [-h hues] [-t tolers] [-r] infile outfile # USAGE: huemap [-help] # # OPTIONS: # # -h hues hues=srchue,dsthue; source and destination hue values; # comma separated list; 0<=integer<=360; default=240,120 # maps blue to green # -t tolers tolers=stoler,dtoler; source and destination range of # hues on each side of hue values; 0<=integer<=360; # default=10,10 # -r reverse direction of range of destination hues # ### # # NAME: HUEMAP # # PURPOSE: To transform the hues in an image from one range to another. # # DESCRIPTION: HUEMAP transform the hues in an image from one range to another. # One hue can be mapped to another single hue. One range of hues can be mapped # to a single hue. Or one range of hues can be mapped to another range of hues. # This is similar to GIMP's Rotate Hues. # # # OPTIONS: # # -h hues ... HUES=SRCHUE,DSTHUE. These are source and destination hue values # in the range of 0<=integer<=360. The source hue (range) will be replaced by # the detination hue (range), depending upon the tolers values below. The # default=240,120 maps blue to green. # # -t tolers ... TOLERS=STOLER,DTOLER. These are the source and destination # ranges on each side of the specified hues that determine the range of hues # to be mapped. Values are in the range of 0<=integer<=360. The default=10,10. # # -r ... REVERSE the direction of the range of destination hues. # # REQUIREMENT: IM version 6.3.5-7 or higher due to the use of -clut. # # NOTE: If the image has an alpha channel, it will be copied unchanged to # the output image. # # CAVEAT: No guarantee that this script will work on all platforms, # nor that trapping of inconsistent parameters is complete and # foolproof. Use At Your Own Risk. # ###### # # set default values hues="240,120" # src,dst hues tolers="10,10" # src and dst ranges on either side of src,dst hues reverse="no" # reverse direction of start and end dst # set directory for temporary files dir="." # suggestions are dir="." or dir="/tmp" # set up functions to report Usage and Usage with Description PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path PROGDIR=`dirname $PROGNAME` # extract directory of program PROGNAME=`basename $PROGNAME` # base name of program usage1() { echo >&2 "" echo >&2 "$PROGNAME:" "$@" sed >&2 -e '1,/^####/d; /^###/g; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" } usage2() { echo >&2 "" echo >&2 "$PROGNAME:" "$@" sed >&2 -e '1,/^####/d; /^######/g; /^#/!q; s/^#*//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" } # function to report error messages errMsg() { echo "" echo $1 echo "" usage1 exit 1 } # function to test for minus at start of value of second part of option 1 or 2 checkMinus() { test=`echo "$1" | grep -c '^-.*$'` # returns 1 if match; 0 otherwise [ $test -eq 1 ] && errMsg "$errorMsg" } # test for correct number of arguments and get values if [ $# -eq 0 ] then # help information echo "" usage2 exit 0 elif [ $# -gt 7 ] then errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" else while [ $# -gt 0 ] do # get parameter values case "$1" in -help) # help information echo "" usage2 exit 0 ;; -h) # get hues shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID HUES SPECIFICATION ---" checkMinus "$1" hues=`expr "$1" : '\([,0-9]*\)'` [ "$hues" = "" ] && errMsg "--- HUES=$hues MUST BE TWO COMMA DELIMITED NON-NEGATIVE INTEGERS ---" ;; -t) # get tolers shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID TOLERANCES SPECIFICATION ---" checkMinus "$1" tolers=`expr "$1" : '\([,0-9]*\)'` [ "$tolers" = "" ] && errMsg "--- TOLERANCES=$tolers MUST BE TWO COMMA DELIMITED NON-NEGATIVE INTEGERS ---" ;; -r) # get reverse reverse="yes" ;; -) # STDIN and end of arguments break ;; -*) # any other - argument errMsg "--- UNKNOWN OPTION ---" ;; *) # end of arguments break ;; esac shift # next option done # # get infile and outfile infile="$1" outfile="$2" fi # test that infile provided [ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED" # test that outfile provided [ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED" # get im version im_version=`convert -list configure | \ sed '/^LIB_VERSION_NUMBER /!d; s//,/; s/,/,0/g; s/,0*\([0-9][0-9]\)/\1/g' | head -n 1` srchue=`echo $hues | cut -d, -f 1` dsthue=`echo $hues | cut -d, -f 2` stoler=`echo $tolers | cut -d, -f 1` dtoler=`echo $tolers | cut -d, -f 2` #echo "srchue=$srchue; dsthue=$dsthue; stoler=$stoler; dtoler=$dtoler" # set up temp files tmpI1="$dir/huemap_I_$$.mpc" tmpI2="$dir/huemap_I_$$.cache" tmpH1="$dir/huemap_H_$$.mpc" tmpH2="$dir/huemap_H_$$.cache" tmpS1="$dir/huemap_S_$$.mpc" tmpS2="$dir/huemap_S_$$.cache" tmpL1="$dir/huemap_L_$$.mpc" tmpL2="$dir/huemap_L_$$.cache" tmpG1="$dir/huemap_G_$$.mpc" tmpG2="$dir/huemap_G_$$.cache" tmpT1="$dir/huemap_T_$$.mpc" tmpT2="$dir/huemap_T_$$.cache" tmpA1="$dir/huemap_A_$$.mpc" tmpA2="$dir/huemap_A_$$.cache" trap "rm -f $tmpI1 $tmpI2 $tmpH1 $tmpH2 $tmpS1 $tmpS2 $tmpL1 $tmpL2 $tmpG1 $tmpG2 $tmpT1 $tmpT2 $tmpA1 $tmpA2;" 0 trap "rm -f $tmpI1 $tmpI2 $tmpH1 $tmpH2 $tmpS1 $tmpS2 $tmpL1 $tmpL2 $tmpG1 $tmpG2 $tmpT1 $tmpT2 $tmpA1 $tmpA2; exit 1" 1 2 3 15 trap "rm -f $tmpI1 $tmpI2 $tmpH1 $tmpH2 $tmpS1 $tmpS2 $tmpL1 $tmpL2 $tmpG1 $tmpG2 $tmpT1 $tmpT2 $tmpA1 $tmpA2; exit 1" ERR # read the input image and filter image into the temp files and test validity. convert -quiet "$infile" +repage "$tmpI1" || errMsg "--- FILE $infile DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---" # separate alpha channel if exists is_alpha=`identify -ping -verbose $tmpI1 | grep "Alpha" | head -n 1` if [ "$is_alpha" != "" ]; then convert $tmpI1 -alpha extract $tmpA1 convert $tmpI1 -alpha off $tmpI1 fi # convert to HSL and separate channels convert $tmpI1 -colorspace HSL -channel R -separate $tmpH1 convert $tmpI1 -colorspace HSL -channel G -separate $tmpS1 convert $tmpI1 -colorspace HSL -channel B -separate $tmpL1 # create length 360 gradient convert -size 1x360 gradient: -rotate 90 $tmpG1 # modify gradient as lut to apply to hue channel # create gradient section of length=2*toler+1 and value=dst1 to dst2 scaled to percent # insert into beginning of transparent row # due to bug, make it red, then change to dst later dst1=`convert xc: -format "%[fx:($dsthue-$dtoler)]" info:` dst2=`convert xc: -format "%[fx:($dsthue+$dtoler)]" info:` #echo "dst1=$dst1; dst2=$dst2" if [ "$reverse" = "yes" ]; then dst=$dst1 dst1=$dst2 dst2=$dst fi #echo "dst1=$dst1; dst2=$dst2" len=$((2*$stoler+1)) if [ $stoler -eq 0 -a $dtoler -gt 0 ]; then errMsg "--- DESTINATION TOLERANCE MUST BE 0 IF SOURCE TOLERANCE IS 0 ---" elif [ $dtoler -eq 0 ]; then convert -size ${len}x1 xc:red \ -background none -gravity west -extent 360x1 $tmpT1 else # test if range crosses hue=0 red test=`convert xc: -format "%[fx:($dst1<0||$dst2<0)?1:0]" info:` if [ $test -eq 1 ]; then convert -size ${len}x1 xc: \ -fx "uu=($dst2-$dst1)*i/(w-1)+$dst1; mod(uu+360,360)/360" \ -channel red -evaluate set 0 +channel \ -background none -gravity west -extent 360x1 $tmpT1 else dst1=`convert xc: -format "%[fx:100*$dst1/360]" info:` dst2=`convert xc: -format "%[fx:100*$dst2/360]" info:` convert -size ${len}x1 gradient:"gray($dst1%)"-"gray($dst2%)" \ -channel red -evaluate set 0 +channel \ -background none -gravity west -extent 360x1 $tmpT1 fi fi # roll the above tmp to the correct start (hue) position # and composite with gradient # and convert red to dst src1=`convert xc: -format "%[fx:$srchue-$stoler]" info:` sign=`convert xc: -format "%[fx:sign($src1)<0?0:1]" info:` abs_src1=`convert xc: -format "%[fx:abs($src1)]" info:` if [ $sign -eq 0 ]; then rollval="-${abs_src1}+0" else rollval="+${abs_src1}+0" fi #echo "rollval=$rollval" if [ $dtoler -eq 0 ]; then dst1=`convert xc: -format "%[fx:100*$dst1/360]" info:` convert $tmpG1 \( $tmpT1 -roll $rollval \) \ -compose over -composite \ -fill "gray($dst1%)" -opaque red \ $tmpG1 else convert $tmpG1 \( $tmpT1 -roll $rollval \) \ -compose over -composite \ -channel G -separate +channel \ $tmpG1 fi # apply modified gradient as lut to hue channel convert $tmpH1 $tmpG1 -interpolate nearest-neighbor -clut $tmpH1 # colorspace swapped at IM 6.7.5.5, but not properly fixed until 6.7.6.6 # before swap verbose info reported colorspace=RGB after colorspace=sRGB if [ "$im_version" -ge "06070606" ]; then cspace="sRGB" else cspace="RGB" fi # recombine channels and convert to RGB convert $tmpH1 -colorspace HSL \ $tmpH1 -compose CopyRed -composite \ $tmpS1 -compose CopyGreen -composite \ $tmpL1 -compose CopyBlue -composite \ -colorspace $cspace $tmpI1 # add alpha channel back if needed if [ "$is_alpha" = "True" ]; then convert $tmpI1 $tmpA1 -alpha off -compose copy_opacity -composite "$outfile" else convert $tmpI1 "$outfile" fi exit 0