Rationale
I find Haskell a fine tool for writing little utility programs. It’s most succinct, elegant, and programs tend to just work suprisingly often. Often I’ll just write a single .hs file and the job’s done.
However there are a couple of issues:
- Some of the cleverer libraries e.g. JuicyPixels use the type system to great effect, letting you work with images at a very abstract level. I find this makes it a bit harder to use them from e.g. the ghci prompt without peppering things with more concrete type annotations.
- I can be quite lazy when writing such utilities. Suppose I have three images which I want to munge in a particular way, and save to new files. I think a good approach would to automatically generate a suitable name for the output file, then iterate over the three input names. In practice, I’ve tended to just invoke a command three times, manually specifying the output names.
This simple library is an attempt to solve these. It is deliberately opinionated in that it provides functions which I find useful, whilst making it harder to access things which I use less often. You might think of it as a compression scheme tailored to give short expressions for things I personally use often.
Practical matters
You can get the code from GitHub1.
Haddock documentation2 is also available.
If the two resources are in sync, that’s purely coincidental: if you actually care about this, grab the code and generate the docs locally.
Examples
Most of the functions are short enough to read quickly, and are documented in the source. So, I present here some (somewhat contrived) examples.
Toy.Generic3
processGeneric
If you’re interating over some input files, generating a separate output file for each input, it can be fiddly to generate good names for the outputs. This tries to help:
- it programmatically changes the basename;
- it assigns a fixed suffix, because we expect output to have a fixed format.
Note: it only processes one file, so you’ll typically call it repeatedly.
processArgs
This seems hardly worthwhile for now, but I have some vague plans to add functionality. Then again, YAGNI usually wins!
An example
Here’s a simple utility to cast the contents of text files into upper-case:
import Toy.Generic
import Data.Char
main = processArgs $ processGeneric ucContents (++ "-u") "txt"
ucContents :: FilePath -> FilePath -> IO ()
ucContents = transformText (map toUpper)
transformText :: (String -> String) -> FilePath -> FilePath -> IO ()
transformText tx inf outf = do
orig <- readFile inf
let new = tx orig
writeFile outf new
transformText
should probably be included in a (presently non-existent) Toy.Text module.
You might use it like this:
$ echo abc > abc.txt
$ echo def > def.text
$ ls *.txt
abc.txt def.text
$ stack exec ht-uc-text *.txt *.text
$ ls *.txt
abc-u.txt abc.txt def-u.txt def.text
$ cat abc-u.txt
ABC
Toy.JuicyPixels4
JuicyPixels5 is a great package for loading and saving images, and I often use it to munge images in command line tools.
Toy.JuicyPixels deals exclusively with PixelRGB8 images because most of the images I encounter are in that format.
loadImage, loadImageThen
As their names suggest these loadImages, but unlike readImage in the JuicyPixels library force the data loaded into PixelRGB8 format.
loadImageThen also handles error conditions by printing an error message: this simplifies life for the user.
Here’s a complete GHCI session showing code to extract the width of an image:
$ stack ghci
Run from outside a project, using implicit global project config
...
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Ok, modules loaded: none.
ghci> import Codec.Picture
ghci> import Toy.JuicyPixels
ghci> loadImageThen (putStrLn . show . imageWidth) "avatar.png"
420
transformImagePNG, transformImagePNG'
These load an image, transform its contents, then saves the result as a PNG file. The PNG' version uses a fixed basename transformation: adding -x.
Again here’s an example in ghci: swapping red and blue channels:
$ stack ghci
Run from outside a project, using implicit global project config
...
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Ok, modules loaded: none.
ghci> import Codec.Picture
ghci> import Toy.JuicyPixels
ghci> let px = pixelMap (\(PixelRGB8 r g b) -> PixelRGB8 b g r)
gchi> transformImagePNG' px "bird.jpg"
transformImagesInArgsPNG
As above, but iterate over the command line arguments, this time in an application.
import Toy.JuicyPixels
import Codec.Picture
main = transformImagesInArgsPNG px (++ "-f")
px :: ImageRGB8 -> ImageRGB8
px = pixelMap (\(PixelRGB8 r g b) -> PixelRGB8 b g r)
transformImage, transformImagesInArgs
These are more general versions which can write any file format. Accordingly the caller must provide code to save the data, and an appropriate suffix. Unsurprisingly the PNG versions are implemented in terms of these. For example:
transformImagePNG = transformImage writePng "png"
describeImage, describeImagesInArgs
Sometimes, it’s enough to extract data from the image and print it out. Rather than taking a String, any Show instance will do.
Here’s a reimplementation of our image width displayer:
ghci> describeImage imageWidth "avatar.png"
avatar.png:
420
Note that as well as the output, it also prints the filename.
iPixelList, pixelList
Return a list of pixels in the image. This isn’t very efficient, but it can be convenient.
ht-pixel-freqs, one of the toy applications in the distribution uses this:
import Toy.JuicyPixels
import Codec.Picture
import qualified Data.List as L
import Text.Printf
main = describeImagesInArgs countPixels
countPixels :: ImageRGB8 -> String
countPixels = concatMap pp . freqs . pixelList
freqs :: (Ord a, Eq a) => [a] -> [(a,Int)]
freqs = map (\ps -> (head ps, length ps)) . L.group . L.sort
pp (PixelRGB8 r g b, n) = printf "%3d %3d %3d: %8d\n" r g b n
liftRGB, memoizeWord8
A couple of combinators to help with efficient, colour-blind, transforms. The haskell-toys distribution includes a program which flips the bits in each pixel, which I find helpful to reveal hidden low-level structure.
import Toy.JuicyPixels
import Codec.Picture
import Data.Word
import Data.Bits
import qualified Data.List as L
main = transformImagesInArgsPNG flipImage (++ "-f")
flipImage :: ImageRGB8 -> ImageRGB8
flipImage = pixelMap (liftRGB flipByte)
flipByte :: Word8 -> Word8
flipByte = memoizeWord8 flipByte'
flipByte' :: Word8 -> Word8
flipByte' x = L.foldl' setBit 0 $ [ 7 - i | i <- [0..7], testBit x i ]
References
- 1. https://github.com/mjoldfield/haskell-toys
- 2. https://s3.amazonaws.com/mjoldfield-stack-docs/haskell-toys/index.html
- 3. https://s3.amazonaws.com/mjoldfield-stack-docs/haskell-toys/Toy-Generic.html
- 4. https://s3.amazonaws.com/mjoldfield-stack-docs/haskell-toys/Toy-JuicyPixels.html
- 5. http://hackage.haskell.org/package/JuicyPixels