16

I'm running the following command:

mogrify -resize '400x400>' *.png

According to the docs, the "widthxheight>" geometry (with > at the end) "Shrinks an image with dimension(s) larger than the corresponding width and/or height argument(s)."

I expect the images with width/height less than 400x400 to be untouched, but mogrify rewrites the file for no reason―the modification date and file size are changed.

Is there a way to make it actually just leave the smaller images alone? I'd like to avoid several thousand unnecessary write operations.

aleb
  • 366
Mike
  • 315

7 Answers7

21

I think mogrify systematically rewrites the file, so your only hope is to filter the list first, as suggested by jippie. Here's how you might do it (untested): print out a list of image files with a size indication, keep only the names whose associated size is within range, and process that list.

identify -format '%w %h %i\n' ./*.png |
awk '$1 > 400 || $2 > 400 {sub(/^[^ ]* [^ ]* /, ""); print}' |
tr '\n' '\0' |
xargs -0 mogrify -resize '400x400'

Script explanation:

  • For each file, print a line with the width, a space, the height, a space, and the file name. Depending on your version of identify, the \n to add a final newline may either be necessary (ImageMagick 6.6.0) or superfluous but harmless (GraphicsMagick 1.1.11).
  • (awk) On each line, if the width ($1) and height ($2) match the required conditions, then:
    • Remove all text up to the second space character. This strips the width and height.
    • Print what remains of the line, which is the file name.
  • Replace newlines by null characters.
  • Call xargs -0 to execute the mogrify command on the file names. (We can't use plain xargs because it can't deal with input containing whitespace or \'".)

The file names may contain any character except newlines.

  • can you explain that script? Specifically the "sub" part. It's printing the file names without a space or inserting a new line. mogrify: unable to open file `22 553.png308 400 0134 2.png' @ error/png.c/ReadPNGImage/2951. I have no idea where that "308 400" comes from. I should mention the files have spaces in their names

    Thank you.

    – Mike May 18 '12 at 16:15
  • I'm getting mogrify: unable to open file `340 271 22 553.png308 400 0134 2.png' @ error/png.c/ReadPNGImage/2951. I ran the command using 200x200 on two sample files. I see that 308x400 is the size of one of the files – Mike May 18 '12 at 16:23
  • 2
    @Mike Ah, got it. Some versions of identify automatically put a newline after each record, others need to have one explicitly. Add \n at the end of the argument to -format (see my edit). – Gilles 'SO- stop being evil' May 19 '12 at 12:10
15

I faced the same problem you described. Here's my solution:

#!/bin/bash
files=*.jpg
minimumWidth=640
minimumHeight=640

for f in $files
do
    imageWidth=$(identify -format "%w" "$f")
    imageHeight=$(identify -format "%h" "$f")

    if [ "$imageWidth" -gt "$minimumWidth" ] || [ "$imageHeight" -gt "$minimumHeight" ]; then
        mogrify -resize ''"$minimumWidth"x"$minimumHeight"'' $f
    fi
done

I tested it on several JPEG images on a virtualized CentOS 6.5 machine. The script only resized and compressed the images whose width or height was larger than 640 pixels. This made it work for images with dimensions like 800 x 600 (landscape, resizing it to 640 x 480) and dimensions like 600 x 800 (portrait, resizing it to 480 x 640).

PS: A note on the 400x400 parameter: mogrify will process the file even if its dimensions are equal or smaller than 400x400, but will resize only if its dimensions are larger than 400x400. That's why files' modification time and size are changed (in my case, mogrify made these files even larger than they were).

HalosGhost
  • 4,790
5

imagemagick fx operator allows filtering images based on height/width e.g.

(( $(identify -format '%[fx:(h>400 && w>400)]' image.png) ))

the inner command will output 1 if the image is bigger than 400x400 and 0 if it's equal or smaller than 400x400 so the (( )) expression will evaluate to true and respectively false. So you could iterate over the files and run this test, adding the positives to an array which is then passed to mogrify as arguments:

args=()
for f in ./*.png
do
(( $(identify -format '%[fx:(h>400 && w>400)]' "$f") )) && args+=("$f"}
done
mogrify -resize '400x400' "$args[@]"

If you're a zsh user see also this answer.

don_crissti
  • 82,805
3

How about using identify to find the size of your image and decide from a small script if you want to edit it or not:

identify -format "width=%w heigth=%h" bootchart.png 
width=3853 heigth=10092

Shouldn't be too hard to edit the output format for use in a simple script.

jippie
  • 14,086
  • Considering my limited skills and the fact the images' sizes are so irregular, I need a simpler method. I'd rather process them all with mogrify. – Mike May 18 '12 at 15:13
1

Here's my take on it, incorporating ideas from @ArionKrause and @don_crissti:

#!/bin/bash
# adapted from http://unix.stackexchange.com/a/157594/110635
# and http://unix.stackexchange.com/a/220619/110635
W=1024
H=768
SIZE_TEST="%[fx:(h>$H && w>$W)]"'\n'

for f in $*; do
  if [ $(identify -format "$SIZE_TEST" "$f") = 1 ]; then
    echo "Resize: $f"
    mogrify -resize ''"$W"x"$H"'' "$f"
  else
    echo "Do not resize: $f"
  fi
done

(I needed this because my favorite batch processor Phatch does not work with Ubuntu 16.04.)

bovender
  • 141
0

I use such PHP-script, it uses ImageMagick:

<?php
$dir = ".";
$exts = array('jpg', 'jpeg', 'png', 'gif');
$max_size = is_numeric($argv[1]) ? $argv[1] : 3000;
$morgify = "mogrify -verbose -scale \"${max_size}x${max_size}>\" -quality 85";
$identify = "identify -format \"%wx%h\"";

$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
    $path = "$dir/$file";
    // skip no images
    $dot = strrpos($file, '.');
    $ext = strtolower(substr($file, $dot + 1));
    if (!in_array($ext, $exts)) continue;
    // large size?
    $size = exec("$identify \"$path\"");
    list($width, $height) = explode('x', trim($size));
    if (max($width, $height) > $max_size) {
        // scale!
        print "scale $file ${width}x${height}";
        exec("$morgify \"$path\"");
        print "\n";
    }
}
closedir($dh);
?>

It scales all images in current directory larger than 3000 on some edge.

Run command: php scale.php OR php scale.php 2000

0

My functions to resize and optimize

resize_and_optimize_images () {
  resize_images 700 $PWD
  optimize_images 85 $PWD
}

resize_images () {
  max="$1"
  dir="$2"

  echo "Resizing dir $dir, max size - $max"

  shopt -s globstar

  for f in $dir/**/*.jpg $dir/**/*.jpeg $dir/**/*.png ; do
    echo "Checking $f"
    s=`identify -format "%w" $f`

    if [ $s -gt $max ]; then
      echo "Resizing..."
      mogrify -verbose -resize $max $f
    fi
    echo
  done

  echo "Done resizing dir $dir"
}

optimize_images () {
  quality="$1"
  dir="$2"

  echo "Optimizing dir $dir, quality - $quality"

  docker run -it --rm --name optimize_images_foo \
    -v $dir:/usr/src/app \
    -w /usr/src/app ruby:2.4-stretch bash -c \
    "gem install image_optim image_optim_pack && \
    (curl -L \"http://static.jonof.id.au/dl/kenutils/pngout-20150319-linux.tar.gz\" | tar -xz -C /usr/bin --strip-components 2 --wildcards \"*/x86_64/pngout\") && \
    image_optim --verbose --allow-lossy --jpegoptim-allow-lossy true --jpegoptim-max-quality $quality --pngquant-allow-lossy true --pngquant-quality 0..$quality -r ."

  echo "Done optimizing dir $dir"
}
srghma
  • 259