#!/usr/bin/env bash # # Developed by Fred Weinhaus 6/20/2009 .......... revised 4/25/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: spherize [-d diameter(s)] [-a amp] [-z zoom] [-b bcolor ] [-s] [-t] infile outfile # USAGE: spherize [-h or -help] # # OPTIONS: # # -d diameter(s) diameters; comma separate x and y diameters; # integers>0 and less than or equal to image # dimensions; if only one value provided, # it will be used for both; default is the # image "width,height" # -a amp distortion amplification factor; float>=0; # default=0 # -z zoom zoom factor; scales the input image on the # sphere; float>=0; default=1 # -b bcolor background color; any valid IM color or # "none" for transparent or "image" for input # image background; default=black # -s make result with both diameters equal to the # smaller of the x or y diameters # -t trim image to diameters # ### # # NAME: SPHERIZE # # PURPOSE: To warp an image onto a (hemi-)sphere. # # DESCRIPTION: UNROTATE warps an image onto a (hemi-)sphere and views it # orthographically. The x and y diameters can be specified along with the # distortion amplification and input image zoom. The background around # the sphere is also user controlled. This is a limited version of my # bubblewarp script that is not limited by the use of the IM -fx function # and therefore is very fast. # # # Arguments: # # -d diameter(s) --- DIAMETER(S) is a comma separate list of the desired # x and y diameters of the sphere. If only one value is provided, then it # will be used for both diameters. Values are integers greater than zero # and equal to or smaller than the dimensions of the image. The default # values are the size of the image. Thus if the image is square, then a # spherical effect will be produced. But if the image is not square, then # elliptical effect will be produced. # # -a amp --- AMP is the distortion amplification factor. Values are floats # greater than or equal to zero. The default=1. Values larger than one will # make more distortion and values less than one will make less distortion. # # -z zoom --- ZOOM is the zoom factor for the input image before warping it # onto the sphere. Values are floats greater than or equal to zero. The # default=1. Values larger than one will magnify the input image on the sphere. # Values less than one will minify the input image on the sphere. # # -b bcolor ... BCOLOR is the color for the background outside the sphere. # Any valid IM color is allowed or one may specify "none" for a transparent # background or "image" to have the input image as the background. The default # is black. # # -s ... Make the result with both diameters equal to the smaller of the # x or y diameters. # # -t ... Trim the output image to the diameters. # # NOTE: This script is a limited version of my bubblewarp script, but is # much faster, since it does not use -fx. # # NOTE: This script requires IM 6.5.3-9 or higher due to the use of # -compose distort. Many thanks to Anthony Thyssen for his hard work # developing it. # # 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; dx="" # x diameter dy="" # y diameter amp=1 # distortion amplification zoom=1 # input image zoom factor bcolor="black" # background color; none for transparency; or "image" symmetry="no" # sphere with both diameters equal to min of x and y diameters trim="no" # trim output to diameters # 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 12 ] then errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" else while [ $# -gt 0 ] do # get parameters case "$1" in -h|-help) # help information echo "" usage2 ;; -d) # diameters shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID DIAMETERS SPECIFICATION ---" checkMinus "$1" diameters=$1 test=`echo "$1" | tr "," " " | wc -w` [ $test -eq 1 -o $test -gt 2 ] && errMsg "--- INCORRECT NUMBER OF DIAMETERS SUPPLIED ---" diameters=`expr "$1" : '\([0-9]*,[0-9]*\)'` [ "$diameters" = "" ] && errMsg "--- DIAMETERS=$diameters MUST BE A PAIR OF NON-NEGATIVE INTEGERS SEPARATED BY A COMMA ---" diameters="$1," dx=`echo "$diameters" | cut -d, -f1` dy=`echo "$diameters" | cut -d, -f2` # further testing done later ;; -a) # amp shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID AMPLIFICATION SPECIFICATION ---" checkMinus "$1" amp=`expr "$1" : '\([.0-9]*\)'` [ "$amp" = "" ] && errMsg "--- AMPLIFICATION=$amp MUST BE A NON-NEGATIVE FLOATING POINT VALUE (with no sign) ---" ;; -z) # zoom shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID ZOOM SPECIFICATION ---" checkMinus "$1" zoom=`expr "$1" : '\([.0-9]*\)'` [ "$zoom" = "" ] && errMsg "--- ZOOM=$zoom MUST BE A NON-NEGATIVE FLOATING POINT VALUE (with no sign) ---" ;; -b) # get bcolor shift # to get the next parameter # test if parameter starts with minus sign errorMsg="--- INVALID BACKGROUND COLOR SPECIFICATION ---" checkMinus "$1" bcolor="$1" ;; -s) # set symmetry symmetry="yes" ;; -t) # set trim trim="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" # setup temporaries tmpA1="$dir/spherize_A_$$.mpc" tmpA2="$dir/spherize_A_$$.cache" tmpB1="$dir/spherize_B_$$.mpc" tmpB2="$dir/spherize_B_$$.cache" tmpF1="$dir/spherize_T_$$.mpc" tmpF2="$dir/spherize_T_$$.cache" tmpM1="$dir/spherize_M_$$.mpc" tmpM2="$dir/spherize_M_$$.cache" tmpX1="$dir/spherize_X_$$.mpc" tmpX2="$dir/spherize_X_$$.cache" tmpY1="$dir/spherize_Y_$$.mpc" tmpY2="$dir/spherize_Y_$$.cache" trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpF1 $tmpF2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2;" 0 trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2; exit 1" 1 2 3 15 trap "rm -f $tmpA1 $tmpA2 $tmpB1 $tmpB2 $tmpM1 $tmpM2 $tmpX1 $tmpX2 $tmpY1 $tmpY2; exit 1" ERR # read the input image into the TMP cached image. convert -quiet "$infile" +repage "$tmpA1" || errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---" # test for minimum IM version required # IM 6.5.3.9 or higher to conform to new -compose distort 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` [ "$im_version" -lt "06050309" ] && errMsg "--- REQUIRES IM VERSION 6.5.3-9 OR HIGHER ---" # get image dimensions ww=`convert $tmpA1 -ping -format "%w" info:` hh=`convert $tmpA1 -ping -format "%h" info:` # set default diameters if [ "$dx" = "" ]; then dx=$ww fi if [ "$dy" = "" ]; then dy=$hh fi # compute min diameter dm=`convert xc: -format "%[fx:min($dx,$dy)]" info:` if [ "$symmetry" = "yes" ]; then dx=$dm dy=$dm fi # test diameters [ $dx -eq 0 -o $dx -gt $ww ] && errMsg "--- X DIAMETER MUST BE GREATER THAN 0 AND LESS OR EQUAL TO IMAGE WIDTH ---" [ $dy -eq 0 -o $dy -gt $hh ] && errMsg "--- Y DIAMETER MUST BE GREATER THAN 0 AND LESS OR EQUAL TO IMAGE HEIGHT ---" # scale radius so that that image fits circle or ellipse at any diameter with zoom=1 # invert zoom so increases with larger values # use absolute coord displacement # xamount=(rx/zoom)*(ww/dx)=ww/(2*zoom); where rx = x radius xamount=`convert xc: -format "%[fx:$ww/(2*$zoom+quantumscale)]" info:` yamount=`convert xc: -format "%[fx:$hh/(2*$zoom+quantumscale)]" info:` # create radial gradient convert -size ${dm}x${dm} radial-gradient: -negate $tmpF1 # convert circular gradient to elliptical if [ $dx -ne $dy ]; then convert $tmpF1 -resize ${dx}x${dy}! $tmpF1 fi # create mask convert $tmpF1 -negate -gravity center -background black -extent ${ww}x${hh} -threshold 0 $tmpM1 # set up amplify for distortion if [ "$amp" = "1" ]; then amplify="" else amplify="-evaluate pow $amp" fi # compute spherize function F=(2/pi)*asin(rd)/rd convert $tmpF1 \( +clone -function arcsin '2,0,2,0' $amplify \) \ -compose divide -composite $tmpF1 # set up x displacement # compute x gradient increasing with i convert -size ${dy}x${dx} gradient: -rotate 90 $tmpX1 # compute u*v=xd*asin(rd)/rd; u=gradient 0,1; v=asin(rd)/rd # but where want xd in range -1,1; so must use (2*u-1) # then modify to 0.5*u*v+0.5 to recenter at mid gray # old fx method: 0.5*((2*u-1)*v)+0.5 = u*v - 0.5*v + 0.5 # convert $tmpX1 $tmpF1 -monitor -fx "u*v-0.5*v+0.5" $tmpX1 if [ "$im_version" -ge "06050403" ]; then convert $tmpX1 $tmpF1 +swap \ -compose Mathematics -set option:compose:args "1,0,-0.5,0.5" \ -composite $tmpX1 else # multiply arcsin(r)/(r) by abs of gradient relative to midgray as zero # abs is computed as -solarize 50% -level 50,0% # abs has V shape and ranges 0 to 1 # then scale so that range is 0.5 to 1 # # convert gradient to sign of gradient # sign is computed as -threshold 50% # thus zero (for neg) on left and one (for pos) on right # use sign as mask to separate the right (pos) side, # then negate mask and arcsin-absgrad product # to get the left (neg) side and then combine with right side convert $tmpX1 $tmpF1 \ \( -clone 0 -solarize 50% -level 50,0% \) \ \( -clone 1,2 -compose multiply -composite -function polynomial "0.5,0.5" \) \ \( -clone 0 -threshold 50% \) \ \( -clone 3,4 -compose multiply -composite \) \ \( -clone 3,4 -negate -compose multiply -composite \) \ -delete 0-4 -compose plus -composite \ $tmpX1 fi # y displacement if [ $dx -eq $dy ]; then # map is symmetric so just rotate x displacement convert $tmpX1 -rotate 90 $tmpY1 else # compute y gradient increasing with j convert -size ${dx}x${dy} gradient: -negate $tmpY1 if [ "$im_version" -ge "06050403" ]; then convert $tmpY1 $tmpF1 +swap \ -compose Mathematics -set option:compose:args "1,0,-0.5,0.5" \ -composite $tmpY1 else convert $tmpY1 $tmpF1 \ \( -clone 0 -solarize 50% -level 50,0% \) \ \( -clone 1,2 -compose multiply -composite -function polynomial "0.5,0.5" \) \ \( -clone 0 -threshold 50% \) \ \( -clone 3,4 -compose multiply -composite \) \ \( -clone 3,4 -negate -compose multiply -composite \) \ -delete 0-4 -compose plus -composite \ $tmpY1 fi fi # displace image (center relative) # absolute coord displacement convert $tmpA1 $tmpX1 $tmpY1 \ -gravity center -compose distort \ -set option:compose:args ${xamount}x${yamount} -composite \ $tmpB1 # set up trim if [ "$trim" = "yes" ]; then trimming="-gravity center -crop ${dx}x${dy}+0+0 +repage" else trimming="" fi # composite mask if [ "$bcolor" = "none" ]; then convert $tmpB1 $tmpM1 \ -alpha off -compose copy_opacity -composite $trimming "$outfile" elif [ "$bcolor" = "image" -a "$symmetry" = "no" ]; then convert $tmpB1 $trimming "$outfile" elif [ "$bcolor" = "image" -a "$symmetry" = "yes" ]; then convert $tmpA1 $tmpB1 $tmpM1 -compose over -composite $trimming "$outfile" else convert \( $tmpB1 $tmpM1 \ -gravity center -alpha off -compose copy_opacity -composite \) \ \( -size ${ww}x${hh} xc:"$bcolor" \) +swap \ -gravity center -compose over -composite $trimming "$outfile" fi exit 0