Automated updates: 2021-10-06

This commit is contained in:
John Colagioia 2021-10-06 06:25:16 -04:00
parent 341280941d
commit 0871a95adc
2 changed files with 170 additions and 0 deletions

View File

@ -7,6 +7,7 @@ tags: [programming, project, devjournal]
summary: Progress on assorted projects
thumbnail: /blog/assets/World-Space-WeekStacked.png
offset: -20%
proofed: true
---
I am absolutely cheating, here, but today marks the start of [World Space Week](https://en.wikipedia.org/wiki/World_Space_Week), recognizing the span between the anniversary of the [Sputnik 1](https://en.wikipedia.org/wiki/Sputnik_1) launch to the signing of the [Outer Space Treaty](https://en.wikipedia.org/wiki/Outer_Space_Treaty) that basically everybody ignores. This year's celebration is themed around women in space, so if you've been dying to start conversations about [Valentina Tereshkova](https://en.wikipedia.org/wiki/Valentina_Tereshkova), [Christa McAuliffe](https://en.wikipedia.org/wiki/Christa_McAuliffe), [Mae Jemison](https://en.wikipedia.org/wiki/Mae_Jemison), or any of that crowd, this week is your excuse.

169
2021-10-06-collage.md Normal file
View File

@ -0,0 +1,169 @@
---
layout: post
title: So You Need an Image of Random Images...
date: 2021-10-06 06:25:23-0400
tags: [programming, techtips, linux, blog]
summary: Generating a random collage of small images
thumbnail: /blog/assets/emoji-collage-2.png
offset: -14%
proofed: true
---
![Another bunch of emoji](/blog/assets/emoji-collage-2.png "Another run of the script")
If you follow the blog's [tech tip](/blog/tag/techtips) posts, you might remember my ending [last week's post about searching for posts with emoji]({% post_url 2021-09-29-emoji %}) with the following teaser.
> If there wasn't enough programming involved in this post, take a good look at the header image. I created that programmatically, and do I ever have a story about *that* nightmare.
>
> That, however, is another post...
Yes, you guessed it, intrepid reader. This is that other post.
## Review Time
Since last week's post covered my introduction to [OpenMoji](https://openmoji.org/) in addition to trying to find emoji, it seemed appropriate to illustrate with a random sampling of them. This seemed easy enough. Surely, after all, [ImageMagick](https://imagemagick.org/)---a command-line image editor which I usually have installed---has some magic `composite` command that takes a folder of source images and copies a random selection of them to random locations on a larger "canvas" object, right? Right...?
That question began a frustrating research project lasting three or four hours, as I discovered that---strangely, if you ask me---nobody else seems to have ever even wanted to do this. I *did*, however, find many references to the [`montage` command](https://imagemagick.org/script/montage.php), because apparently people like rigid grids, these days.
Disappointed, I decided to just build a script.
## Preparing
The first task was to get my hands on the OpenMoji images. Conveniently, clicking on or hovering over the **Get OpenMojis <i class="fas fa-chevron-down"></i>** button (upper right of the page, as I write this), we get the option of fonts, SVGs, 72x72 pixel PNGs, and 618x618 pixel PNGs, plus some other items most people probably don't care about. Post widths here on the blog are 740 pixels, so the 72x72 pixel package works out fairly well, with enough space for rows of a bit more than ten emoji, *if* I put them in a grid.
This doesn't make a difference to how I'm going to process them, but for those who need to know, the files are named for their Unicode code-points. So, "woman superhero" with [Fitzpatrick skin tone](https://en.wikipedia.org/wiki/Fitzpatrick_scale) type VI 🦸🏿‍♀️ is `1F9B8-1F3FF-200D-2640-FE0F.png`, because there are multiple code-points associated with the character, including the base character, the gender indicator, the gender and skin color.
![Female superhero with type-VI skin emoji](/blog/assets/1F9B8-1F3FF-200D-2640-FE0F.png "Save us, Noseless Woman with Fabulous Hair!")
Again, it's not useful information here, but if you're going to pick apart a similar set of images, this sort of formatting makes it easy to pick out the images with a medium (type IV) skin tone and no gender, if you need them.
In any case, I unzipped the package and created a subfolder inside, where I could work.
## Creating the Canvas
We can start with the easy part. If we need an empty image of a particular size, we run the following, from the command line.
```console
convert -size 740x370 xc:transparent emoji-collage.png
```
I assume that you don't need me to explain that we give it the size in pixels, background color, and file name.
## Selecting Random Images
My favorite part of this *might* be getting a random selection of the image files, because it "accidentally" feeds right into the main loop to process the images.
```console
ls -1 ../*.png | sort -R | tail -10
```
That line asks for a listing of all files in the parent folder---remember that we're in a subfolder, mentioned above?---that end in `.png`, one file per line, because of the `-1` option. Then, it shuffles the lines of that listing (`sort -R`), and gets the last ten lines of the list.
How does that get us to a loop? We add another command to the pipeline.
```console
ls -1 ../*.png | sort -R | tail -10 | while read emoji
do
# Here's the loop
done
```
Each time through the loop, the `$emoji` variable will look something like `../1F9D1-1F3FC-200D-2696-FE0F.png`.
## Overlay Nightmares
As I mentioned above, there is no way to just feed this list to ImageMagick and say "give me a randomly distributed collage of these files, like a child pasting pictures out of a magazine for a school project." So, we'll need to do some more work than that.
Surely, though, there's a command to place an image at a random location within another image. No...?
Well then, surely, there's a way to place an image at a *fixed* location within another image, and I'll just scrounge up my own random numbers. No...?
Plenty of research later, what I discovered was that there *is* a way to expand the canvas around an image. Then, we can compose that new image with another image, like the background.
```console
file=$(basename "$filename")
convert "$emoji" \
-gravity southeast \
-background transparent \
-extent 72x72 \
"$file"
composite "$file" emoji-background.png emoji-background.png
```
It's not extremely random, yet. In fact, it'll just pile up the emoji in the upper-left corner of the image, since I'm resizing the image files to their current sizes, but we'll work on that next. What we *do* have is a resized copy of the file in our working folder, where the original image is shoved down to the lower-right ("southeast") of the new image, with a transparent background. We then overlay the copied image onto our background, so that we can progressively add images.
## Random Coordinates
My first instinct---because that's how I always handle random numbers---was to do something like this.
```console
x=$(rand -M 740)
y=$(rand -M 370)
echo "-extent ${x}x${y}"
```
That is, use the default `rand` command to generate values from 0 to 739 and from 0 to 369. And on the command line or in small doses, that's fine.
By the way, the above includes `echo` to show how we'd fill in the `-extent` parameter to `convert`, but it's otherwise unnecessary for this purpose.
It turns out, though, that I don't actually know where `rand` gets its numbers from, and if you run it repeatedly in rapid succession, it tends to produce the same or similar numbers, meaning that this resulted in strange clusters of images on the background.
Give it some time to increase randomness, though, and it gets better. So, I tried adding `sleep 1` to the end of the loop. And that was an improvement, *except* that the emoji now all fell along diagonal lines, because the x- and y-coordinates were the same. Plus, spending a few full minutes generating these collages---especially when there's a chance that it might produce garbage---isn't acceptable.
Another possibility was providing `rand` with a seed, like the number of nanoseconds on the clock, but that's similarly ugly and prone to going wrong.
Looking at other possibilities, though, there's a surprisingly good way of generating random numbers like this.
```sh
x=$(shuf -i 0-740 -n1)
y=$(shuf -i 0-370 -n1)
```
It seems like running this rapid succession doesn't make it any less random, so that's a far better solution.
## The Entire Mess
All that's left to do is collect the pieces above, and pull out the constants to variables, for when somebody eventually wants a different size image or different packing.
```sh
#!/bin/sh
bg=emoji-collage.png
images=375
size=72
width=740
height=$((width / 2))
rm -f *.png
convert -size ${width}x${height} xc:transparent "$bg"
ls -1 ../*.png | sort -R | tail -$images | while read emoji
do
file=$(basename "$emoji")
x=$(shuf -i 0-"$width" -n1)
y=$(shuf -i 0-"$height" -n1)
convert "$emoji" -gravity southeast -background transparent -extent $((x + size / 2))x$((y + size / 2)) "$file"
composite "$file" "$bg" "$bg"
rm "$file"
done
```
We've already seen most of this. However, after setting the default values, the script wipes out any image files that might be left over from the last run. Then, we run a loop over the random sample of emoji files, and---for each of the emoji---create a resized copy, compose it with the background that slowly collects emoji, and delete the copied emoji.
It still takes a while to run---about a minute and a half---but that's not so long that I forget about it, and the results are decent without *too* much work.
## Possible Enhancements
Someone---maybe even me in the future, though I doubt it---might want to take this a step further. Given what ImageMagick is best at, one possibility would be to start with the 618x618 pixel images and, while recopying them to random-sized canvases, resize them to a random size between 36x36 and 216x216 pixels. Source images could also be rotated within a certain range of angles, so that they feel like they have a more random placement, but always still look mostly "right-side up."
Oh, and it *probably* doesn't need to be said, but this sort of script would work for any collection of images. I just keep talking about emoji because it's the application that I happen to have needed. But a poster of headshots for a reunion, book covers, products, or just about anything else should be at least viable. Just be careful that you don't use a random system in cases where one picture covering up most of another picture would be awkward, like the face of someone whose feelings might be hurt if they seem to be the only person not visible.
Another possibility, probably passing the point of diminishing returns for most applications, would be to divide the background into "sectors," focusing on each one before moving on to the next, to get a more thorough fill, and avoid the unsightly "dead spots" that you can see in the generated images.
For now, though, this does what I need, and I even managed to use it for two posts...
#### 😮‍💨
* * *
**Credits**: The header image is a random assortment of OpenMoji images, generated by me from images made available under the terms of the [Creative Commons Attribution Share-Alike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/ license), as is the woman superhero image.