2020-02-15 20:06:52

Pride flag generator

Hi all! I made a pride flag generator! It's dead simple. But I wanna talk about it anyways.

So I was thinking about small project ideas that I could realistically implement in a short time. All my ideas tend to get out of scope really fast, so I'm trying to limit myself to things I can actually finish and gain experience while I'm at it. I thought something like Identicon could be fun to make. The idea is that you create a unique image for every string (normally a username or email), so it can be used to identify a user who hasn't uploaded an avatar yet. We want a service like this to be deterministic: it should be fairly random, but the same string should always generate the same icon. That way, if some platform wants to use our service, they don't have to store the user's avatar; they can point to our service, and the same avatar will be generated every time. Plus, if the user ever makes an account on a different platform that uses the same service, they get to keep their visual identity. Pretty cool.

This seemed like a good opportunity to make something with Haskell, and randomly generated pride flags sounded like a fun idea, so I got to work. The repository is here, so you can follow along, but I'll be posting code snippets anyways. I created the project skeleton with Stack, which makes setting up Haskell projects super easy, and started thinking about the dependencies I'd need.

  • Haskell is purely functional, so it treats randomness a bit differently from other languages (functions with random inputs aren't pure!). We can use the random package for that.
  • Obviously we need some way to create images. I went with Rasterific.
  • I thought colour manipulation might come in handy, so colour could take care of that.
  • If we want to turn this into an actual service, a web server would be good. scotty is one I've used a couple times before, so I know it's really simple to use.

With all of that in place, we can get coding. I decided I'd split the logic into three parts: flag generation, converting flags into images and the server. Let's start with the first one:

Flag generation (code)

First of all, we need a data type to represent "a flag", whatever that means. My initial idea of a randomly generated flag is just a few evenly spaced horizontal stripes of random colours. Alright, that sounds simple enough. A flag, then, is just a number of stripes and a list of colours:

data Flag = Flag
  { stripes :: Int
  , colours :: [RGB Word8]

Colours (in this case from the colours library) are represented as a triple of Word8s, that is, 8 bits. So we need a way to generate three random Word8s whenever we want. This is where the random library comes in, and I struggled with it a bit.

The way random works is: you have a RandomGen, which is a generator, and you use that to generate a value and another generator. (Side note, but it's absolutely wild to me that there is no monadic interface in random. It seems like such a perfect use case, and doing it manually like this gets kind of uncomfortable, but oh well...) We can use a RandomGen provided to us, or we can generate our own from a String, which is guaranteed to always be the same for the same input. Perfect, right? That's exactly what we want. So let's generate a colour:

randomColour :: (RandomGen g) => g -> (RGB Word8, g)
randomColour gen =
  let (w1, gen1) = random gen
      (w2, gen2) = random gen1
      (w3, gen3) = random gen2
   in (RGB w1 w2 w3, gen3)

We're basically copying the way in which the functions in random work: we take a generator, we spit out a value and another generator (it's exactly what the random function does). We get three Word8s like that, and make an RGB with them. Wonderful. There's another function called randomColours which does the same, but giving us n colours instead of one. So far so good.

With that, we can do something similar to generate a flag:

randomFlag :: (RandomGen g) => g -> Flag
randomFlag gen =
  let (n, gen1) = randomR (3, 7) gen
      (cs, _) = randomColours gen1 n
   in Flag {stripes = n, colours = cs}

Similar idea. Normally we'd return the generator along with the flag, but I couldn't be bothered in this case, since I know the flag is what I ultimately want and I won't generate anything more after it. We generate a random integer n between 3 and 7, and then n colours. If I re-did this code, I'd probably give the flag a list of infinite colours. Haskell is cool like that: you can give it infinite stuff, and it will only take what it needs and run in finite time. For this case, though, this is good enough. Just gotta be careful to not break it in the future by giving it less than n colours.

Painting the flag (code)

Once we know the shape of our data (number of stripes and list of colours), painting it is pretty straightforward. Rasterific does mercifully come with a monadic interface, but it took me a bit to figure out that it's based on JuicyPixels, so we need to use the JuicyPixels data types to represent our pixel colours and textures. Just have to add the correct imports, and write a helper function to convert our colour from the colours data types to the JuicyPixels ones:

texture :: RGB Word8 -> Texture PixelRGBA8
texture (RGB r g b) = uniformTexture $ PixelRGBA8 r g b 0xFF

Apparently we need to use PixelRGBA8 instead of PixelRGB8, or the compiler will complain. Makes sense, we'll be generating PNG files so we need an alpha channel. Other than that, translation is straightforward.

Anyways, all the fun happens here:

renderFlag :: Flag -> Image PixelRGBA8
renderFlag Flag {stripes = s, colours = cs} =
  renderDrawing (floor width) (floor height) backgroundColor $ forM_ (zip [0..s] cs) $ \(n, c) -> do
    withTexture (texture c) $ do
      let h = height / fromIntegral s
          y = h * fromIntegral n
       in fill $ rectangle (V2 0 y) width h
    backgroundColor = PixelRGBA8 0xFF 0xFF 0xFF 0xFF

If you don't care about monads, you can either skip the next two paragraphs, or stare at the code until you convince yourself that it does a reasonable thing. If you do: the Drawing type from Rasterific is what we call a monad. A monadic value represents an action, or computation. That action can "return" pure values; in this case, they don't. Monads are incredibly useful in Haskell, because they come with lots of mechanisms to, say, transform the result of an action, or chain actions together. The Drawing monad represents the action of "painting something onto a canvas". So if we have a Drawing that means "paint a rectangle here" and another Drawing that says "paint a circle there", we can combine (chain) them together to get a Drawing that means "paint a rectangle here, then a circle there". Sounds obvious, but it's incredibly powerful.

In our case, what we want is to take our colours and paint a rectangle for each of them; that is, take our list, turn each element into a Drawing, then combine the results. That's exactly what the forM_ function does, as seen in the documentation. (Yeah, I meant it when I said Haskell has a lot of tools for working with monads.) As for zip, it just turns our list of colours [a, b, ...] into a list of pairs [(0, a), (1, b), ...]. We need that index to know where to draw each colour. The rest is just simple geometry.

Finally, the function renderDrawing just takes our Drawing and turns it into an Image that we can then convert into a PNG file. The encodePng function does exactly that, so we can create a convenience function, rawFlag, that just puts the whole process together.

rawFlag :: Flag -> BS.ByteString
rawFlag = encodePng . renderFlag

In Haskell, the . operator is function composition: take the output of renderFlag and pass it into encodePng. That way we don't have to actually name the Flag-type argument; instead of saying "this function when applied to flag does this", we can just say "this function is the result of piping these two functions together". Same result, less variable names to think of. It's super convenient and clean.

The server (code)

As I said, we'll be using scotty for the web server. The ScottyM type is also a monad! In this case, each action (each value of type ScottyM) represents attaching a route to our server. Here, we have two routes (both will handle GET requests), and each route is given by a path and an ActionM value... which is, you guessed it, a monad. We're using do blocks all throughout to make our life easier:

scotty port $ do
  get "/flag/:name" $ do
    name <- param "name"
    let image = rawFlag $ randomFlag (generator name)
    setHeader "Content-Type" "image/png"
    raw $ image
  get "/" $ do
    html $ mainPage

For those curious: A do block just takes a bunch of monadic values and chains them together. Their usefulness comes from binding: here, I bind the name name to the result of the ActionM param "name". As you can probably guess, param is an action that returns the value of a parameter (taken from either the URL or form data if it's a POST). So, it doesn't give us a String, but an ActionM String (that is, an "action that returns a string"). Binding just lets us treat it as a String in another action, which I do in the very next line. That's how actions get chained together. Don't worry if you didn't catch that last part, this is what most people (as far as I know) struggle with when first working with monads.

In any case, we've now defined a route that takes the name from the URL, passes it to our rawFlag function, and returns the result as a PNG image. There's also another route for the web frontend, but I won't get into that here.

Now we just have to test it out, and... it works... sort of. It creates pride flags, for sure, but it seems to generate the same flag for, say, "dvorak" and "dvorak_keyboard". After some experimenting, it turns out it's only using the first six characters of our string. (Thanks Holly Lotor for finding this!)

It turns out, the cause is our random generator: the docs fail to mention that, when converting a string into a generator, it only uses the first six characters. That's not ideal. We don't want users "socks@onedomain.com" and "socks@anotherdomain.com" to get the same avatar. So we have to find a way around it. Luckily, the solution is simple: use a hash function, like md5. If you don't know what that is, think of it as a function that always gives the same result from the same input, but it gives wildly different results for even very similar inputs. That's exactly what we need. So, we define generator name as follows:

generator :: String -> StdGen
generator = fst . head . reads . md5String

md5String :: String -> String
md5String = show . hashWith MD5 . C.pack

Again, we're using function composition to make things much cleaner. The whole process ends up being: "take the input string, interpret it as bytes, pass it through md5, convert the result back to a string (hopefully that doesn't destroy any information...), then make that string into a random generator". This does mean that we only have six bytes of possibilities, or 2^(6*8), which is... about 200 trillion possible pride flags, I think. Yeah, I'm pretty sure we'll be fine.

The result

The Emi Socks pride flag!

It works! And it will always generate the same flag for the same string. Of course, it's extremely simplistic. There's much more we could do with it: we could add symbols, a triangle to the side, make vertical stripes, or even randomly generate only a colour hue and pick the saturation and lightness ourselves; that's one of the reasons I wanted to use the colours library in any case, but I haven't gotten to it yet. In any case, suggestions are very welcome, either by email or fediverse!

Posted by Emi Socks | Permanent link | File under: haskell