3

I'm running this from the command line:

magick.exe input.png -shave '1x0' output_%d.png
magick.exe input.png -shave '2x0' output_%d.png
magick.exe input.png -shave '3x0' output_%d.png
magick.exe input.png -shave '4x0' output_%d.png
magick.exe input.png -shave '5x0' output_%d.png
magick.exe input.png -shave '0x1' output_%d.png
magick.exe input.png -shave '0x2' output_%d.png
magick.exe input.png -shave '0x3' output_%d.png
magick.exe input.png -shave '0x4' output_%d.png
magick.exe input.png -shave '0x5' output_%d.png

The first command creates output_0.png but the following commands overwrite the same file. Is there a single command that could generate output_0.png to output_9.png instead?

The ImageMagick documentation says:

Filename References

Optionally, use an embedded formatting character to write a sequential image list. Suppose our output filename is image-%d.jpg and our image list includes 3 images. You can expect these images files to be written:

image-0.jpg 
image-1.jpg
image-2.jpg

That's the closest evidence I found it's possible to do what I'm looking for in ImageMagick. It's not clear to me if I need to leverage shell scripting to do this or if ImageMagick provides a command line feature.

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356

2 Answers2

4

With ImageMagick you can run multiple operations on separate instances of the input image in a single command by cloning the input and isolating the operations inside parentheses like this...

magick input.png \
   \( -clone 0 -shave '1x0' \) \
   \( -clone 0 -shave '2x0' \) \
   \( -clone 0 -shave '3x0' \) \
   \( -clone 0 -shave '4x0' \) \
   \( -clone 0 -shave '5x0' \) \
   \( -clone 0 -shave '0x1' \) \
   \( -clone 0 -shave '0x2' \) \
   \( -clone 0 -shave '0x3' \) \
   \( -clone 0 -shave '0x4' \) \
   \( -clone 0 -shave '0x5' \) \
   -delete 0 output_%02d.png

That will create 10 output images, each with a sequential filename with the number padded to two places, like "output_00.png ... output_10.png".

To convert this to Windows CMD syntax you can remove all the backslashes that escape the parentheses, and replace the continued-line backslashes "\" with carets "^".

EDITED TO ADD: There are many ways to accomplish this task with ImageMagick. Here is another example command that would produce the same results, but it uses FX expressions so it will only work in IMv7. (The above command should work with IMv6 by changing "magick" to "convert".)

magick input.png -duplicate 9 -shave "%[fx:t<5?t+1:0]x%[fx:t>4?t-4:0]" output_%02d.png

This reads the input and duplicates it 9 times, then uses FX expressions as arguments to the -shave operation so it can step through all 10 images, shaving each according to the formula in the FX expression.

GeeMack
  • 4,486
  • 1
  • 7
  • 10
  • Thank you! I'm actually using WSL, so would you mind converting this into bash syntax? I think that'll help the majority of people reading your answer. – Daniel Kaplan Sep 13 '22 at 19:34
  • Also, let me see if I understand this correctly: that `clone` operation is creating a file named 0, `output_%02d.png` is the output filename of the `shave`, and `-delete 0` removes the clone file at the end? – Daniel Kaplan Sep 13 '22 at 19:37
  • 1
    No, `-clone 0` will create a copy in memory of the zeroth image, i.e. the first one you loaded. – Mark Setchell Sep 13 '22 at 20:09
  • 1
    For bash, escape all the parentheses with backslashes "\\(...\\)" and replace all the continued-line carets "^" with backslashes "\". Also, the `-clone 0` is, as Mark described, a new copy of (in this case) the input image. Another clone is created and shaved inside each set of parentheses, which creates a list of images. Then `-delete 0` just deletes the first image in the list which is the un-modified input image. – GeeMack Sep 13 '22 at 21:00
  • okay that's worth knowing because I didn't want to delete the original. Thanks for the information. I understand you've described how to convert this into bash, but before I accept the answer, can you write it in bash syntax? I'm requesting this change for the community as I believe people here tend to convert bash into other commandline syntax, not the other way around. – Daniel Kaplan Sep 14 '22 at 05:16
  • 1
    I edited the command to work in a *nix shell. I also added another example command that shows another approach to the task. – GeeMack Sep 14 '22 at 05:56
2

As @GeeMack points out, there are many ways of doing this with ImageMagick and I was inspired to try a couple of other methods by his marvellous FX expression - just for fun!

Here's what I was originally proposing:

magick input.png -write MPR:orig \
  -shave 1x -write out_1.png \
  -shave 1x -write out_2.png \
  -shave 1x -write out_3.png \
  -shave 1x -write out_4.png \
  -shave 1x -write out_5.png \
  -delete 0--1               \
  MPR:orig                   \
  -shave x1 -write out_6.png \
  -shave x1 -write out_7.png \
  -shave x1 -write out_8.png \
  -shave x1 -write out_9.png \
  -shave x1 out_10.png 

It loads the input image and saves a copy in an MPR or "Magick Persistent Register" called orig. It then shaves a pixel off left and right sides and writes out_1.png, then shaves a further pixel off left and right sides and saves in out_2.png and continues till left and right are shaved by 5 pixels. It then reloads the original from the MPR and shaves off the top and bottom five times.

With a 1080p image, i.e. 1920x1080, this takes 3.4s and uses 96MB of RAM. By comparison, GeeMack's takes 3.5s on my machine and uses 291MB of RAM.

I then parallelised the creation of the left-right shaving with the top-bottom shaving as follows:

magick input.png \
  -shave 1x -write out_1.png \
  -shave 1x -write out_2.png \
  -shave 1x -write out_3.png \
  -shave 1x -write out_4.png \
  -shave 1x out_5.png &

magick input.png \
  -shave x1 -write out_6.png \
  -shave x1 -write out_7.png \
  -shave x1 -write out_8.png \
  -shave x1 -write out_9.png \
  -shave x1 out_10.png &

wait

That takes just 1.97s and peaks at 68MB of RAM.

I think GeeMack's solution is probably simplest and most concise, for most cases, but there may be some mileage in some of the ideas here for some situations, i.e. probably larger images.


In case anyone is interested how to measure the peak RAM usage, I used this:

/usr/bin/time -l ./go
    1.97 real         6.01 user         2.48 sys
        68354048  maximum resident set size
               0  average shared memory size
               0  average unshared data size
               0  average unshared stack size
           10343  page reclaims
               0  page faults
               0  swaps
               ...
               ...
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432