Screenshots with SnapClip

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:

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.

Screenshot with cursor

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.

Screenshot with window selection

It’s at this point that SnapClip diverges from ⇧⌘4. A window pops up on your screen with a couple of choices.

SnapClip

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,

Window with border

or a bare window with no edges,

Window with no border

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.

Window with SnapClip border

The color of the border is Solid Aqua Dark Blue, which is, by an amazing coincidence, the color I use for my Desktop.

Desktop color chooser

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:

SnapClip macro

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.