Photo collage
Beautiful, dynamic photo collages. Way beyond grid/ horizontal/ vertical!
What this shortcut does
Watch the video tutorial! Direct download link Notes/Limitations 100% native shortcut painstakingly adapted from my Python version, requires no other apps/shortcuts to run It's faster than it used to be, but you'll still have to wait a bit, up to 1-2 minutes or more, for larger collages (50+ images) If you want more speed, check out the Python and Pythonista (iOS) versions on my GitHub that run over 10X faster On iOS, Photo collage can fail if too much memory is used If this happens, use the included "Preprocess images" utility which creates downscaled copies of images in a new photo album, then rerun Photo collage with those images It will also run faster if preprocessing is done first Can run by sharing images from the Share Sheet (works better with Siri Shortcut than with Pythonista version) On macOS, sharing images from Photos to a Shortcut doesn't work (thanks, Apple....) Interface Run the shortcut from within the Shortcuts app, from a widget, or using the Share Sheet to share photos directly from a photo app (Photos/Google Photos/etc) Select options for the collage and then select “Continue” The collage will be generated and previewed, and you then have options to save and/or make a new collage from the same batch of selected images The shortcut supports using 1 or 2 levels of nesting. The reason to use more than one level of nesting is to get a more dynamic collage, and with greater size difference between the smallest and largest images in the collage. Features (feel free to zoom into these images) Credits Thanks to @gluebyte for Join Shortcuts , necessary for developing this shortcut by allowing for the independent development of subroutines Thanks to @Avieshek for Update Engine used for update checking Thanks to @atnbueno for the inspiration to use Shortcuts's in-built ability to run JS for optimizing the algorithms used in this shortcut. How it works The partition problem This shortcut works by solving the so-called "partition problem", where, given a set of items with different "weights" (values), and a set number of groups into which they need to be organized, you want to determine the grouping such that each group has the same total (sum of) weights. Let's see how this applies to photo collages. Row-based collages Initially, this shortcut started from a Python program I wrote years ago that only produced row-ordered collages like this one. In this case, the "weights" are the aspect ratios of each image, and the number of groups is the number of rows. The number of rows is determined by the user-specified collage target aspect ratio and the average aspect ratio of the images. The solution to the problem will be a grouping such that each row has the same width. The partition problem is a known problem in computer science, and I used this Stack Overflow Python implementation to modify (heavily) delimitry's collage maker in order to make the original version of PyPhotoCollage. After the grouping of images into rows is determined, I still need to ensure everything lines up correctly. So, images in each row are resized to a common height, preserving aspect ratio, and combined into one image per row. Then, the rows are resized to a common width, preserving aspect ratio. Although there are slight differences in height between rows, I think the results are pretty decent . Refactoring to a Shortcut Turning the Python version into a native Shortcut was not easy. The main difficulty lies in the lack of the ability to modify a particular item in a list. In my case, I needed to modify items in two-dimensional lists to make this work, which I achieved by treating dictionaries like lists, where each item's key is its index, had it been a real list. Although it's not at all efficient, this allows one to create multi-dimensional arrays, and to modify individual elements within a multi-dimensional array. For example, the key "i,j" is used to represent the value at row i and column j . Another limitation of Shortcuts is the lack of what programmers call "functions". This can be overcome by having a Shortcut use the "Run Shortcut" action to run itself , but with a specific input that changes the program flow of the Shortcut. I learned this trick from the amazing JellyCuts iOS app, which I hope has a long and exciting future ! (Before seeing how JellyCuts did it, I would have been forced to resort to using multiple shortcuts to achieve the same functionality.) But it turns out this method of recursive shortcut calling has considerable overhead since the shortcut is re-parsed on each recursive call. So I've removed all functions in favor of putting the necessary actions in-place. Now the only "function" that remains is the one that allows the shortcut to call itself recursively when producing nested collages. Where possible, "functions" are implemented in JavaScript, where they run somewhere around 5000X faster than the equivalent Shortcuts "code". For example, here's the JS version of the linear partition algorithm in shortcut form . This simplified the shortcut, reducing it by over 200 actions. Then I showed my Mom , and it wasn't good enough. Row-ordered and nested collages To appease my Mother (and, to be honest, myself), I had to spice things up. First, supporting row-ordered collages was fairly simple. I only needed to rotate the images up front, run the same algorithm, and rotate the resulting collage back the other way. To break away from row- or column-ordered collage styles, I needed another approach. This was something I had thought about previously, and the solution was tedius. This time around, I found a much simpler method: to recursively call the Photo collage shortcut on smaller batches of images. So, when enough images are provided to the shortcut, it will take a subset of those images and make a collage out of those, and the resulting sub-collage will then be used as if it was a single image in the original collage. The shortcut supports using 1 or 2 levels of nesting, so that if there are enough images in a given batch, those images too can be broken into yet more batches, resulting in a collage of collages of collages of images. The reason to use more than one level of nesting is to get a more dynamic collage, and with greater size difference between the smallest and largest images in the collage. The shortcut actually supports an arbitrary number of nesting levels, but to keep the options menu simpler, I limited it to 2. The Python and Pythonista versions allow the user to specify more than 2 levels. To keep it looking dynamic, the size of the batches and the aspect ratios and row/column orderings of nested collages are determined randomly (though bounded) (see the "Create batches" function). Also, because a dictionary in Shortcuts can only contain text or numbers, I had to base64-encode images that are passed into the shortcut recursively. Then, when the shortcut is called by itself in this way, the base64-encoded images must be decoded, and the user-options modified so that the recursive shortcut calls can proceed without any user interaction. Additional work was required in order to ensure that things like image ordering are correctly handled despite the style of collage selected (row/column/nested). Then, Shortcuts made it fairly easy to do things like image spacing, and image/collage corner rounding. (Interestingly, the solutions for row-ordered and nested collages only came to me while working on the shortcut, and so I actually originally implemented these in Shortcuts. Once I had everything working, it took only one day to add these features to the Python version of the collage tool . Adding corner-rounding using only the Python pillow library was a bit of a pain.) Debugging and testing Now, Shortcuts can fail on iOS for any number of reasons that have nothing to do with whether they're written correctly . This is because a shortcut will be killed by iOS if it exceeds memory limits, and there are also just tons of bugs in Shortcuts that you encounter along the way. Additionally, you get precisely zero useful information when a shortcut called by another shortcut fails for any reason. This makes debugging recursive calls to the shortcut nearly impossible. Shortcuts lacks proper debugging tools—you cannot step through, one action at a time, or pause to see what values are saved to variables at a given time—so I had to make due. I did this by implementing extensive logging throughout the shortcut (disabled by default), and using an auxiliary shortcut for testing that runs the Photo collage shortcut 864 times in order to test nearly every possible combination of user options and numbers of images. The original testing run would have produced over 12-thousand collages, so I had to scale down the number of combinations of user options tested. An example of logged information can be found below, along with an example of the output produced by the auxiliary testing shortcut . Also, to better understand the structure of nested collages, I added the ability to indicate nesting depth using colored borders on the images at each level of recursion. Debug logging, colored borders, and the testing routine are not intended for use by regular users, but if you want to try them out, you can set them manually inside the shortcut using the config dictionary at the top. The auxiliary shortcut used for testing can be found here (download link) And so you see, this is not your typical photo collage shortcut! Optimizations The shortcut now runs 3.5X-43X faster than the original version described here. The biggest optimization was to implement the number crunching using JavaScript, which runs around 5000X faster than the equivalent Shortcuts "code", and which was done for the linear partition algorithm and a few others. The next optimization was to take advantage of parallel image processing by running image operations in batches instead of one image at a time (which I originally did to avoid crashes on iOS; this way you can use it to a lesser extent without risking a crash). Example log output for a nested collage of 25 images I've left out some of the repetative parts, indicated by an ellipsis "..." 2024-06-10 10:28:04 Starting photo collage on Mac with dictionary input... 2024-06-10 10:28:10 Depth 0, 50 image(s) 2024-06-10 10:28:10 Depth 0, user options: {"Target aspect ratio":1,"Round image corners":0,"Collage type":"Nested (2)","Collage max width\/height":2000,"Image spacing":0,"Round collage corners":0,"Image order":"Shuffle"} 2024-06-10 10:28:10 Depth 0, converting/resizing/masking/rotating 50 images to PNG or JPEG depending on if corner rounding or image spacing are used (PNG if the following is greater than 0, or JPEG otherwise: 0) 2024-06-10 10:28:10 Depth 0, converting batch 1 of 1 2024-06-10 10:28:16 Depth 0, shuffling order of 50 images 2024-06-10 10:28:18 Depth 0, can make at most 16.6666666666667 batches from 50 images (need more than 3 batches to recurse) 2024-06-10 10:28:19 Depth 0, creating batches from 50 between 3 and 14 2024-06-10 10:28:20 Depth 0, making 11 batches from 50. Batches: {"7":31,"3":[7,8,9,10,11,12,13,14,15],"8":32,"4":16,"11":[44,45,46,47,48,49,50],"9":[33,34,35,36,37,38,39],"5":[17,18,19,20,21,22,23,24,25],"1":1,"6":[26,27,28,29,30],"2":[2,3,4,5,6],"10":[40,41,42,43]} 2024-06-10 10:28:20 Depth 0, preparing batch 1, size 1 2024-06-10 10:28:21 Depth 0, preparing batch 2, size 5 2024-06-10 10:28:21 Depth 0, Processing image operation batch 1 for batch 2 2024-06-10 10:28:21 Depth 0, batch 2, encoding to base64 2024-06-10 10:28:22 Depth 1, 5 image(s) 2024-06-10 10:28:23 Depth 1, user options: {"Target aspect ratio":0.75,"Max recursion depth":2,"Round image corners":"0","Collage type":"Nested (2)","Collage max width\/height":1000,"Image spacing":0,"Round collage corners":"0","Image order":"Shuffle","Recursion depth":1} 2024-06-10 10:28:23 Depth 1, can make at most 1.66666666666667 batches from 5 images (need more than 3 batches to recurse) 2024-06-10 10:28:24 Depth 1, aspect ratio 0.75, 3 row(s) and 1.66666666666667-ish col(s), common height = 333, common width =. Resizing... 2024-06-10 10:28:24 Depth 1, resizing image batch 1 of 1 2024-06-10 10:28:25 Depth 1, running linear partition routine to create 3 row(s) 2024-06-10 10:28:26 Depth 1, combining row 1 2024-06-10 10:28:26 Depth 1, combining row 2 2024-06-10 10:28:26 Depth 1, combining final row (count = 2) 2024-06-10 10:28:27 Depth 1, resizing row 1 of 3 to common width of 694 2024-06-10 10:28:27 Depth 1, resizing row 2 of 3 to common width of 694 2024-06-10 10:28:27 Depth 1, resizing row 3 of 3 to common width of 694 2024-06-10 10:28:27 Depth 1, reversing order of rows... 2024-06-10 10:28:27 Depth 1, combining rows vertically 2024-06-10 10:28:28 Depth 1, finished in 5 seconds 2024-06-10 10:28:28 Depth 0, batch 2 complete 2024-06-10 10:28:28 Depth 0, preparing batch 3, size 9 2024-06-10 10:28:29 Depth 0, Processing image operation batch 1 for batch 3 2024-06-10 10:28:29 Depth 0, batch 3, encoding to base64 ... 2024-06-10 10:30:55 Depth 0, batch 8 complete 2024-06-10 10:30:55 Depth 0, preparing batch 9, size 1 2024-06-10 10:30:56 Depth 0, aspect ratio 1, 4 row(s) and 2.25-ish col(s), common height = 500, common width =. Resizing... 2024-06-10 10:30:56 Depth 0, resizing image batch 1 of 1 2024-06-10 10:30:57 Depth 0, running linear partition routine to create 4 row(s) 2024-06-10 10:30:57 Depth 0, combining row 1 2024-06-10 10:30:58 Depth 0, combining row 2 2024-06-10 10:30:58 Depth 0, combining row 3 2024-06-10 10:30:58 Depth 0, combining final row (count = 2) 2024-06-10 10:30:59 Depth 0, resizing row 1 of 4 to common width of 1361 2024-06-10 10:30:59 Depth 0, resizing row 2 of 4 to common width of 1361 2024-06-10 10:30:59 Depth 0, resizing row 3 of 4 to common width of 1361 2024-06-10 10:30:59 Depth 0, resizing row 4 of 4 to common width of 1361 2024-06-10 10:30:59 Depth 0, reversing order of rows... 2024-06-10 10:31:00 Depth 0, combining rows vertically 2024-06-10 10:31:00 Depth 0, finished in 1 minute Example output of Photo collage testing shortcut Download the testing shortcut to try it yourself Here's a sample of the contents of the resulting csv file. Note that the aspect ratio error is meaningless for the rows with target aspect ratio of 0.001 and 1000. # Num images Collage max width/height Image spacing Round image corners Round collage corners Target aspect ratio Collage type Image order Wall time Out width Out height Out aspect ratio Collage size % rel error Aspect ratio % rel error 1 25 2000 0 0 10 1 Nested (2) Shuffle 42 2157 1606 1.34 7.9 34.3 2 25 2000 0 0 10 1 Nested (2) Oldest first 40 2091 1861 1.12 4.6 12.4 3 25 2000 0 0 10 1 Nested (2) Newest first 48 1370 1186 1.16 -31.5 15.5 4 25 2000 0 0 10 1 Simple by rows Shuffle 55 2265 1542 1.47 13.3 46.9 5 25 2000 0 0 10 1 Simple by rows Oldest first 61 2274 1549 1.47 13.7 46.8 6 25 2000 0 0 10 1 Simple by rows Newest first 23 2204 1504 1.47 10.2 46.5 7 25 2000 0 0 10 1 Simple by columns Shuffle 17 1801 1632 1.1 -10 10.4 8 25 2000 0 0 10 1 Simple by columns Oldest first 50 1765 1583 1.11 -11.8 11.5 9 25 2000 0 0 10 1 Simple by columns Newest first 64 1765 1583 1.11 -11.8 11.5 10 25 2000 0 0 10 0.001 Nested (2) Shuffle 46 97 2211 0.04 10.6 4287.2 11 25 2000 0 0 10 0.001 Nested (2) Oldest first 46 97 2211 0.04 10.6 4287.2 12 25 2000 0 0 10 0.001 Nested (2) Newest first 48 97 2211 0.04 10.6 4287.2 13 25 2000 0 0 10 0.001 Simple by rows Shuffle 48 97 2211 0.04 10.6 4287.2