Development, Shortcuts, and Warhol

For the past several days, I’ve been annoying my family with a shortcut. If there are people in your life you want to annoy, you might want to use it. And apart from the annoyance value, it has some Shortcuts programming techniques that I want to memorialize for future use.

My daughter has a dog named Daisy, and on a per-kilobyte basis, she is the biggest topic of conversation in the family texts. There are lots of texts about Daisy, and most of them are photos of her. Even so, there’s never enough Daisy, so I wrote a shortcut to amplify the Daisy content. It takes a single picture of Daisy,

Daisy single

and makes a grid (or small multiple, as Tufte would have it) of Daisys.

Untinted Daisy

As an option, I can apply a random tint to the component images.

Tinted Daisy

I’ve called this shortcut Warhol for what should be obvious reasons.

Using the shortcut is simple. Upon launching Warhol, you’re asked to choose an image from Photos.1 When that’s done, you’re asked to choose the grid size:

Choosing grid size

Finally, you’re asked if you want the images tinted or not.

You can download the shortcut and install it. As we’ll see in the description, Warhol does require a bit of external setup: a Photos album named Tints that contains 1×1 PNG images of a single color. Here’s what my Tints album looks like:

Tints album

You can download this zip file of my tints, but don’t feel restricted to these colors. Warhol will work with whatever you put in the Tints album. The only restriction—unless you change the Warhol source code—is that the album must be named Tints.

Here are the steps of Warhol:

StepActionComment
1 Warhol Step 01 Put up the usual Shortcuts photo picker. You’re allowed to choose only one.
2 Warhol Step 02 This is a dictionary of dictionaries, which we’ll use to resize the images and assemble the grid. The keys of the “outer” dictionary are the grid sizes you see in the left column. The keys of the “inner” dictionaries are count and width. A table of values and an explanation of how the values were determined is below.
3 Warhol Step 03 Put up a menu for choosing the grid size from the dictionary in Step 2.
4 Warhol Step 04 Resize the photo chosen in Step 1 to the width value from the dictionary item chosen in Step 3.
5 Warhol Step 05 Ask if the photos in the grid should be tinted.
6 Warhol Step 06 If yes…
7 Warhol Step 07 For the number of photos in the grid, which is the count value from the dictionary chosen in Step 3…
8 Warhol Step 08 Select an image at random from the Tints album.
9 Warhol Step 09 Resize the tint image and overlay it onto the photo selected in Step 1 and resized in Step 4. Width and Height are the width and height of the resized photo. For the opacity of the overlay, I tried several values and landed on 40%. You may prefer something else.
10 Warhol Step 10 Add the tinted image to the photos list.
11 Warhol Step 11 We’re done assembling the tinted photos.
12 Warhol Step 12 If no tinting…
13 Warhol Step 13 For the number of photos in the grid, which is the count value from the dictionary chosen in Step 3…
14 Warhol Step 14 Add the photo selected in Step 1 and resized in Step 4 to the photos list.
15 Warhol Step 15 We’re done assembling the untinted photos.
16 Warhol Step 16 We’re done handling the tinted/untinted option.
17 Warhol Step 17 Arrange the images collected in photos into a grid with a 1-pixel white line between them.
18 Warhol Step 18 Save the grid image to Photos.

The dictionary of dictionaries defined in Step 2 contains these values:

grid count width
2x2 4 1920
3x3 9 1280
4x4 16 960
5x5 25 768
6x6 36 640
8x8 64 480
10x10 100 384

The numerical trick in choosing the sizes is to come up with a multiple of 2, 3, 4, 5, 6, 8, and 10 that is the width you want for the final image (ignoring the 1-pixel separator lines). This will have to be a multiple of 120, which is the least common multiple of those numbers. I chose a final image size of 3840, which I sometimes think is too large, but I do like to zoom in on the individual images and still have decent detail. If you don’t want to use storage space and bandwidth as profligately as I do, choose a smaller multiple of 120 (1200? 2400?) and divide that by 2, 3, 4, 5, 6, 8, and 10 to get the widths.

It’s not obvious how to use a variable as the repetition count in a Repeat loop, as is done in Steps 7 and 13. When you first add a Repeat step, you’re given a count that you can change by tapping the plus or minus buttons that appear when you touch the count button.

Repeat action

To use a variable for the count instead of a predefined number, you have to press and hold on the count button. A menu like this will appear,

Variable repetition menu

and you’ll be able to choose either a regular variable or a magic variable for the number of repetitions. This is a terrible, undiscoverable design decision. And even after I did discover it (through Googling), I was disappointed to see that the step reads “Repeat count” instead of “Repeat count times.”

You may be wondering where the photos variable in Steps 10 and 14 came from. Basically, it gets made up on the spot and accumulates additional values in a list with each pass through the loop. Here’s the description of Add to Variable:

Appends this action’s input to the specified variable, creating the variable if it does not exist.

This allows you to make a variable hold multiple items.

If you’re used to writing in stricter languages, it may bother you that you don’t have to initialize photos with an empty list before adding values to it. If you’ve programmed in Perl, you’ll probably feel right at home.

I often feel guilty about presenting a completed automation, one that I’ve spent a lot of time writing and rewriting to make clearer, more efficient, and more idiomatic to the language in which it’s written. I can assure you that even a silly program like Warhol didn’t spring out fully formed, like Athena from Zeus’s head. It started out both much simpler, in that it handled fewer grid sizes and couldn’t tint the images, and more complicated, in that it didn’t take advantage of some of the niceties of Shortcuts.

For example, the earliest versions of Warhol presented only a few grid sizes and used a repetitive Choose from Menu section to set the size of the list of images. It was only after I had the basic logic of the shortcut worked out that I switched to using the dictionary of dictionaries to handle the different grid sizes. That change cleaned up a lot of code.

The earlier versions of Warhol also had no logic for resizing the base image before assembling it into a list of images. The Resize Image step came at the end, only after Combine Images had created a truly enormous grid image, usually well over 10,000 pixels wide. These early versions often ran very slowly because of all the data they were slinging around.

I don’t consider any of these inefficiencies in code size or running time to be bugs. They were part of my normal process of development:

  1. Order my thoughts to understand what the problem is and how to solve it.
  2. Work out some code that implements that solution.
  3. Improve the code to make it tighter, easier to understand, and, if necessary, faster.

Steps 2 and 3 usually lead to revisions in what I came up with in Step 1. Working out a solution almost always gives me a better understanding of what the problem really is.

Ultimately, Warhol is a solution in search of a problem. It’s the kind of automation I create because I think writing it would be fun and I would learn something in the process. On those terms, it was a success.


  1. Normally, I’d have a shortcut like this set up to run in the Share Sheet, but memory restrictions don’t allow that.