💾 Archived View for emisocks.com › blog › 2020-02-15_Pride_flag_generator captured on 2023-11-04 at 11:28:59. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
---
Hi all! I made a pride flag generator[1]! 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[2] 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[3], so you can follow along, but I'll be posting code snippets anyways. I created the project skeleton with Stack[4], which makes setting up Haskell projects super easy, and started thinking about the dependencies I'd need.
3: https://git.emisocks.com/socks/prideflags/src/commit/2107cc9ef936c9e89770688c491ea6fd20177feb
4: https://docs.haskellstack.org/en/stable/README/
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:
5: https://hackage.haskell.org/package/random
6: https://hackage.haskell.org/package/Rasterific
7: https://hackage.haskell.org/package/colour
8: https://hackage.haskell.org/package/scotty
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 `Word8`s, that is, 8 bits. So we need a way to generate three random `Word8`s 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 `Word8`s 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.
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[11], 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:
11: https://hackage.haskell.org/package/JuicyPixels
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 where 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[12]. (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.
12: https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad.html#v:forM_
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.
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[14] for finding this!)
14: https://transfur.online/@Frinkeldoodle
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 Emi Socks pride flag! [IMG]
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!
---