Álvaro Ramírez
Stitching images from the comfort of dired
I recently wanted a few images stitched together. A perfect job for ImageMagick. A quick search yielded the magical incantation:
convert image1.jpg image2.jpg image3.jpg +append joined.jpg
Great, now I know, but I'll rarely use it and will soon forget it. I may as well add it to my repository of DWIM command line utilities, wrapped in a convenient Emacs function, applicable from different contexts… know what I mean? 🙃
I built dwim-shell-command for this purpose. You can take the above command and easily turn it into an interactive Emacs command with something like the following:
(require 'dwim-shell-command) (defun dwim-shell-commands-join-images-horizontally () "Join all marked images horizontally as a single image." (interactive) (dwim-shell-command-on-marked-files "Join images horizontally" "convert -verbose '<<*>>' +append 'joined.jpg'" :utils "convert"))
You can select as many images as you'd like from the comfort of your dired and make the ImageMagick happen.
The snippet does the job just fine, but we can make it smarter. For starters, let's not hardcode the output filename. We'll ask the user instead. While we're asking, let's offer a default filename, but let's not assume the output extension is .jpg
. Let's guess based on the image selection. While we're at it, let's not override the output file if already exists. Uniquify it.
Most of the above can be achieved by either using dwim-shell-command helpers or its templating language. For example, <<joined.png(u)>>
ensures that if joined.png
already exists, it automatically generates joined(1).png
instead.
(require 'dwim-shell-command) (defun dwim-shell-commands-join-images-horizontally () "Join all marked images horizontally as a single image." (interactive) (let ((filename (format "joined.%s" (or (seq-first (dwim-shell-command--file-extensions)) "png")))) (dwim-shell-command-on-marked-files "Join images horizontally" (format "convert -verbose '<<*>>' +append '<<%s(u)>>'" (dwim-shell-command-read-file-name (format "Join as image named (default \"%s\"): " filename) :default filename)) :utils "convert")))
Here's the new horizontal command in action…
Notice how this time we didn't mark the images using dired-mark
, typically bound to m
. Instead, we made our selection using the region. Also, if you haven't gotten your junk food fix yet, here's the fries equivalent ;)
We'll rinse all and repeat to get the vertical command equivalent. I know, I know, there's fair amount of duplication but c'est la vie.
(require 'dwim-shell-command) (defun dwim-shell-commands-join-images-vertically () "Join all marked images vertically as a single image." (interactive) (let ((filename (format "joined.%s" (or (seq-first (dwim-shell-command--file-extensions)) "png")))) (dwim-shell-command-on-marked-files "Join images vertically" (format "convert -verbose '<<*>>' -append '<<%s(u)>>'" (dwim-shell-command-read-file-name (format "Join as image named (default \"%s\"): " filename) :default filename)) :utils "convert")))
…and for our grand finale, we'll vertically join our burgers and fries. Behold!
These commands are now part of dwim-shell-command. To get them, load the optional commands via (require 'dwim-shell-commands)
.