42 Astoundingly Useful Scripts and Automations for the Macintosh

Random colors in your ASCII art

One of the great things about writing your own scripts is that when you need new functionality, you can add it. I needed random colors in a single-character ASCII art image. It was easy to add to the asciiArt script. Here’s how.

Jerry Stratton, April 29, 2020

I recently wanted to put dollar signs inside a Texas shape for a blog post about local sales taxes. That, of course, is easy with the asciiArt script: just set up a single-character palette and use a solid two-color image of Texas. But I also wanted to highlight the potential patchwork of sales taxes that people will have to know just to sell things online or even to send things sold in-person to the customer’s home.

The obvious solution was to randomly color each character in the image. The asciiArt script in the book can handle colors, but not random colors. It can use the color of the main image or overlay the color of a different image. Adding a random color option wasn’t difficult, however, because the script already grabs the red, green, and blue components of the image in a central place.

Here’s how I added a random color option to the script. The first question is, how to request random colors? There’s already a request for an overlay image file. What I want now is to, instead, overlay random colors. So I’m going to use --overlay random. It will make no sense to use an overlay file and random colors at the same time. The colors have to come from somewhere, and if they come randomly the file won’t be seen, and if they come from a file the random colors won’t be seen. So combining those options into a single place makes sense.

That means changing the case for --overlay:

[toggle code]

  • case "--overlay", "-o":
    • overlayFile = popString(warning:"Overlaying requires an image to overlay.")
    • if overlayFile == "random" || files.fileExists(atPath: overlayFile) {
      • useColors = true
    • } else {
      • help(message:"File " + overlayFile + " does not exist.")
    • }
  • case "--palette", "-p":

The bold lines, lines three through seven, are the new lines. All it really changes is to add “random” as a valid value for --overlay. If the overlay string exists as a file or, if the string is the word “random”, that section of code does what it did before. Otherwise, it displays the script’s help text and quits the script.

Because overlayFile can now contain text that is not a file, the check for reading the overlay file has to be changed, too:

[toggle code]

  • if overlayFile != "" && overlayFile != "random" {
    • overlay = resize(image: overlayFile, columns: lineWidth, rows:bitmap.size.height)
  • }

This keeps it from trying to read a non-existent file called “random”. I could just as well have set overlayFile back to the empty string in the case section and used a flag, maybe useRandomColors. But by leaving the text “random” in the variable, it will display that the overlay is “random” when --details are requested, with no extra code.

In the part of the script that does the work, a little reorganization will help. Here’s what it currently looks like:

[toggle code]

  • if useColors || useGreyscale {
    • if overlay != nil {
      • (red, green, blue) = pixelRGB(bitmap:overlay!, x:column, y:row)
    • }
    • if useColors {
      • //30 is black
      • var termColor = 30
      • if red > 0.3 {termColor += 1}
      • if green > 0.5 {termColor += 2}
      • if blue > 0.3 {termColor += 4}
      • line += "\u{001B}[;" + String(termColor) + "m"
    • }
    • var characterColor = NSColor(red: red, green: green, blue: blue, alpha: 1.0)
    • if useGreyscale {
      • characterColor = NSColor(red: grey, green: grey, blue: grey, alpha: 1.0)
    • }
    • characterAttributes[NSAttributedString.Key.foregroundColor] = characterColor
  • }

And here’s what I changed it to:

[toggle code]

  • if useColors || useGreyscale {
    • if overlayFile == "random" {
      • red = CGFloat.random(in: 0...1)
      • green = CGFloat.random(in: 0...1)
      • blue = CGFloat.random(in: 0...1)
    • } else if overlay != nil {
      • (red, green, blue) = pixelRGB(bitmap:overlay!, x:column, y:row)
    • }
    • if useGreyscale {
      • red = (red * 0.3 + green * 0.59 + blue * 0.11)
      • green = red
      • blue = red
    • } else {
      • //30 is black
      • var termColor = 30
      • if red > 0.3 {termColor += 1}
      • if green > 0.5 {termColor += 2}
      • if blue > 0.3 {termColor += 4}
      • line += "\u{001B}[;" + String(termColor) + "m"
    • }
    • let characterColor = NSColor(red: red, green: green, blue: blue, alpha: 1.0)
    • characterAttributes[NSAttributedString.Key.foregroundColor] = characterColor
  • }

You can see that it uses much of the same code, but it adds a check for overlayFile == "random" and sets red, green, and blue to be random CGFloats between 0 and 1. It also changes the logic of using greyscale slightly. Previously, if it was using greyscale it always used the greyscale of the main image. The two options—overlay and greyscale—were mutually exclusive. Now, the script uses the greyscale of the overlay if one was specified. That includes a random overlay. If you specify random colors and greyscale, you’ll get random greyscale.

This change let me create a Texas flag with randomly colored dollar signs using the command line:

  • asciiArt Texas\ Flag.png --palette '$' --blank ' ' --sequence .8 --overlay random
Psychedelic Head: Randomly colored ASCII art of myself wearing a Mr. Incredible kleenex mask.; Jerry Stratton; ascii art

If you’ve played any roguelike that supports ANSI colors and you’ve been so hungry you’ve eaten mushrooms, you may start feeling a flashback.

And for the photo of myself that I used in the book, I can get random colors over the ASCII fake greyscale characters by using:

  • asciiArt head.png --overlay random --save psychedelic.png

It looks a lot like eating mushrooms in Angband.

This may end up being a very limited use case for the script, but it is a lot of fun, and a lot of fun is half the reason for the book, so I’ve added this update to 42 Astounding Scripts. If you bought the ebook from a vendor that does automatic updates, you probably already have the update; otherwise, it should be available to redownload. If you have the print book, you can type the changes above and get the same benefits! Any new copies of the print book will have the updated script.

Despite the… interesting effects on a photo, this option for the script is probably best used with solid shapes, such as shapes of states or other items, since the colors, being random, will tend to muddy any more complex image.

I noticed when preparing this post that random colors don’t compress very well. PNG can’t compress it because it has no solid colors, and JPEG can’t do it because the colors are all unrelated. JPEG is optimized for colors as they might appear in nature, with variations of the same color shifting to variations of other colors. Variations on the green in trees, for example, blending into variations on the blue in the sky, blending into variations on the white in clouds. Because these colors are random, there are no such variations. The colors are basically solid until they vary, and their variations are from one unrelated color to another. JPEG does better than PNG, however, which surprised me a little. This is probably because there’s anti-aliasing going on in the text.

Enjoy! This script and the piano script remain the ones I find most fun in 42 Astoundingly Useful Scripts.

  1. <- Hello, world amber
  2. ASCII palettes ->