More Haskell Diagrams: Images

Table of contents

Let's continue exploring the Haskell diagrams library. In this post, we will embed external images in our diagrams.

Motivation

I would like to create images from Haskell diagrams with embedded images. At the same time, I want to use diagrams' cairo backend (diagrams-cairo) instead of the svg backend.

Program

I am going to need 3 Haskell packages for this Literate Haskell program: diagrams, diagrams-cairo and markdown-unlit. It is noted in the diagrams documentation page that the cairo backend is a little tricky to install in some environments. Luckily, nixpkgs has a well-packaged version of diagrams-cairo:

{
  ##...

  ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: [
    ## ...
    hpkgs.diagrams
    hpkgs.diagrams-cairo
    hpkgs.markdown-unlit
    ## ...
  ]);

  thisShell = pkgs.mkShell {
    buildInputs = [
      ## ...

      ghc

      ## ...
    ];

    NIX_GHC = "${ghc}/bin/ghc";
    NIX_GHCPKG = "${ghc}/bin/ghc-pkg";
    NIX_GHC_DOCDIR = "${ghc}/share/doc/ghc/html";
    NIX_GHC_LIBDIR = "${ghc}/lib/ghc-9.6.5/lib";
  };

  # ...
}

We will need to import a few modules.

import Diagrams.Backend.Cairo
import Diagrams.Prelude
import System.Environment (getArgs)

Unlike the previous post, we are importing Diagrams.Backend.Cairo instead of Diagrams.Backend.SVG. The API interface is pretty much the same, though.

Why cairo? Firstly, image clipping does not work with the diagrams-svg backend for some reason. I guess why, but I will not speculate here. Secondly, I want to see how diagrams-cairo backend works for me.

We will generate a few diagrams in this tutorial. Let's implement our entry point:

main :: IO ()
main = do
  dir <- head <$> getArgs
  logo <- loadLogo
  render dir "diagram-logo.png" logo
  render dir "diagram-logo-clipped.png" (mkAvatar logo)
  render dir "diagram-logo-clipped-with-text.png" (mkAvatarWithText "The Negation" logo)
  render dir "diagram1.svg" (mkAvatarWithText "SVG (~20KB)" logo)
  render dir "diagram1.png" (mkAvatarWithText "PNG (~12KB)" logo)
  render dir "diagram1.jpg" (mkAvatarWithText "JPG (~12KB)" logo)
  where
    render dpath fname = renderCairo (dpath <> "/" <> fname) (mkSizeSpec2D (Just 240) Nothing) . frame 0.2

This is how we will run our blog post:

runhaskell \
  -pgmLmarkdown-unlit \
  content/posts/2024-08-10_haskell-diagrams-images.lhs \
  static/assets/media/posts/haskell-diagrams-images

Our objective is to create an avatar image for my blog. It will be a circle with my Website's logo in it. We will also add some text under the avatar image.

This is how my Website's logo looks like as a raw PNG:

Let's load the image inside a Diagram B value. We are scaling the diagram to 10:

loadLogo :: IO (Diagram B)
loadLogo = do
  (Right img) <- loadImageEmb "./static/android-chrome-512x512.png"
  pure $ scaleUToX 10 $ image img

Let's create a function to clip a given image in a circle. We will use the clipBy function to achieve this. Since our square image is scaled to 10, We are using a circle with a radius of 5:

mkAvatar :: Diagram B -> Diagram B
mkAvatar =
  clipBy (circle 5)

I also want to add some text under the image:

mkAvatarWithText :: String -> Diagram B -> Diagram B
mkAvatarWithText txt img =
  vsep 0.3 $
    [ mkAvatar img
    , text txt # fontSizeL 1 # fc black # frame 1
    ]

That's all! Let's see how SVG, PNG and JPG images look like:

Wrap-Up

I am getting used to the diagrams library. This onboarding process will take some time until I get the fundamentals right, but I am happy with the progress so far. It is also reassuring to see that this library is out for quite some time, it is actively maintained, and there is a decent community around it.

I will continue to explore the library in my future posts.

0
Subscribe to my newsletter

Read articles from Vehbi Sinan Tunalioglu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vehbi Sinan Tunalioglu
Vehbi Sinan Tunalioglu

My name is Sinan. I am a computer programmer and a life-style entrepreneur. You can check my LinkedIn and GitHub profile pages for more information, and send an email to vst@vsthost.com to contact me. I am re-publishing my technical blog posts on hashnode. My website is available on thenegation.com, and its source code is available on GitHub.