Screenshots with SnapClip
February 25, 2017 at 12:55 PM by Dr. Drang
I’ve been writing and rewriting this screenshot script/workflow/thing since 2006. Every now and then I have an idea of how to improve it, and I inflict the update on you. Usually, instead of describing the whole thing, I refer you back to one or more of the earlier posts and only explain what’s new. That makes for convoluted reading (assuming you’re reading at all), so I decided to write three posts on the topic that cover everything:
- This one, which describes a Keyboard Maestro macro, SnapClip, that takes a screenshot, puts it on the clipboard, and optionally saves it to the Desktop.
- Second, another Keyboard Maestro macro, SnapSCP, which takes a screenshot, saves it locally, uploads it to a server via
scp
, and puts an HTML link to the file on the clipboard for pasting into a blog post. - Finally, a set of instructions for creating and configuring login credentials on your Mac and the server so you don’t have to enter a password or passphrase when you run SnapSCP (or when you run
scp
orssh
from the command line).
I’ll put links to the latter two post in the list above once they’re written.
SnapClip is modeled on the builtin macOS ⇧⌘4 keyboard shortcut for taking a screenshot of a portion of the screen. (As we’ll see in a bit, it uses macOS’s screencapture
command.) Like ⇧⌘4, it starts by changing your pointer to a crosshair, which you can drag across the screen to select an arbitrary rectangle for capturing.
Alternately, if you want to select an entire window, tap the spacebar. The pointer will change to a camera, and whatever window the camera is over will turn blue, indicating that it will be what’s captured when you click.
It’s at this point that SnapClip diverges from ⇧⌘4. A window pops up on your screen with a couple of choices.
If you select “Background border”, SnapClip will put a blue border around the screenshot, just like you see in the screenshot above. Although this option appears for both arbitrary rectangle and window screenshots, it’s intended to be used only for the latter.
The second option, “Save file to Desktop,” does exactly what you think. In some cases, you want to save the screenshot in addition to having it on your clipboard. Like ⇧⌘4, the filename is based on the date and time at which the screenshot was taken, but it isn’t as verbose. The format is simply
yyyymmdd-HHMMSS.png
The default action button is labeled “Clipboard” as a reminder that the purpose here is to get the screenshot onto the clipboard, even if a copy is also saved to a file.
The blue background border is my attempt to strike a happy medium between the two types of window screenshots ⇧⌘4 and screencapture
can produce: a window with a big dropshadow border,
or a bare window with no edges,
The dropshadow border is way too big and lots of people in the Mac world hate it, but the edgeless window gives me vertigo; I feel as if I’ll walk off the edge and fall to my death. It doesn’t even look like a window. SnapClip’s blue border is relatively narrow but gives the sense of a window with the Desktop behind it, which is what I’m looking for in a screenshot.
The color of the border is Solid Aqua Dark Blue, which is, by an amazing coincidence, the color I use for my Desktop.
Now that we know how SnapClip works and what it looks like in action, let’s see how it’s built. It’s a Keyboard Maestro macro with a hot key of ⌃⌥⌘4:
The macro has only one step, but that step is a doozy. It’s this Python script:
python:
1: #!/usr/bin/env python
2:
3: import Pashua
4: import tempfile
5: from PIL import Image
6: import sys, os
7: import subprocess
8: import shutil
9: from datetime import datetime
10:
11: # Local parameters
12: type = "png"
13: localdir = os.environ['HOME'] + "/Pictures/Screenshots"
14: tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
15: bgcolor = (61, 101, 156)
16: border = 16
17: desktop = os.environ['HOME'] + "/Desktop/"
18: fname = desktop + datetime.now().strftime("%Y%m%d-%H%M%S." + type)
19: impbcopy = os.environ['HOME'] + '/Dropbox/bin/impbcopy'
20: optipng = '/usr/local/bin/optipng'
21:
22: # Dialog box configuration
23: conf = '''
24: # Window properties
25: *.title = Snapshot
26:
27: # Border checkbox properties
28: bd.type = checkbox
29: bd.label = Background border
30: bd.x = 10
31: bd.y = 60
32:
33: # Save file checkbox properties
34: sf.type = checkbox
35: sf.label = Save file to Desktop
36: sf.x = 10
37: sf.y = 35
38:
39: # Default button
40: db.type = defaultbutton
41: db.label = Clipboard
42:
43: # Cancel button
44: cb.type = cancelbutton
45: '''
46:
47: # Capture a portion of the screen and save it to a temporary file.
48: status = subprocess.call(["screencapture", "-io", "-t", type, tfname])
49:
50: # Exit if the user canceled the screencapture.
51: if not status == 0:
52: os.remove(tfname)
53: sys.exit()
54:
55: # Open the dialog box and get the input.
56: dialog = Pashua.run(conf)
57: if dialog['cb'] == '1':
58: os.remove(tfname)
59: sys.exit()
60:
61: # Add a desktop background border if asked for.
62: snap = Image.open(tfname)
63: if dialog['bd'] == '1':
64: # Make a solid-colored background bigger than the screenshot.
65: snapsize = tuple([ x + 2*border for x in snap.size ])
66: bg = Image.new('RGB', snapsize, bgcolor)
67: bg.paste(snap, (border, border))
68: bg.save(tfname)
69:
70: # Optimize the file.
71: subprocess.call([optipng, '-quiet', tfname])
72:
73: # Put the image on the clipboard.
74: subprocess.call([impbcopy, tfname])
75:
76: # Save to Desktop if asked for.
77: if dialog['sf'] == '1':
78: shutil.copyfile(tfname, fname)
79:
80: # Delete the temporary file
81: os.remove(tfname)
The first thing of note is that the script relies on two nonstandard libraries, i.e., two libraries that don’t come with macOS. The first is Pashua, which handles the SnapClip window and its controls. Pashua is an application written by Carsten Blüm and has libraries for several scripting languages, including Python.
The second nonstandard library is Pillow, which handles the addition of the border to the screenshot. Pillow is a fork of and drop-in replacement for PIL, the Python Imaging Library, a venerable piece of software that’s been around for about two decades.
In addition to Pashua and Pillow, SnapClip also makes use of two command line utilities that don’t come standard with macOS. Alec Jacobson’s impbcopy
is a short Objective-C program that works sort of like pbcopy
but for images. It reads the image data from a file and puts it on the clipboard. Alec gives the source code and instructions for compiling impbcopy
. If you’d rather not get into compiling software, you can download a copy I compiled and try that. I make no guarantees, but it works for me.
The other command line utility, OptiPNG, recompresses PNG files in place losslessly. I typically get a 30–40% reduction in file size on screenshots, definitely enough to make it worthwhile. I installed OptiPNG through Homebrew, but there are other ways to get it.
With those preliminaries out of the way, let’s go through the script.
Lines 11–20 set up a bunch of parameters that will be used later in the program. If you need to customize the script, this is probably where you’ll do it. Line 12 set the image type. The screenshots are temporarily saved in a Screenshots folder in my Pictures folder, so Line 13 sets the variable localdir
to point there. Line 14 then uses the tempfile
library to create a secure temporary file for the screenshot. Line 15 sets the color of the border to match the RGB parameters of Solid Aqua Dark Blue, and Line 16 sets the width of the border to 16 pixels. Lines 17–18 then set the full path to the Desktop file where the image will be saved if the user so chooses. Lines 19–20 give the full paths to the impbcopy
and optipng
commands.
Lines 22–45 set up the geometry of the Pashua window. I won’t go through every line here. It should be fairly obvious what each section does, and you can get the details from the Pashua documentation.
Line 48 runs screencapture
via the subprocess
module. It’s launched in interactive mode (-i
), does not capture the dropshadow if capturing a window (-o
), and saves the image in PNG format (-t type
) to the temporary file (tfname
).
Lines 51–53 stop the program and delete the temporary file if the user abort the screenshot by hitting the Escape key or ⌘-Period.
Line 56 runs Pashua to put up the SnapClip window and collect its input.
Lines 57–59 stop the program and delete the temporary file if the user clicks the Cancel button in the SnapClip window.
Lines 61–68 check to see if the user asked for a Desktop background border and add it if necessary. Line 65–66 creates a solid-colored image that’s two border widths larger in each dimension than the raw screenshot. Lines 66–67 then paste the raw screenshot on top of the solid-colored image and save the result back to the temporary image file.
Line 71 uses optipng
to reduce the size of the temporary image file, and Line 74 copies it to the clipboard with impbcopy
.
Line 77 checks to see if the user asked to save the image. If so, Line 78 copies the temporary file to the Desktop with the yyyymmdd-HHMMSS.png
name.
Finally, Line 81 deletes the temporary file.
I use SnapClip practically every day. On the fun side, it’s how I get screenshots into Twitter and Messages. On the business side, it’s how I copy Amazon receipts into my expense reports. Although I usually use it “bare,” without adding a background border and without saving a copy to the Desktop, having those additional features gives me a tool that handles almost all my screenshot needs. The only thing I commonly do that SnapClip doesn’t is upload images for showing here in the blog. For that, I use the very similar SnapSCP utility that I’ll describe in the next post.