Image selection by aspect ratio and pixel count

On this page:

Aspect ratio

Here's a simple shell script that will display the aspect ratio of an image file, 100 being a square image. It can also be used to select and copy images that are within a certain range.

History:
v1.0 — 2010/11/24 — Original release
v1.1 — 2010/11/25 — Rewritten to use getopts
#! /bin/bash
# Ratio.sh
# This script displays the image ratio of an image (or images)
# It relies on ImageMagick (or possibly GraphicsMagick) being available
# It can also selectively copy images for a range of values
# (c) Guillaume Dargaud - http://www.gdargaud.net/
# Last modification: 20010/11/25

# Options to pass to the copy command (see 'man cp')
CpOpt=-vi

# You shouldn't have to change anything below that
ShowName=1
ShowRatio=1
Min=0
Max=9999999

usage() {
cat << EOF
USAGE: $0 [Options] File [Files...]
  Gives the aspect ratio of an image (100*width/height), 100 being square and more than 100 for landscape frames.
OPTIONS:	
  -n	Display filenames but not the aspect ratio
  -x	Display aspect ratio but not the filenames
  -r	Display 'Ratio Filename' instead of 'Filename Ratio', allowing you to pipe to 'sort -n'
  -g N	Only display/copy if the ratio is greater then the value given (inclusive)
  -l N	Only display/copy if the ratio is lower then the value given (inclusive)
  -c Dir	Copy images that are within requested -g/-l values to a specific directory
EXAMPLES:
  $0 -g \$((100*16/9)) *.jpg   to list panoramic images above the 16:9 ratio
  $0 -c /tmp/tv -g 133 -l 133 *.tif   to copy all 4:3 images to /tmp/tv
EOF
}

while getopts hnxrg:l:c: opt; do
  case $opt in
	n) unset ShowRatio;;
	x) unset ShowName;;
	r) Reverse=1;;
	g) Min=$OPTARG;;
	l) Max=$OPTARG;;
	c) Dest=$OPTARG;;
	h) usage; exit 1;;
    ?) echo "Invalid option: -$OPTARG" >&2; exit 1;;
  esac
done

shift $(($OPTIND - 1)) 

if [ $# -eq 0 ]; then usage; exit 1; fi

if [ "$ShowName" -a  "$ShowRatio" ]; then Join=" "; fi

while [ "$1" != "" ]; do
  Ratio=$(($(identify -format "100 * %w / %h\n" "$1")))
  if [ $Min -le $Ratio -a $Ratio -le $Max ]; then 
	# Deals with the displayed line
	if [ $ShowName ]; then Name="$1"; fi
	if [ $ShowRatio ]; then R=$Ratio; fi
	if [ "$ShowName" -o  "$ShowRatio" ]; then 
	  if [ $Reverse ]; then echo "$R$Join$Name"; else echo "$Name$Join$R"; fi
	fi
	# Deal with the file copy
	if [ $Dest ]; then cp $CpOpt "$1" "$Dest/"; fi
  fi
  shift
done


Pixel count

Here's a second similar shell script that will display the number of pixels of an image file. It can also be used to select and copy images that are within a certain range, and to make size conversions based on final number of pixels while maintaining aspect ratio.

History:
v1.0 — 2010/11/24 — Original release
v1.1 — 2010/11/25 — Rewritten to use getopts
#! /bin/bash
# MPix.sh
# This script displays the mega-pixel count of an image (or images)
# It relies on ImageMagick (or possibly GraphicsMagick) and bc being available
# It can also selectively copy/convert images for a range of values
# (c) Guillaume Dargaud - http://www.gdargaud.net/
# Last modification: 20010/11/25

# Options to pass to the copy command (see 'man cp')
CpOpt=-vi

# Additional options to pass to ImageMagick convert
#ImgOpt=-quality 92
ImgOpt=

# You shouldn't have to change anything below that
ShowName=1
ShowCount=1
Min=0
Max=9999999999
Divider="/1024/1024"

usage() {
cat << EOF
USAGE: $0 [Options] File [Files...]
  Gives the pixel count of an image (10 for 10 million pixels).
OPTIONS:	
  -n	Display filenames but not the pixel count
  -x	Display pixel count but not the filenames
  -r	Display 'MPix Filename' instead of 'Filename MPix', allowing you to pipe to 'sort -n'
  -m	Pixel count is in megapixels (default)
  -k	Pixel count is in kilopixels
  -p	Pixel count is in pixels
  -g N	Only display/copy if the pixel count is greater then the value given (inclusive)
  -l N	Only display/copy if the pixel count is lower then the value given (inclusive)
  -c Dir	Copy images that are within requested -g/-l values to a specific directory
  -t Nb	When used in addition to -c, will downsize the image to a given [mega][kilo]pixel count (or copied instead of enlarged)
EXAMPLES:
  $0 -prg \$((1024*768)) *.jpg | sort -n   to sort images bigger than 1024x768 pixels by pixel count
  $0 -c /tmp/tv -t 2 -g 10 -l 12 *   to copy and convert images in the 10~12 MPix range to 2 MPix in /tmp/tv
EOF
}

while getopts hnxrmkpg:l:c:t: opt; do
  case $opt in
	n) unset ShowCount;;
	x) unset ShowName;;
	r) Reverse=1;;
	m) M=1;;
	k) K=1; Divider="/1024";;
	p) P=1; Divider="";;
	g) Min=$OPTARG;;
	l) Max=$OPTARG;;
	c) Dest=$OPTARG;;
	t) Trans=$OPTARG;;
	h) usage; exit 1;;
    ?) echo "Invalid option: -$OPTARG" >&2; exit 1;;
  esac
done

shift $(($OPTIND - 1)) 

if [ $# -eq 0 ]; then usage; exit 1; fi

if [ "$ShowName" -a  "$ShowCount" ]; then Join=" "; fi

while [ "$1" != "" ]; do
  Count=$(($(identify -format "%w * %h$Divider\n" "$1")))
  if [ $Min -le $Count -a $Count -le $Max ]; then 
	# Deals with the displayed line
	if [ $ShowName ]; then Name="$1"; fi
	if [ $ShowCount ]; then Cnt=$Count; fi
	if [ "$ShowName" -o  "$ShowCount" ]; then 
	  if [ $Reverse ]; then echo "$Cnt$Join$Name"; else echo "$Name$Join$Cnt"; fi
	fi

	# Deals with file copy / conversion
	if [ $Dest ]; then 
	  if [ $Trans ]; then	# Convert
		DestFile="$Dest/$(basename "$1")"
		Count=$(identify -format "%w * %h" "$1" | bc -l)
		if [ $M ]; then Mult="1024*1024"; fi
		if [ $K ]; then Mult="1024"; fi
		if [ $P ]; then Mult="1"; fi
		# bash arithmetics only work with integers, but here we need a sqrt, so we use bc
		RS=$( echo "100 * sqrt ( $Trans * $Mult / $Count ) + 0.5" | bc -l | sed -e "s/\..*//")
		if [ $RS -ge 100 ]; then	# Simple copy
		  cp $CpOpt "$1" "$Dest/"; 
		else	# Resampling
		  convert "$1" -resize $RS% $ImgOpt "$DestFile"
		fi
		
	  else	# Only copy
		cp $CpOpt "$1" "$Dest/"; 
	  fi
	fi
  fi
  shift
done


Getopts mini-tutorial

The bash shell built-in getopts is used to read command line parameters passed to a script. It can read single letter parameters, single letter parameters with a single argument, possibly repeated single letter parameters, possibly joined, but not long parameters. For instance, it can read: script -h -vv -f "my file" arg1 arg2, in that case the parameters are h, f and v (twice). But it can't read -long --long -f File1 File2... Here's how you can use it, with a simplified example:

#! /bin/bash
function usage {
cat << EOF
  USAGE: $0 [-v] [-h] [-f File] [args...]
EOF
}

while getopts vf:h opt; do
# 'opt' is the variable that will contain each letter in turn.
# 'vf:h' describes the list of single letter parameters, 
# with a : if there's an optional argument (which ends up in $OPTARG)

  case $opt in
	v) echo "parameter -v";;	 # the order of vgh doesn't matter
	f) echo "parameter -f with argument $OPTARG";;        # Here we make use of the argument passed along -f
	h) usage; exit 1;;
	?) echo "Invalid option: -$OPTARG" >&2; exit 1;;
  esac
done

shift $(($OPTIND - 1)) # This is necessary so that now the remaining arguments end up as $1, $2...

if [ $# -eq 0 ]; then usage; exit 1; fi		# No other arguments, may or may not be a good thing

while [ "$1" != "" ]; do
        echo "Argument: $1" # Now list all the remaining arguments
        shift           	# Next argument
done

There are a few other options. For more info you can try help getopts since man doesn't usually work for builtins. And here's the result:

$ ./tmp -v
parameter -v
  USAGE: ./tmp [-v] [-h] [-f File] [args...]
$ ./tmp -vv -fFile1 -v -f "Big File" Arg1 Arg2 Arg3
parameter -v
parameter -v
parameter -f with argument File1
parameter -v
parameter -f with argument Big File
Argument: Arg1
Argument: Arg2
Argument: Arg3