403 lines
15 KiB
Plaintext
403 lines
15 KiB
Plaintext
|
#!/bin/bash
|
||
|
#
|
||
|
# Revised by Fred Weinhaus ...................... revised 8/24/2010
|
||
|
# Revised by Anthony Thyssen to add -b option ... revised 8/24/2010
|
||
|
# Developed by Fred Weinhaus 1/30/2010 .......... revised 7/7/2010
|
||
|
#
|
||
|
# USAGE: multicrop [-c coords] [-b bcolor] [-f fuzzval] [-g grid] [-u unrotate] [-m mask] infile outfile
|
||
|
# USAGE: multicrop [-h or -help]
|
||
|
#
|
||
|
# OPTIONS:
|
||
|
#
|
||
|
# -c coords pixel coordinate to extract background color;
|
||
|
# may be expressed as gravity value (NorthWest, etc)
|
||
|
# or as "x,y" value; default is NorthWest=(0,0)
|
||
|
# -b bcolor background color to use instead of option -c;
|
||
|
# any valid IM color; default is to use option -c
|
||
|
# -f fuzzval fuzz value for separating background color;
|
||
|
# expressed as (integer) percent 0 to 100;
|
||
|
# default=0 (uniform color)
|
||
|
# -g grid grid spacing in both x and y as percent of
|
||
|
# image width and height; used to locate images;
|
||
|
# integer>0; default=10;
|
||
|
# -u unrotate unrotate method; choices are 1 for -deskew,
|
||
|
# 2 for unrotate script and 3 for no unrotate;
|
||
|
# default=1
|
||
|
# -m mask mask presentation method; choices are view,
|
||
|
# save (to file) or output mask only; default
|
||
|
# is none of the above, just output the images
|
||
|
#
|
||
|
###
|
||
|
#
|
||
|
# NAME: MULTICROP
|
||
|
#
|
||
|
# PURPOSE: To crop and unrotate multiple images from a scanned image.
|
||
|
#
|
||
|
# DESCRIPTION: MULTICROP crops and unrotates multiple images from a scanned image.
|
||
|
# The images must be well separate so that background color shows between them.
|
||
|
# The process uses a floofill technique based upon a seed coordinate and a fuzz
|
||
|
# value to separate the individual images from the background of the scan.
|
||
|
# The correct choice of fuzz factor is very important. If too small, the images
|
||
|
# will not be separate. If too larger, parts of the outer area of the image
|
||
|
# containing similar colors will be lost and the image may be separated into
|
||
|
# multiple parts. There are two unrotate methods. The first uses the IM deskew
|
||
|
# function, but is limited to 5 degrees of rotate or less. The second uses my
|
||
|
# unrotate script. It allows much larger rotations, but will be slower. If
|
||
|
# using the second method, my unrotate script must be downloaded and installed.
|
||
|
#
|
||
|
# IMPORTANT: The images in the scanned file must be well separated in x and y
|
||
|
# so that their bounding boxes do not overlap. This is especially important
|
||
|
# if the images have a significant rotation.
|
||
|
#
|
||
|
# The output images will be named from the specified outfile and -0, -1,
|
||
|
# etc, will be appended before the .suffix.
|
||
|
#
|
||
|
# Arguments:
|
||
|
#
|
||
|
# -c coords ... COORDS is any location within the background (non-image) area
|
||
|
# for the algorithm to find the background color. It may be specified in terms
|
||
|
# of gravity parameters (NorthWest, North, NorthEast, East, SouthEast, South,
|
||
|
# SouthWest or West) or as a pixel coordinate "x,y". The default is the
|
||
|
# upper left corner = NorthWest = "0,0".
|
||
|
#
|
||
|
# -b bcolor ... BCOLOR is the background color to use for flood fill instead
|
||
|
# of extracting this color from the image. This is useful when an image has
|
||
|
# no borders with sub-images hard against the edges. Any valid IM color is
|
||
|
# allowed. The default is to use option -c.
|
||
|
#
|
||
|
# -f fuzzval ... FUZZVAL is the fuzz amount specified as an integer percent
|
||
|
# value between 0 to 100 (without the % sign). # The correct choice of fuzz
|
||
|
# factor is very important. If too small, the images will not be separate.
|
||
|
# If too larger, parts of the outer area of the image containing similar
|
||
|
# colors will be lost and the image may be separated into multiple parts.
|
||
|
# Typical values are probably between 5 and 20 percent. The default=10
|
||
|
#
|
||
|
# -g grid ... GRID is the grid spacing for testing points in the input image
|
||
|
# to see if they are background or image. The grid value is specified as an
|
||
|
# integer percent greater than 0 and less than 100 of the width and height
|
||
|
# of the input image. The default=10.
|
||
|
#
|
||
|
# -u unrotate ... UNROTATE is the unrotation method. Choices are: 1, 2 or 3.
|
||
|
# The default is unrotate=1, which is fast and uses the IM -deskew function,
|
||
|
# but is limited to images that are rotated no more than 5 degrees in the scan.
|
||
|
# Option unrotate=2 uses my unrotate script. It can handle larger rotations,
|
||
|
# but is slower. If using the latter method, my unrotate script must be
|
||
|
# downloaded and also installed so that it is available for this script to use.
|
||
|
# Option unrotate=3 makes no attempt to unrotate the images.
|
||
|
#
|
||
|
# -m mask ... MASK provides several options for reviewing the initial mask that
|
||
|
# is generated by the fuzz value. The choices are: view (display to X11 window),
|
||
|
# save (to disk) along with the images, or output (without processing the images).
|
||
|
# The default is to simply process the images without showing or saving the mask.
|
||
|
# If using the view mode, then processing will stop until the image is closed.
|
||
|
# But this allows you to then kill the script if the mask is not appropriate.
|
||
|
# A good approach is to use the output mode repeatedly with various fuzzvals
|
||
|
# until a reasonable mask is created. Note that the mask must separate the
|
||
|
# images, but the background can "eat" a little into the images so long as no
|
||
|
# full edge is lost or the images is split into multiple parts.
|
||
|
#
|
||
|
# NOTE: If using unrotate method 2, then my script, unrotate, is required
|
||
|
# as well.
|
||
|
#
|
||
|
# 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="0,0" # initial coord for finding background color
|
||
|
bcolor="" # initial background color
|
||
|
fuzzval=10 # fuzz amount in percent for making background transparent
|
||
|
grid=10 # grid spacing in percent image
|
||
|
mask="" # view, save, output
|
||
|
unrotate=1 # 1=deskew 2=unrotate
|
||
|
|
||
|
# 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 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME"
|
||
|
}
|
||
|
usage2()
|
||
|
{
|
||
|
echo >&2 ""
|
||
|
echo >&2 "$PROGNAME:" "$@"
|
||
|
sed >&2 -n '/^######/q; /^#/!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 14 ]
|
||
|
then
|
||
|
errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---"
|
||
|
else
|
||
|
while [ $# -gt 0 ]
|
||
|
do
|
||
|
# get parameters
|
||
|
case "$1" in
|
||
|
-h|-help) # help information
|
||
|
echo ""
|
||
|
usage2
|
||
|
;;
|
||
|
-f) # fuzzval
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID FUZZVAL SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
fuzzval=`expr "$1" : '\([0-9]*\)'`
|
||
|
[ "$fuzzval" = "" ] && errMsg "--- FUZZVAL=$fuzzval MUST BE A NON-NEGATIVE INTEGER VALUE (with no sign) ---"
|
||
|
fuzzvaltestA=`echo "$fuzzval < 0" | bc`
|
||
|
fuzzvaltestB=`echo "$fuzzval > 100" | bc`
|
||
|
[ $fuzzvaltestA -eq 1 -a $fuzzvaltestB -eq 1 ] && errMsg "--- FUZZVAL=$fuzzval MUST BE A NON-NEGATIVE INTEGER VALUE BETWEEN 0 AND 100 ---"
|
||
|
;;
|
||
|
-c) # coords
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID COORDS SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
coords=$1
|
||
|
# further testing done later
|
||
|
;;
|
||
|
-b) # coords
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID COORDS SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
bcolor=$1
|
||
|
;;
|
||
|
-g) # grid
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID GRID SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
grid=`expr "$1" : '\([0-9]*\)'`
|
||
|
[ "$grid" = "" ] && errMsg "--- GRID=$grid MUST BE A NON-NEGATIVE INTEGER VALUE (with no sign) ---"
|
||
|
gridtestA=`echo "$grid <= 0" | bc`
|
||
|
gridtestB=`echo "$grid >= 100" | bc`
|
||
|
[ $gridtestA -eq 1 -a $gridtestB -eq 1 ] && errMsg "--- GRID=$grid MUST BE A NON-NEGATIVE INTEGER VALUE LARGER THAN 0 AND SMALLER THAN 100 ---"
|
||
|
;;
|
||
|
-u) # unrotate
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID UNROTATE SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
unrotate=`expr "$1" : '\([0-9]\)'`
|
||
|
[ $unrotate -lt 1 -a $unrotate -gt 3 ] && errMsg "--- UNROTATE=$unrotate MUST BE EITHER 1, 2 OR 3 ---"
|
||
|
;;
|
||
|
-m) # mask
|
||
|
shift # to get the next parameter
|
||
|
# test if parameter starts with minus sign
|
||
|
errorMsg="--- INVALID MASK SPECIFICATION ---"
|
||
|
checkMinus "$1"
|
||
|
mask=`echo "$1" | tr "[:upper:]" "[:lower:]"`
|
||
|
[ "$mask" != "view" -a "$mask" != "save" -a "$mask" != "output" ] && errMsg "--- MASK=$mask MUST BE EITHER VIEW, SAVE OR OUTPUT ---"
|
||
|
;;
|
||
|
-) # 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 if both bcolor and coords specified at the same time
|
||
|
if [ "X$bcolor" != "X" -a "X$coods" != "X" ]; then
|
||
|
errMsg "--- BACKGROUND COLOR AND COODINATES CAN NOT BE USED TOGETHER ---"
|
||
|
fi
|
||
|
|
||
|
# test that infile provided
|
||
|
[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED"
|
||
|
|
||
|
# test that outfile provided
|
||
|
[ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED"
|
||
|
|
||
|
# set up temp file
|
||
|
tmpA1="$dir/multicrop_1_$$.mpc"
|
||
|
tmpB1="$dir/multicrop_1_$$.cache"
|
||
|
tmpA2="$dir/multicrop_2_$$.mpc"
|
||
|
tmpB2="$dir/multicrop_2_$$.cache"
|
||
|
tmpA3="$dir/multicrop_3_$$.mpc"
|
||
|
tmpB3="$dir/multicrop_3_$$.cache"
|
||
|
tmpA4="$dir/multicrop_4_$$.mpc"
|
||
|
tmpB4="$dir/multicrop_4_$$.cache"
|
||
|
tmpA5="$dir/multicrop_5_$$.mpc"
|
||
|
tmpB5="$dir/multicrop_5_$$.cache"
|
||
|
|
||
|
trap "rm -f $tmpA1 $tmpB1 $tmpA2 $tmpB2 $tmpA3 $tmpB3 $tmpA4 $tmpB4 $tmpA5 $tmpB5; exit 0" 0
|
||
|
trap "rm -f $tmpA1 $tmpB1 $tmpA2 $tmpB2 $tmpA3 $tmpB3 $tmpA4 $tmpB4 $tmpA5 $tmpB5; exit 1" 1 2 3 15
|
||
|
|
||
|
|
||
|
# read the input image into the temp files and test validity.
|
||
|
convert -quiet -regard-warnings "$infile" +repage "$tmpA1" ||
|
||
|
errMsg "--- FILE $infile1 DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---"
|
||
|
|
||
|
# get output filename and suffix
|
||
|
outnameArr=(`echo "$outfile" | sed -n 's/^\(.*\)[.]\([^.]*\)$/\1 \2/p'`)
|
||
|
outname="${outnameArr[0]}"
|
||
|
suffix="${outnameArr[1]}"
|
||
|
#echo "outname=$outname"
|
||
|
#echo "suffix=$suffix"
|
||
|
|
||
|
|
||
|
# get image width and height
|
||
|
width=`identify -ping -format "%w" $tmpA1`
|
||
|
height=`identify -ping -format "%h" $tmpA1`
|
||
|
|
||
|
|
||
|
# get color at user specified location
|
||
|
if [ "X$bgcolor" != "X" ]; then
|
||
|
coords="0,0"
|
||
|
else
|
||
|
widthm1=`convert xc: -format "%[fx:$width-1]" info:`
|
||
|
heightm1=`convert xc: -format "%[fx:$height-1]" info:`
|
||
|
midwidth=`convert xc: -format "%[fx:round(($width-1))/2]" info:`
|
||
|
midheight=`convert xc: -format "%[fx:round(($height-1))/2]" info:`
|
||
|
coords=`echo "$coords" | tr "[:upper:]" "[:lower:]"`
|
||
|
case "$coords" in
|
||
|
''|nw|northwest) coords="0,0" ;;
|
||
|
n|north) coords="$midwidth,0" ;;
|
||
|
ne|northeast) coords="$widthm1,0" ;;
|
||
|
e|east) coords="$widthm1,$midheight" ;;
|
||
|
se|southeast) coords="$widthm1,$heightm1" ;;
|
||
|
s|south) coords="$midwidth,$heightm1" ;;
|
||
|
sw|southwest) coords="0,$heightm1" ;;
|
||
|
w|west) coords="0,$midheight" ;;
|
||
|
[0-9]*,[0-9]*) coords=$coords ;;
|
||
|
*) errMsg "--- INVALID COORDS ---" ;;
|
||
|
esac
|
||
|
bgcolor=`convert $tmpA1 -format "%[pixel:u.p{$coords}]" info:`
|
||
|
fi
|
||
|
#echo "bgcolor=$bgcolor"
|
||
|
|
||
|
|
||
|
# get grid spacing
|
||
|
wg=`convert xc: -format "%[fx:round($grid*$width/100)]" info:`
|
||
|
hg=`convert xc: -format "%[fx:round($grid*$height/100)]" info:`
|
||
|
num=`convert xc: -format "%[fx:round(100/$grid) - 2]" info:`
|
||
|
#echo "width=$width; height=$height; wg=$wg; hg=$hg; num=$num"
|
||
|
|
||
|
|
||
|
# add a border, and flood fill from all edges inward
|
||
|
convert $tmpA1 -fuzz ${fuzzval}% -fill none \
|
||
|
-bordercolor $bgcolor -border 1x1 \
|
||
|
-draw "matte $coords floodfill" \
|
||
|
-shave 1x1 -fill red +opaque none \
|
||
|
$tmpA2
|
||
|
|
||
|
if [ "$mask" = "view" ]; then
|
||
|
display $tmpA2
|
||
|
elif [ "$mask" = "save" ]; then
|
||
|
convert $tmpA2 ${outname}_mask.gif
|
||
|
elif [ "$mask" = "output" ]; then
|
||
|
convert $tmpA2 ${outname}_mask.gif
|
||
|
exit 0
|
||
|
fi
|
||
|
|
||
|
# set up for unrotate 1 or 3
|
||
|
if [ $unrotate -eq 1 ]; then
|
||
|
derotate="-deskew 40%"
|
||
|
elif [ $unrotate -eq 3 ]; then
|
||
|
derotate=""
|
||
|
fi
|
||
|
|
||
|
|
||
|
echo ""
|
||
|
# loop over grid and floodfill and trim to get individual mask for each image
|
||
|
k=0
|
||
|
y=0
|
||
|
for ((j=0;j<=$num;j++))
|
||
|
do
|
||
|
x=0
|
||
|
y=$(($y + $hg))
|
||
|
for ((i=0;i<=$num;i++))
|
||
|
do
|
||
|
x=$(($x + $wg))
|
||
|
# test if found color other than "none" (i.e. red)
|
||
|
testcolor=`convert $tmpA2 -channel rgba -alpha on -format \
|
||
|
"%[fx:u.p{$x,$y}=="none"?0:1]" info:`
|
||
|
# echo "$x $y $testcolor"
|
||
|
if [ $testcolor -eq 1 ]; then
|
||
|
echo "Processing Image $k"
|
||
|
# Take red and none mask.
|
||
|
# Floodfill the local red region with white.
|
||
|
convert $tmpA2 -channel rgba -alpha on -fill "white" \
|
||
|
-draw "color $x,$y floodfill" $tmpA3
|
||
|
# Fill anything not white with transparency and
|
||
|
# turn transparency off so black.
|
||
|
# Then clone and trim to bounds of white.
|
||
|
# Then fill any black with white.
|
||
|
# Then flatten back onto white and black image so that any white
|
||
|
# areas eaten away are filled with white.
|
||
|
# Note flatten uses the virtual canvas left by -trim so that it
|
||
|
# goes back into the right location.
|
||
|
convert \( $tmpA3 -channel rgba -alpha on \
|
||
|
-fill none +opaque white -alpha off \) \
|
||
|
\( +clone -trim -fill white -opaque black -write $tmpA5 \) \
|
||
|
-flatten $tmpA4
|
||
|
# Print size and page geometry
|
||
|
identify -ping -format " Size: %wx%h\n Page Geometry: %g" $tmpA5
|
||
|
# Composite the black and white mask onto the original scan.
|
||
|
# Then trim and deskew/unrotate to make the output.
|
||
|
if [ $unrotate -eq 1 -o $unrotate -eq 3 ]; then
|
||
|
convert $tmpA1 $tmpA4 -compose multiply -composite \
|
||
|
-fuzz ${fuzzval}% -trim -background "$bgcolor" $derotate \
|
||
|
-compose over -bordercolor "$bgcolor" -border 2 -trim +repage \
|
||
|
${outname}-${k}.${suffix}
|
||
|
elif [ $unrotate -eq 2 ]; then
|
||
|
convert $tmpA1 $tmpA4 -compose multiply -composite \
|
||
|
-fuzz ${fuzzval}% -trim miff:- | \
|
||
|
unrotate -f ${fuzzval}% - ${outname}-${k}.${suffix}
|
||
|
fi
|
||
|
# Fill the selected photo area in the red/none mask with none
|
||
|
# for use with next coordinate so that it does not find it again.
|
||
|
convert $tmpA3 -channel rgba -alpha on -fill none -opaque white $tmpA2
|
||
|
k=$(($k + 1))
|
||
|
fi
|
||
|
done
|
||
|
done
|
||
|
echo ""
|
||
|
exit 0
|
||
|
|
||
|
|
||
|
|