Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
#!/bin/bash

# This is a(n outdated) COPY of Fred Winehaus's "whiteboard" script.
# For the upstream version, see: 
#   http://www.fmwconcepts.com/imagemagick/whiteboard/index.php
#
# This copy is hosted at:
#   http://angg.twu.net/bin/whiteboard.html
#   http://angg.twu.net/bin/whiteboard
#    (find-angg        "bin/whiteboard")
#
# Some of my makefiles ("me" = Eduardo Ochs, a.k.a. Edrx) call this
# script. For example:
#
#   http://angg.twu.net/2019.2-C2/Makefile.html
#    (find-angg        "2019.2-C2/Makefile")


# Developed by Fred Weinhaus 5/29/2009 .......... revised 5/7/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: whiteboard  [-c coords] [-a aspect ] [-m magnify] [-d dimensions] 
# [-e enhance ] [-f filtersize] [-o offset] [-t threshold]  [-s sharpen] 
# [-S saturation] [-w whitecolor] [-p percent] infile outfile
# USAGE: whiteboard [-help]
#
# OPTIONS:
#
# -c      coords            a list of the four x,y coordinates of the corner of 
#                           the whiteboard in the picture ordered clockwise 
#                           from the upper left corner. The default is the 
#                           four corners of the input image.
# -a      aspect            width-to-height aspect ratio of actual whiteboard; 
#                           typical aspect ratios are: 2 (2:1), 1.5 (3:2) and 
#                           1.33 (4:3); floats>0; The default is computed 
#                           automatically
# -m      magnify           ouptut image magnification (or minification) factor 
#                           applied to automatically computed dimensions; float>0; 
#                           values>1 are magnify; values<1 are minify; 
#                           default=1 (no change)
# -d      dimensions        desired dimension(s) of the output; choices are: 
#                           WIDTH, xHEIGHT or WIDTHxHEIGHT; if either of the 
#                           first two, then the other will be computed from the 
#                           aspect ratio and magnify will be ignored; if the latter, 
#                           then both aspect and magnify will be ignored; default is 
#                           to ignore dimensions and use aspect and magnify
# -e      enhance	        enhance image brightness before cleaning background;
#                           choices are: none, stretch, whitebalance or both; 
#                           default=both
# -f      filtersize        size of processing filter to clean up background;
#                           integer>0; default=15
# -o      offset            offset of filter in percent to reduce noise;
#                           integer>=0; default=5
# -t      threshold			text smoothing threshold; 0<=threshold<=100; 
#                           nominal value is about 50; default is no smoothing
# -s      sharpamt          sharpening amount in pixels; float>=0; 
#                           nominal about 1; default=0
# -S      saturation        color saturation expressed as percent; integer>=0; 
#                           default=200 (double saturation)
# -w      whitecolor        desired color for whiteboard background;
#                           default=white
# -p      percent           percent near white to use for white balancing;
#                           float>=0; default=0.01
#
###
#
# NAME: WHITEBOARD
# 
# PURPOSE: To process a picture of a whiteboard to clean up the background 
# and correct the perspective.
# 
# DESCRIPTION: WHITEBOARD processses a picture of a whiteboard with writing 
# on it to clean up the background and correct the perspective. The four 
# corners of the actual interior of the whiteboard in the picture must be 
# supplied in order to correct the perspective.
# 
# OPTIONS: 
# 
# -c coords ... COORDS is a list of the four x,y coordinates of the corner of 
# the whiteboard in the picture ordered clockwise startin with the upper left 
# corner, e.g. "x1,y1 x2,y2 x3,y3 x4,y4". The default will be the four corners 
# of the input image and thus will not trim any existing border or area outside 
# the whiteboard, nor will it correct any perspective distortion.
# 
# -a aspect ... ASPECT is the width-to-height aspect ratio of actual whiteboard. 
# Typical values are: 2 (2:1), 1.5 (3:2) and 1.33 (4:3). Values are floats>0. 
# The default is computed automatically.
# 
# -m magnify ... MAGNIFY is the output image magnification (or minification) factor 
# Values are floats>0. Values larger than 1 will magnify. Values less than 1 will 
# minify. The default=1 and will produce an output whose height is the length of the 
# left edge as defined by the supplied coordinates and whose width=height*aspect. A 
# value of 2 will be twice that size and a value of 0.5 will be half that size. If 
# no coordinates are supplied, then the width and height will be those of the 
# input image multiplied by the magnify factor.
# 
# -d dimensions ... DIMENSIONS are the desired dimension(s) of the output image. 
# Choices are: WIDTH, xHEIGHT or WIDTHxHEIGHT; if either of the first two options 
# are selected, then the other dimension will be computed from the aspect ratio 
# and magnify will be ignored. If the latter option is selected, then both aspect 
# and magnify will be ignored. If no coordinates are supplied, then the input image 
# aspect ratio will be use. The default is to ignore dimensions and use the aspect
# and magnify. 
# 
# -e enhance ... Enhance image brightness before cleaning background. The choices 
# are: none, stretch, white balance or both. The default=none.
# 
# -f filtersize ... FILTERSIZE is the size of the filter used to clean up the 
# background. Values are integers>0. The filtersize needs to be larger than 
# the thickness of the writing, but the smaller the better beyond this. Making it 
# larger will increase the processing time and may lose text. The default is 15. 
#
# -o offset ... OFFSET is the offset threshold in percent used by the filter  
# to eliminate noise. Values are integers>=0. Values too small will leave much 
# noise and artifacts in the result. Values too large will remove too much 
# text leaving gaps. The default is 5. 
# 
# -t threshold ... THRESHOLD is the text smoothing threshold. Values are integers 
# between 0 and 100. Smaller values smooth/thicken the text more. Larger values 
# thin, but can result in gaps in the text. Nominal value is in the middle at 
# about 50. The default is to disable smoothing.
#
# -s sharpamt ... SHARPAMT is the amount of sharpening to be applied to the 
# resulting image in pixels. Values are floats>=0. If used, it should be small 
# (suggested about 1). The default=0 (no sharpening). 
# 
# -S saturation ... SATURATION is the desired color saturation of the text 
# expressed as a percentage. Values are integers>=0. A value of 100 means 
# no change. The default=200. Larger values will make the text colors more 
# saturated.
# 
# -w whitecolor ... WHITECOLOR is the desired background color of the whiteboard 
# after it has been cleaned up. Any valid IM color may be use. The default is white.
# 
# -p percent ... PERCENT near white to use for white balancing; float>=0; 
# default=0.01
# 
# NOTE: For coordinate selection, one can use the IM display function and 
# on the Mac option-rightmousebutton hold and drag to display coordinates. 
# I am not sure what the equivalent is on other systems. Possibly middle 
# mouse button.
# 
# NOTE: Requires IM 6.3.6-0 or higher only if control points are supplied or  
# magnification is not equal to 1, due to the control point ordering for the 
# perspective and also due to the use of -set option:distort:viewport. 
#
# Thanks to Jens Mueller for suggesting this function and supplying references to examples.
# 
# REFERENCES:
# http://www.sagenb.org/home/pub/704/
# http://research.microsoft.com/users/zhang/Papers/WhiteboardRectification.pdf
# http://research.microsoft.com/en-us/um/people/zhang/papers/tr03-39.pdf
# 
# 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
coords=""			# 4 pairs of x,y coordinates
aspect=""			# typical width-to-height ratios of 1, 2, 1.5 or 1.33
magnify=1			# magnification of image size from computed dimensions
dimensions=""		# output image dimension(s)
enhance="none"		# none, stretch, normalize
filtersize=15		# local area filter size
offset=5			# local area offset to remove "noise"; too small-get noise, too large-lose text
threshold=""        # smoothing threshold
sharpamt=0			# sharpen sigma
saturation=200		# color saturation percent; 100 is no change
wcolor="white"		# color for output whiteboard background
bluramt=0.2			# blur sigma for use with smoothing threshold
percent=0.01		# percent close to white for white balancing 

# 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"
	}

# function to test if valid float point pair
testIntegerPair()
	{
	v1=`echo $1 | cut -d, -f1`
	v2=`echo $1 | cut -d, -f2`
	test1=`expr "$v1" : '^[0-9][0-9]*$'`
	test2=`expr "$v2" : '^[0-9][0-9]*$'`
	[ $test1 -eq 0 -o $test2 -eq 0 ] && errMsg "$1 IS NOT A VALID POINT PAIR"
	}

# function to get channel mean
getChannelMean()
	{
	img="$1"
	# get im version
	if [ "$im_version" -ge "06030901" ]
		then 
		mean=`convert $img -format "%[mean]" info:`
		mean=`convert xc: -format "%[fx:100*$mean/quantumrange]" info:`
	else
		data=`convert $img -verbose info:`
		mean=`echo "$data" | sed -n 's/^.*[Mm]ean:.*[(]\([0-9.]*\).*$/\1/p ' | head -1`
		mean=`convert xc: -format "%[fx:100*$mean]" info:`
	fi
	}

# function to get average of near-white pixels
getAverage()
	{
	getChannelMean "$1"
	# get ave in range 0-100
	# note both mean and mask_mean are in range 0-100
	# note average of just near_white values mean of masked image divided by
	# the fraction of white pixels (from mask)
	# which is the mean in range 0 to 1 divided by 100
	ave=`convert xc: -format "%[fx:100*$mean/$maskmean]" info:`
	[ "$ave" = "0" -o "$ave" = "0.0" ] && ave=100
	ratio=`convert xc: -format "%[fx:100/$ave]" info:`
	diff=`convert xc: -format "%[fx:(100-$ave)]" info:`
	}

# function to perform whitebalance (taken from autowhite script)
whiteBalance()
	{
	img="$1"
	# get image size
	ww=`convert $img -format "%w" info:`
	hh=`convert $img -format "%h" info:`
	# separate channels
	convert $tmpA1 $setcspace -channel R -separate $tmpR1
	convert $tmpA1 $setcspace -channel G -separate $tmpG1
	convert $tmpA1 $setcspace -channel B -separate $tmpB1
	# get mask of top percent closest to white
	# approximation using negated saturation and brightness channels multiplied
	convert $tmpA1 $setcspace -colorspace HSB -channel G -negate -channel GB -separate \
		-compose multiply -composite +channel \
		$setcspace -contrast-stretch 0,${percent}% -fill black +opaque white \
		$tmpM1
	# get mean of mask
	getChannelMean "$tmpM1"
	maskmean=$mean
	# use mask image to isolate user supplied percent of pixels closest to white
	# then get ave graylevel for each channel of mask selected pixels
	convert $tmpR1 $tmpM1 -compose multiply -composite $tmpT1
	getAverage "$tmpT1"
	redratio=$ratio
	convert $tmpG1 $tmpM1 -compose multiply -composite $tmpT1
	getAverage "$tmpT1"
	greenratio=$ratio
	convert $tmpB1 $tmpM1 -compose multiply -composite $tmpT1
	getAverage "$tmpT1"
	blueratio=$ratio
	# process image
	if [ "$im_version" -lt "06060100" ]; then	
		convert $tmpA1 -recolor "$redratio 0 0 0 $greenratio 0 0 0 $blueratio" $tmpA1
	else
		convert $tmpA1 -color-matrix "$redratio 0 0 0 $greenratio 0 0 0 $blueratio" $tmpA1
	fi	
	}

# test for correct number of arguments and get values
if [ $# -eq 0 ]
	then
	# help information
   echo ""
   usage2
   exit 0
elif [ $# -gt 26 ]
	then
	errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---"
else
	while [ $# -gt 0 ]
		do
			# get parameter values
			case "$1" in
		  -h|-help)    # help information
					   echo ""
					   usage2
					   exit 0
					   ;;
			   	-c)    # get coords
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID COORDS SPECIFICATION ---"
					   checkMinus "$1"
					   coords="$1"
					   ;;
			   	-w)    # get wcolor
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID WHITE COLOR SPECIFICATION ---"
					   checkMinus "$1"
					   wcolor="$1"
					   ;;
				-a)    # get aspect ratio
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID ASPECT SPECIFICATION ---"
					   checkMinus "$1"
					   aspect=`expr "$1" : '\([.0-9]*\)'`
					   [ "$aspect" = "" ] && errMsg "--- ASPECT=$aspect MUST BE A NON-NEGATIVE FLOAT ---"
					   aspecttest=`echo "$aspect <= 0" | bc`
					   [ $aspecttest -eq 1 ] && errMsg "--- ASPECT=$aspect MUST BE A FLOAT GREATER THAN 0 ---"
					   ;;
				-m)    # get magnify
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID MAGNIFY SPECIFICATION ---"
					   checkMinus "$1"
					   magnify=`expr "$1" : '\([.0-9]*\)'`
					   [ "$magnify" = "" ] && errMsg "--- MAGNIFY=$magnify MUST BE A NON-NEGATIVE FLOAT ---"
					   magnifytest=`echo "$magnify <= 0" | bc`
					   [ $magnifytest -eq 1 ] && errMsg "--- MAGNIFY=$magnify MUST BE A FLOAT GREATER THAN 0 ---"
					   ;;
				-d)    # get dimensions
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID DIMENSIONS SPECIFICATION ---"
					   checkMinus "$1"
					   dimensions="$1"
					   dimensions="${dimensions}x"
					   dimensions=`expr "$dimensions" : '\([x0-9]*\)'`
					   numdim=`echo "$dimensions" | tr "x" " " | wc -w` 
					   [ "$dimensions" = "" ] && errMsg "--- ONE OR TWO DIMENSIONS MUST BE PROVIDED ---"
					   [ $numdim -ne 1 -a $numdim -ne 2 ] && errMsg "--- ONE OR TWO DIMENSIONS MUST BE PROVIDED ---"
					   ww=`echo "$dimensions" | cut -dx -f1`
					   hh=`echo "$dimensions" | cut -dx -f2`
					   ;;
			   	-e)    # get enhance
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID ENHANCE SPECIFICATION ---"
					   checkMinus "$1"
					   enhance="$1"
					   case "$1" in
					   		none) ;;
					   		stretch) ;;
					   		whitebalance) ;;
					   		both) ;;
					   		*) errMsg "--- ENHANCE=$enhance IS NOT A VALID CHOICE ---" ;;
					   esac
					   ;;
				-f)    # get filtersize
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID FILTERSIZE SPECIFICATION ---"
					   checkMinus "$1"
					   filtersize=`expr "$1" : '\([0-9]*\)'`
					   [ "$filtersize" = "" ] && errMsg "--- FILTERSIZE=$filtersize MUST BE A NON-NEGATIVE INTEGER ---"
					   filtersizetest=`echo "$filtersize < 1" | bc`
					   [ $filtersizetest -eq 1 ] && errMsg "--- FILTERSIZE=$filtersize MUST BE AN INTEGER GREATER THAN 0 ---"
					   ;;
				-o)    # get offset
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID OFFSET SPECIFICATION ---"
					   checkMinus "$1"
					   offset=`expr "$1" : '\([0-9]*\)'`
					   [ "$offset" = "" ] && errMsg "--- OFFSET=$offset MUST BE A NON-NEGATIVE INTEGER ---"
					   ;;
				-t)    # get threshold
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID THRESHOLD SPECIFICATION ---"
					   checkMinus "$1"
					   threshold=`expr "$1" : '\([0-9]*\)'`
					   [ "$threshold" = "" ] && errMsg "--- THRESHOLD=$threshold MUST BE A NON-NEGATIVE INTEGER ---"
					   thresholdtestA=`echo "$threshold < 0" | bc`
					   thresholdtestB=`echo "$threshold > 100" | bc`
					   [ $thresholdtestA -eq 1 -o $thresholdtestB -eq 1 ] && errMsg "--- THRESHOLD=$threshold MUST BE AN INTEGER GREATER BETWEEN 0 AND 100 ---"
					   ;;
				-s)    # get sharpamt
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID SHARPAMT SPECIFICATION ---"
					   checkMinus "$1"
					   sharpamt=`expr "$1" : '\([.0-9]*\)'`
					   [ "$sharpamt" = "" ] && errMsg "--- SHARPAMT=$sharpamt MUST BE A NON-NEGATIVE FLOAT ---"
					   ;;
				-S)    # get saturation
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID SATURATION SPECIFICATION ---"
					   checkMinus "$1"
					   saturation=`expr "$1" : '\([0-9]*\)'`
					   [ "$saturation" = "" ] && errMsg "--- SATURATION=$saturation MUST BE A NON-NEGATIVE INTEGER ---"
					   ;;
				-p)    # get percent
					   shift  # to get the next parameter
					   # test if parameter starts with minus sign 
					   errorMsg="--- INVALID PERCENT SPECIFICATION ---"
					   checkMinus "$1"
					   percent=`expr "$1" : '\([.0-9]*\)'`
					   [ "$percent" = "" ] && errMsg "--- PERCENT=$percent MUST BE A NON-NEGATIVE FLOAT ---"
					   ;;
				 -)    # 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"

tmpA1="$dir/autowhite_1_$$.mpc"
tmpA2="$dir/autowhite_1_$$.cache"
tmpM1="$dir/autowhite_M_$$.mpc"
tmpM2="$dir/autowhite_M_$$.cache"
tmpT1="$dir/autowhite_T_$$.mpc"
tmpT2="$dir/autowhite_T_$$.cache"
tmpR1="$dir/autowhite_R_$$.mpc"
tmpR2="$dir/autowhite_R_$$.cache"
tmpG1="$dir/autowhite_G_$$.mpc"
tmpG2="$dir/autowhite_G_$$.cache"
tmpB1="$dir/autowhite_B_$$.mpc"
tmpB2="$dir/autowhite_B_$$.cache"
trap "rm -f $tmpA1 $tmpA2 $tmpM1 $tmpM2 $tmpT1 $tmpT2 $tmpR1 $tmpR2 $tmpG1 $tmpG2 $tmpB1 $tmpB2;" 0
trap "rm -f $tmpA1 $tmpA2 $tmpM1 $tmpM2 $tmpT1 $tmpT2 $tmpR1 $tmpR2 $tmpG1 $tmpG2 $tmpB1 $tmpB2; exit 1" 1 2 3 15
# if use following it fails to produce the output????
#trap "rm -f $tmpA1 $tmpA2 $tmpM1 $tmpM2 $tmpT1 $tmpT2 $tmpR1 $tmpR2 $tmpG1 $tmpG2 $tmpB1 $tmpB2; exit 1" ERR

# 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`

# colorspace RGB and sRGB swapped between 6.7.5.5 and 6.7.6.7 
# though probably not resolved until the latter
# then -colorspace gray changed to linear between 6.7.6.7 and 6.7.8.2 
# then -separate converted to linear gray channels between 6.7.6.7 and 6.7.8.2,
# though probably not resolved until the latter
# so -colorspace HSL/HSB -separate and -colorspace gray became linear
# but we need to use -set colorspace RGB before using them at appropriate times
# so that results stay as in original script
# The following was determined from various version tests using whiteboard
# with IM 6.7.4.10, 6.7.6.10, 6.7.9.1
if [ "$im_version" -lt "06070607" -o "$im_version" -gt "06070707" ]; then
	setcspace="-set colorspace RGB"
else
	setcspace=""
fi
# no need for setcspace for grayscale or channels after 6.8.5.4
if [ "$im_version" -gt "06080504" ]; then
	setcspace=""
fi


# read the input image into the TMP cached image.
convert -quiet "$infile" +repage "$tmpA1" ||
	errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---"


# get input image size and center
w=`convert -ping $tmpA1 -format "%w" info:`
h=`convert -ping $tmpA1 -format "%h" info:`
cx=`convert xc: -format "%[fx:$w/2]" info:`
cy=`convert xc: -format "%[fx:$h/2]" info:`

if [ "$coords" != "" ]; then
	# separate input coords
	# first pattern below replaces all occurrences of commas and spaces with a space => 1 2 3 4 5 6
	# second pattern below replaces the first occurrence of a space with a comma => 1,2[ 3 4][ 5 6] - ignore [], they are for emphasis only
	# third pattern below looks for all space number space number pairs and replaces them with a space followed by number1,number2 => 1,2 3,4 5,6
	set - `echo "$coords" | sed 's/[, ][, ]*/ /g; s/ /,/; s/ \([^ ]*\) \([^ ]*\)/ \1,\2/g'`
	
	# test for valid integers for x and y
	index=0
	plist=""
	while [ $# -gt 0 ]
		do
		testIntegerPair $1
		plist="$plist $1"
		shift
		index=`expr $index + 1`
	done
	
	#remove leading space from plist
	plist=`echo "$plist" | sed -n 's/ [ ]*\(.*\)/\1/p'`
	
	# test validity
	[ "$plist" = "" ] && errMsg "--- NO POINTS WERE PROVIDED ---"
	[ $index -ne 4 ] && errMsg "--- FOUR AND ONLY FOUR POINTS MUST BE PROVIDED ---"
	
	# put list into an array
	pArray=($plist)
	
	# separate x,y coordinates
	x1=`echo "${pArray[0]}," | cut -d, -f1`
	y1=`echo "${pArray[0]}," | cut -d, -f2`
	x2=`echo "${pArray[1]}," | cut -d, -f1`
	y2=`echo "${pArray[1]}," | cut -d, -f2`
	x3=`echo "${pArray[2]}," | cut -d, -f1`
	y3=`echo "${pArray[2]}," | cut -d, -f2`
	x4=`echo "${pArray[3]}," | cut -d, -f1`
	y4=`echo "${pArray[3]}," | cut -d, -f2`
	
	# compute the aspect ratio if not provided
	if [ "$aspect" = "" ]; then
		m1x=$x4
		m1y=$y4
		m2x=$x3
		m2y=$y3
		m3x=$x1
		m3y=$y1
		m4x=$x2
		m4y=$y2
		# get centroid of quadrilateral
		ccx=`convert xc: -format "%[fx:($m1x+$m2x+$m3x+$m4x)/4]" info:`
		ccy=`convert xc: -format "%[fx:($m1y+$m2y+$m3y+$m4y)/4]" info:`
		# convert to proper x,y coordinates relative to center
		m1x=`convert xc: -format "%[fx:$m1x-$ccx]" info:`
		m1y=`convert xc: -format "%[fx:$ccy-$m1y]" info:`
		m2x=`convert xc: -format "%[fx:$m2x-$ccx]" info:`
		m2y=`convert xc: -format "%[fx:$ccy-$m2y]" info:`
		m3x=`convert xc: -format "%[fx:$m3x-$ccx]" info:`
		m3y=`convert xc: -format "%[fx:$ccy-$m3y]" info:`
		m4x=`convert xc: -format "%[fx:$m4x-$ccx]" info:`
		m4y=`convert xc: -format "%[fx:$ccy-$m4y]" info:`
		#simplified equations, assuming u0=0, v0=0, s=1
		k2=`echo "scale=5; (($m1y - $m4y)*$m3x - ($m1x - $m4x)*$m3y + $m1x*$m4y - $m1y*$m4x)/(($m2y- $m4y)*$m3x - ($m2x - $m4x)*$m3y + $m2x*$m4y - $m2y*$m4x)" | bc`
		k3=`echo "scale=5; (($m1y - $m4y)*$m2x - ($m1x - $m4x)*$m2y + $m1x*$m4y - $m1y*$m4x)/(($m3y- $m4y)*$m2x - ($m3x - $m4x)*$m2y + $m3x*$m4y - $m3y*$m4x)" | bc`
		ff=`echo "scale=5; (($k3*$m3y - $m1y)*($k2*$m2y - $m1y) + ($k3*$m3x - $m1x)*($k2*$m2x- $m1x))/(($k3 - 1)*($k2 - 1))" | bc`
		if [ "$ff" = "" ]; then
			errMsg "--- ASPECT RATIO CANNOT BE DETERMINED ---"
		else
			# sqrt( $ff*$ff) = abs($ff)
			f=`echo "scale=5; sqrt( sqrt( $ff*$ff) )" | bc`
			aspect=`echo "scale=5; sqrt((($k2 - 1)^2 + ($k2*$m2y - $m1y)^2/$f^2 + ($k2*$m2x - $m1x)^2/$f^2)/(($k3 - 1)^2 + ($k3*$m3y - $m1y)^2/$f^2 + ($k3*$m3x - $m1x)^2/$f^2))" | bc`
		fi
	fi

	if [ "$dimensions" != "" -a "$ww" = "" -a "$hh" != "" ]; then
		# compute output size from aspect ratio and hh
		height=$hh
		width=`convert xc: -format "%[fx:$height*$aspect]" info:`
	elif [ "$dimensions" != "" -a "$ww" != "" -a "$hh" = "" ]; then
		# compute output size from aspect ratio and ww
		width=$ww
		height=`convert xc: -format "%[fx:$width/$aspect]" info:`
	elif [ "$dimensions" != "" -a "$ww" != "" -a "$hh" != "" ]; then
		# output size is ww x hh
		width=$ww
		height=$hh		
	else
		# compute output size from aspect ratio, magnify and left edge
		height=`convert xc: -format "%[fx:$magnify*hypot(($x1-$x4),($y1-$y4))]" info:`
		width=`convert xc: -format "%[fx:$aspect*$height]" info:`
	fi
	
	# compute corresponding corners of output image
	xx1=0
	yy1=0
	xx2=$width
	yy2=0
	xx3=$width
	yy3=$height
	xx4=0
	yy4=$height
	
	# setup perspective conjugate control points src,dst pairs
	coords="$x1,$y1 $xx1,$yy1  $x2,$y2 $xx2,$yy2  $x3,$y3 $xx3,$yy3  $x4,$y4 $xx4,$yy4"
#echo "coords=$coords"
else
	if [ "$dimensions" != "" -a "$ww" = "" -a "$hh" != "" ]; then
		# compute output size from aspect ratio and hh
		aspect=`convert xc: -format "%[fx:$w/$h]" info:`
		height=$hh
		width=`convert xc: -format "%[fx:$height*$aspect]" info:`
		magx=`convert xc: -format "%[fx:$width/$w]" info:`
		magy=`convert xc: -format "%[fx:$height/$h]" info:`
	elif [ "$dimensions" != "" -a "$ww" != "" -a "$hh" = "" ]; then
		# compute output size from aspect ratio and ww
		aspect=`convert xc: -format "%[fx:$w/$h]" info:`
		width=$ww
		height=`convert xc: -format "%[fx:$width/$aspect]" info:`
		magx=`convert xc: -format "%[fx:$width/$w]" info:`
		magy=`convert xc: -format "%[fx:$height/$h]" info:`
	elif [ "$dimensions" != "" -a "$ww" != "" -a "$hh" != "" ]; then
		# output size is ww x hh
		width=$ww
		height=$hh
		magx=`convert xc: -format "%[fx:$width/$w]" info:`
		magy=`convert xc: -format "%[fx:$height/$h]" info:`
	else
		# use image width and height multiplied by magnify
		width=`convert xc: -format "%[fx:$magnify*$w]" info:`
		height=`convert xc: -format "%[fx:$magnify*$h]" info:`
	fi

	# compute offsets for virtual canvas
	delx=`convert xc: -format "%[fx:($w-$width)/2]" info:`
	dely=`convert xc: -format "%[fx:($h-$height)/2]" info:`
	signx=`convert xc: -format "%[fx:sign($delx)]" info:`
	signy=`convert xc: -format "%[fx:sign($dely)]" info:`
	[ "$signx" = "-1" ] && delx="$delx" || delx="+$delx"
	[ "$signy" = "-1" ] && dely="$dely" || dely="+$dely"
fi

# setup sharpening
if [ "$sharpamt" = "0" -o "$sharpamt" = "0.0" ]; then
	sharpening=""
else
	sharpening="-sharpen 0x${sharpamt}"
fi

# setup blurring
if [ "$threshold" = "" ]; then
	blurring=""
else
	blurring="-blur 1x65535 -level ${threshold}x100%"
fi

# setup modulation
if [ $saturation -eq 100 ]; then
	modulation=""
else
	modulation="-modulate 100,$saturation"
fi

# do enhance
if [ "$enhance" = "stretch" ]; then
	convert $tmpA1 $setcspace -contrast-stretch 0 $tmpA1
elif [ "$enhance" = "whitebalance" ]; then
	whiteBalance "$tmpA1"
elif [ "$enhance" = "both" ]; then
	convert $tmpA1 $setcspace -contrast-stretch 0 $tmpA1	
	whiteBalance "$tmpA1"
fi

# process image
if [ "$coords" = "" -a "$magnify" = "1" -a "$dimensions" = "" ]; then
	convert \( $tmpA1 \) \
		\( -clone 0 -colorspace gray -negate -lat ${filtersize}x${filtersize}+${offset}% $setcspace -contrast-stretch 0 $blurring \) \
		-compose copy_opacity -composite -fill "$wcolor" -opaque none -alpha off \
		$sharpening $modulation \
		"$outfile"
elif [ "$coords" = "" -a "$magnify" != "1" ]; then
	convert \( $tmpA1 -virtual-pixel white -set option:distort:viewport ${width}x${height}${delx}${dely} -distort SRT "$magnify 0" \) \
		\( -clone 0 -colorspace gray -negate -lat ${filtersize}x${filtersize}+${offset}% $setcspace -contrast-stretch 0 $blurring \) \
		-compose copy_opacity -composite -fill "$wcolor" -opaque none -alpha off \
		$sharpening $modulation \
		"$outfile"
elif [ "$coords" = "" -a "$dimensions" != "" ]; then
	convert \( $tmpA1 -virtual-pixel white -set option:distort:viewport ${width}x${height}${delx}${dely} -distort SRT "$cx,$cy $magx,$magy 0" \) \
		\( -clone 0 -colorspace gray -negate -lat ${filtersize}x${filtersize}+${offset}% $setcspace -contrast-stretch 0 $blurring \) \
		-compose copy_opacity -composite -fill "$wcolor" -opaque none -alpha off \
		$sharpening $modulation \
		"$outfile"
elif [ "$coords" != "" ]; then
	convert \( $tmpA1 -virtual-pixel white -set option:distort:viewport ${width}x${height}+0+0 -distort perspective "$coords" \) \
		\( -clone 0 -colorspace gray -negate -lat ${filtersize}x${filtersize}+${offset}% $setcspace -contrast-stretch 0 $blurring \) \
		-compose copy_opacity -composite -fill "$wcolor" -opaque none -alpha off \
		$sharpening $modulation \
		"$outfile"
else
	errMsg = "--- INVALID SITUATION ---"
fi
exit 0