15

I am trying to select a few art images to fit some new frames. I would like to be able to sort these images by proximity to a particular aspect ratio and then view them in decreasing order of proximity through something like feh.

Is there some glorious one-line Unix expression? Otherwise a few dozen lines of Python should clear this up.

2
  • 3
    You can pull the aspect ratio via ImageMagick, e.g. identify -format "%[fx:w/h]:%M\n" *.jpg. I dunno how to easily sort by proximity to an arbitrary value. sort does not seem to support such sorting.
    – N.N.
    Oct 8, 2012 at 16:54
  • Well, basically, you'd want to convert those numbers to an integer distance metric, and then you could pass it to sort -n. It appears you can actually do all the math with ImageMagick.
    – derobert
    Oct 8, 2012 at 17:31

6 Answers 6

9

Using N.N.'s ImageMagick comment as a lead, it turns out you can have it do arbitrary computations as part of its -format output. So, one possible way:

identify -format "%[fx:round(100000*abs((4/3)-(w/h)))]:%M\n" *.jpg | \
    sort -n -k1 -t:

should do what you want. Note that 4/3 in there is a constant, the desired aspect ratio. You could of course find that as the output of a previous identify (to matches "closest to this image").

To explain, we're taking the plain difference between the desired and actual aspect ration. Then multiplying by a large number, and rounding. The multiply and round is because sort only handles integers, not rationals.

edit

It turns out sort does appear to do rationals, not just integers. Its even specified to in the Single Unix Specification (v4). So, you can get rid of that workaround:

identify -format "%[fx:abs((4/3)-(w/h))]:%M\n" *.jpg | \
    sort -n -k1 -t:
6
  • Where did you find the documentation on identify's -format option? I saw nothing in the man page and spent quite some time trying to figure the darn thing out :).
    – terdon
    Oct 8, 2012 at 17:57
  • Oh, and on my system your command does not seem to work. I get 33333:cover.jpg where cover.jpg is 500x500.
    – terdon
    Oct 8, 2012 at 17:58
  • 2
    @terdon well, that's because it has a ratio of 1/1 not 4/3. 4/3-1 = 1/3 = .333…. Just change that 4/3 to the ratio you want (which sounds like its 1/1). The docs are at imagemagick.org/script/fx.php in a typically confusing manner. Took me a while to find them, too.
    – derobert
    Oct 8, 2012 at 18:01
  • Thanks for the link. As for the rest, I was being an idiot. I only had one jpg in my test folder (the rest are pngs) so since I saw only one output line I assumed that was the desired result and that identify was claiming that image had a 4/3 aspect ratio. That's what I get for trying to think on Monday evenings... +1 for an elegant solution.
    – terdon
    Oct 8, 2012 at 18:07
  • Nice answer! Is there a way to append some sort of label to the filenames of all images not adhering to the desired ratio?
    – Mentalist
    Nov 5, 2018 at 8:33
3

Well, the easiest way to do this would be to use ImageMagick. It should be in the repositories of your Linux distribution, for debian based systems, run:

sudo apt-get install imagemagick

One of the programs of the ImageMagick suite is identify, this will print the characteristics of a list of input image files. Combining it with sort will give you a list of images sorted by size (you can change png for whatever extension(s) you have:

identify *png | sort -gk 3

If you actually need the aspect ratio and not just the size, try something like this:

  1. Simple option, assumes your image names have no spaces:

    identify *png *jpg *gif | \
    gawk '{split($3,sizes,"x"); print $1,sizes[1]/sizes[2]}' | \
    sed 's/\[.\]//' | sort -gk 3
    

    The gawk command splits the 3rd field (the image size which has the format LxH) into the array "sizes" and then prints the 1st field (the image name) and the result of dividing the image's length by its height. The sed command is just beautifying the output and the sort command sorts the result according to image size ratio.

  2. More complex, this one can deal with spaces in file names:

    find . \( -iname  "*png" -o -iname "*jpg" -o -iname "*gif" \) -exec identify {} \; |\
    perl -ne '/(.+?)\s+[A-Z]{3}\s+(\d+)x(\d+)/; print "$1 ", $2/$3, "\n"' | \
    sort -gk 2
    

    Here we are using find to identify the files we are interested in and run the identify command, and then piping its output through a little PERL script. The regular expression looks for three capital letters ([A-Z]{3}) which should be the image format. Once we have found that, it is easy to identify the image name and dimensions.

    I am not using gawk here because the presence of spaces in the input file names will confuse the field numbers. Finally, the script will print out the image's name and the result of the length/height division which we sort numerically.

If simply browsing the available aspect ratios is not enough, if you have at least one image with the desired aspect ratio, just use grep to extract those images whose ratio is closest:

identify *png *jpg *gif | \
gawk '{split($3,sizes,"x"); print $1,sizes[1]/sizes[2]}' |\
sed 's/\[.\]//' | sort -gk 3 | grep -C 10 GOOD_IMAGE.jpg
2

You can compute the difference of each aspect ratio with the reference. Looks like derobert's answer, but simpler:

ref=4/3
identify -format "%[fx:abs(w/h - $ref)] %M\n" *.jpg | sort -n -k1
1
  • Hmm, apparently sort does do rationals. I was under the impression it did not...
    – derobert
    Oct 8, 2012 at 18:22
2

An aspect ratio is just a number, so you can browse the images according to their names, if the name is prefixed with the aspect-ratio. Creating symbolic links with the aspect-ratio pre-pended allows you to browse the images sorted by aspect-ratio.

The following viewers work as expected. (feh doesn't forward/back, even for real images - on my system)

  • comix
  • eog (Eye of Gnome / Image Viewer)
  • gwenview

picd='/media/dat_ext4/pictures/jpg/misc'
srtd="$picd/ar-sort"   # directory to hold "sorted" symbolic links
mkdir -p "$srtd"              

find "$picd" -maxdepth 1 -type f -name "*" -print0 | 
  while IFS= read -d $'\0' -r file ; do
    [[ $(file -ib "$file") != image* ]] && continue  # skip non-image files
    ar="$(identify -format "%[fx:w/h]" "$file")"  
    slink="$(printf '%s/%06.4f %s' "$srtd" "$ar" "$(basename "$file")")"
    # make symbolic link in 'ar-sort' directory
    ln -s "$file" "$slink"  
    echo "$slink" 
  done 

Go into the ar-sort directory and just browse from whatever starting point (aspect-ratio) you are interested in.

The symbolic-link names look like this:

0.6732 Gold Bars.jpg
0.7242 Light Bulb.jpg
0.8022 Escher - Waterfall.jpg
1.3696 Old Typewriter.jpg
1.6000 King Tut.jpg
1
1

My usual line of reasoning is to use squared error distance metric wherever I hear words "degree of proximity". One can surely use absolute values, and it would be faster. The script takes as an argument the ideal aspect ratio, processes all *.jpg files in the current directory and outputs them sorted in the increasing order of divergence from the ideal ratio.

head removes trailing end-of-line character inserted by ImageMagick.

The script contains a gawk command which sets output field separator to an unlikely character, computes squared difference and prints back the pair difference|file name. Sort is performed and unnecessary information (squared aspect ratio difference) is culled by cut.

#!/bin/sh
if [ x"$1" = "x" ] ; then
  echo "Usage: $0 TargetAspectRatio" >&2
  exit 2
fi
ASPECT_CMD="BEGIN{OFS=\"|\";}\
{\
print (\$1-$1)^2, \$2;\
}"
identify -format "%[fx:w/h]:%M\n" *.jpg | head -n-1 | gawk -F":" "$ASPECT_CMD" | sort -k1 -t"|" | cut -f2 -d"|"
2
  • Please note that it uses quadratic distance and computations within gawk/awk :) (Thanks to @N.N. for the initial shortcut) Oct 8, 2012 at 18:19
  • Could you please add an explanation of the script to your answer? It would make it better.
    – N.N.
    Oct 8, 2012 at 20:10
1

The top answer by derobert is wrong in one important regard -- although there isn't a clear definition of what "aspect ratio proximity" means, their algorithm uses absolute difference which has one undesirable property.

Let's say the desired aspect ratio is 1:1 (1.0), for simplicity. If we then take two images, first one 1:2 (0.5) and second one 2:1 (2.0), taking the absolute difference tells us that the second one (abs(1.0-2.0) == 1.0) is twice as far away from 1.0 than the first one (abs(1.0-0.5) == 0.5), even though intuitively they should be the same distance, just in an opposite direction!

That's why I propose using a logarithm instead. Since abs(log(x/y)) == abs(log(y/x)), the ratios x/y and y/x will have the same "score":

identify -format "%[fx:abs(log(w/h))]:%M\n" *.jpg | sort -n -k1 -t:

And to use a different ratio than 1:1, let's say 3:4, simply take the difference of logarithms:

identify -format "%[fx:abs(log(w/h)-log(3/4))]:%M\n" *.jpg | sort -n -k1 -t:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .