Another bunch of emoji

If you follow the blog’s tech tip posts, you might remember my ending last week’s post about searching for posts with 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 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—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, 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 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 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

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.

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.

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.

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.

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.

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.

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.

#!/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, as is the woman superhero image.