diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # Revision history for reanimate
 
+## 0.3.1.0 -- 2020-05-12
+
+* Expose 'mkImage'
+
 ## 0.3.0.0 -- 2020-05-11
 
 * Improve README.md with better examples at a higher framerate.
diff --git a/docs/gifs/doc_andThen.gif b/docs/gifs/doc_andThen.gif
Binary files a/docs/gifs/doc_andThen.gif and b/docs/gifs/doc_andThen.gif differ
diff --git a/docs/gifs/doc_bellS.gif b/docs/gifs/doc_bellS.gif
Binary files a/docs/gifs/doc_bellS.gif and b/docs/gifs/doc_bellS.gif differ
diff --git a/docs/gifs/doc_bonneP.gif b/docs/gifs/doc_bonneP.gif
Binary files a/docs/gifs/doc_bonneP.gif and b/docs/gifs/doc_bonneP.gif differ
diff --git a/docs/gifs/doc_bottomleyP.gif b/docs/gifs/doc_bottomleyP.gif
Binary files a/docs/gifs/doc_bottomleyP.gif and b/docs/gifs/doc_bottomleyP.gif differ
diff --git a/docs/gifs/doc_circlePlot.gif b/docs/gifs/doc_circlePlot.gif
Binary files a/docs/gifs/doc_circlePlot.gif and b/docs/gifs/doc_circlePlot.gif differ
diff --git a/docs/gifs/doc_cividis.gif b/docs/gifs/doc_cividis.gif
Binary files a/docs/gifs/doc_cividis.gif and b/docs/gifs/doc_cividis.gif differ
diff --git a/docs/gifs/doc_collignonP.gif b/docs/gifs/doc_collignonP.gif
Binary files a/docs/gifs/doc_collignonP.gif and b/docs/gifs/doc_collignonP.gif differ
diff --git a/docs/gifs/doc_constantS.gif b/docs/gifs/doc_constantS.gif
Binary files a/docs/gifs/doc_constantS.gif and b/docs/gifs/doc_constantS.gif differ
diff --git a/docs/gifs/doc_cubicBezierS.gif b/docs/gifs/doc_cubicBezierS.gif
Binary files a/docs/gifs/doc_cubicBezierS.gif and b/docs/gifs/doc_cubicBezierS.gif differ
diff --git a/docs/gifs/doc_curveS.gif b/docs/gifs/doc_curveS.gif
Binary files a/docs/gifs/doc_curveS.gif and b/docs/gifs/doc_curveS.gif differ
diff --git a/docs/gifs/doc_drawBox.gif b/docs/gifs/doc_drawBox.gif
Binary files a/docs/gifs/doc_drawBox.gif and b/docs/gifs/doc_drawBox.gif differ
diff --git a/docs/gifs/doc_drawCircle.gif b/docs/gifs/doc_drawCircle.gif
Binary files a/docs/gifs/doc_drawCircle.gif and b/docs/gifs/doc_drawCircle.gif differ
diff --git a/docs/gifs/doc_drawProgress.gif b/docs/gifs/doc_drawProgress.gif
Binary files a/docs/gifs/doc_drawProgress.gif and b/docs/gifs/doc_drawProgress.gif differ
diff --git a/docs/gifs/doc_eckert1P.gif b/docs/gifs/doc_eckert1P.gif
Binary files a/docs/gifs/doc_eckert1P.gif and b/docs/gifs/doc_eckert1P.gif differ
diff --git a/docs/gifs/doc_eckert3P.gif b/docs/gifs/doc_eckert3P.gif
Binary files a/docs/gifs/doc_eckert3P.gif and b/docs/gifs/doc_eckert3P.gif differ
diff --git a/docs/gifs/doc_eckert5P.gif b/docs/gifs/doc_eckert5P.gif
Binary files a/docs/gifs/doc_eckert5P.gif and b/docs/gifs/doc_eckert5P.gif differ
diff --git a/docs/gifs/doc_equirectangularP.gif b/docs/gifs/doc_equirectangularP.gif
Binary files a/docs/gifs/doc_equirectangularP.gif and b/docs/gifs/doc_equirectangularP.gif differ
diff --git a/docs/gifs/doc_faheyP.gif b/docs/gifs/doc_faheyP.gif
Binary files a/docs/gifs/doc_faheyP.gif and b/docs/gifs/doc_faheyP.gif differ
diff --git a/docs/gifs/doc_fromToS.gif b/docs/gifs/doc_fromToS.gif
Binary files a/docs/gifs/doc_fromToS.gif and b/docs/gifs/doc_fromToS.gif differ
diff --git a/docs/gifs/doc_githubIcon.gif b/docs/gifs/doc_githubIcon.gif
Binary files a/docs/gifs/doc_githubIcon.gif and b/docs/gifs/doc_githubIcon.gif differ
diff --git a/docs/gifs/doc_greyscale.gif b/docs/gifs/doc_greyscale.gif
Binary files a/docs/gifs/doc_greyscale.gif and b/docs/gifs/doc_greyscale.gif differ
diff --git a/docs/gifs/doc_hammerP.gif b/docs/gifs/doc_hammerP.gif
Binary files a/docs/gifs/doc_hammerP.gif and b/docs/gifs/doc_hammerP.gif differ
diff --git a/docs/gifs/doc_haskellLogo.gif b/docs/gifs/doc_haskellLogo.gif
Binary files a/docs/gifs/doc_haskellLogo.gif and b/docs/gifs/doc_haskellLogo.gif differ
diff --git a/docs/gifs/doc_hsv.gif b/docs/gifs/doc_hsv.gif
Binary files a/docs/gifs/doc_hsv.gif and b/docs/gifs/doc_hsv.gif differ
diff --git a/docs/gifs/doc_hsvComponents.gif b/docs/gifs/doc_hsvComponents.gif
Binary files a/docs/gifs/doc_hsvComponents.gif and b/docs/gifs/doc_hsvComponents.gif differ
diff --git a/docs/gifs/doc_hsvMatlab.gif b/docs/gifs/doc_hsvMatlab.gif
Binary files a/docs/gifs/doc_hsvMatlab.gif and b/docs/gifs/doc_hsvMatlab.gif differ
diff --git a/docs/gifs/doc_inferno.gif b/docs/gifs/doc_inferno.gif
Binary files a/docs/gifs/doc_inferno.gif and b/docs/gifs/doc_inferno.gif differ
diff --git a/docs/gifs/doc_jet.gif b/docs/gifs/doc_jet.gif
Binary files a/docs/gifs/doc_jet.gif and b/docs/gifs/doc_jet.gif differ
diff --git a/docs/gifs/doc_labComponents.gif b/docs/gifs/doc_labComponents.gif
Binary files a/docs/gifs/doc_labComponents.gif and b/docs/gifs/doc_labComponents.gif differ
diff --git a/docs/gifs/doc_lambertP.gif b/docs/gifs/doc_lambertP.gif
Binary files a/docs/gifs/doc_lambertP.gif and b/docs/gifs/doc_lambertP.gif differ
diff --git a/docs/gifs/doc_latex.gif b/docs/gifs/doc_latex.gif
Binary files a/docs/gifs/doc_latex.gif and b/docs/gifs/doc_latex.gif differ
diff --git a/docs/gifs/doc_latexAlign.gif b/docs/gifs/doc_latexAlign.gif
Binary files a/docs/gifs/doc_latexAlign.gif and b/docs/gifs/doc_latexAlign.gif differ
diff --git a/docs/gifs/doc_lchComponents.gif b/docs/gifs/doc_lchComponents.gif
Binary files a/docs/gifs/doc_lchComponents.gif and b/docs/gifs/doc_lchComponents.gif differ
diff --git a/docs/gifs/doc_magma.gif b/docs/gifs/doc_magma.gif
Binary files a/docs/gifs/doc_magma.gif and b/docs/gifs/doc_magma.gif differ
diff --git a/docs/gifs/doc_mapA.gif b/docs/gifs/doc_mapA.gif
Binary files a/docs/gifs/doc_mapA.gif and b/docs/gifs/doc_mapA.gif differ
diff --git a/docs/gifs/doc_mercatorP.gif b/docs/gifs/doc_mercatorP.gif
Binary files a/docs/gifs/doc_mercatorP.gif and b/docs/gifs/doc_mercatorP.gif differ
diff --git a/docs/gifs/doc_mkBackground.gif b/docs/gifs/doc_mkBackground.gif
Binary files a/docs/gifs/doc_mkBackground.gif and b/docs/gifs/doc_mkBackground.gif differ
diff --git a/docs/gifs/doc_mkImage.gif b/docs/gifs/doc_mkImage.gif
new file mode 100644
Binary files /dev/null and b/docs/gifs/doc_mkImage.gif differ
diff --git a/docs/gifs/doc_mkText.gif b/docs/gifs/doc_mkText.gif
Binary files a/docs/gifs/doc_mkText.gif and b/docs/gifs/doc_mkText.gif differ
diff --git a/docs/gifs/doc_mollweideP.gif b/docs/gifs/doc_mollweideP.gif
Binary files a/docs/gifs/doc_mollweideP.gif and b/docs/gifs/doc_mollweideP.gif differ
diff --git a/docs/gifs/doc_oscillateS.gif b/docs/gifs/doc_oscillateS.gif
Binary files a/docs/gifs/doc_oscillateS.gif and b/docs/gifs/doc_oscillateS.gif differ
diff --git a/docs/gifs/doc_parA.gif b/docs/gifs/doc_parA.gif
Binary files a/docs/gifs/doc_parA.gif and b/docs/gifs/doc_parA.gif differ
diff --git a/docs/gifs/doc_parDropA.gif b/docs/gifs/doc_parDropA.gif
Binary files a/docs/gifs/doc_parDropA.gif and b/docs/gifs/doc_parDropA.gif differ
diff --git a/docs/gifs/doc_parLoopA.gif b/docs/gifs/doc_parLoopA.gif
Binary files a/docs/gifs/doc_parLoopA.gif and b/docs/gifs/doc_parLoopA.gif differ
diff --git a/docs/gifs/doc_parula.gif b/docs/gifs/doc_parula.gif
Binary files a/docs/gifs/doc_parula.gif and b/docs/gifs/doc_parula.gif differ
diff --git a/docs/gifs/doc_pathify.gif b/docs/gifs/doc_pathify.gif
Binary files a/docs/gifs/doc_pathify.gif and b/docs/gifs/doc_pathify.gif differ
diff --git a/docs/gifs/doc_pause.gif b/docs/gifs/doc_pause.gif
Binary files a/docs/gifs/doc_pause.gif and b/docs/gifs/doc_pause.gif differ
diff --git a/docs/gifs/doc_pauseAround.gif b/docs/gifs/doc_pauseAround.gif
Binary files a/docs/gifs/doc_pauseAround.gif and b/docs/gifs/doc_pauseAround.gif differ
diff --git a/docs/gifs/doc_pauseAtBeginning.gif b/docs/gifs/doc_pauseAtBeginning.gif
Binary files a/docs/gifs/doc_pauseAtBeginning.gif and b/docs/gifs/doc_pauseAtBeginning.gif differ
diff --git a/docs/gifs/doc_pauseAtEnd.gif b/docs/gifs/doc_pauseAtEnd.gif
Binary files a/docs/gifs/doc_pauseAtEnd.gif and b/docs/gifs/doc_pauseAtEnd.gif differ
diff --git a/docs/gifs/doc_plasma.gif b/docs/gifs/doc_plasma.gif
Binary files a/docs/gifs/doc_plasma.gif and b/docs/gifs/doc_plasma.gif differ
diff --git a/docs/gifs/doc_playThenReverseA.gif b/docs/gifs/doc_playThenReverseA.gif
Binary files a/docs/gifs/doc_playThenReverseA.gif and b/docs/gifs/doc_playThenReverseA.gif differ
diff --git a/docs/gifs/doc_repeatA.gif b/docs/gifs/doc_repeatA.gif
Binary files a/docs/gifs/doc_repeatA.gif and b/docs/gifs/doc_repeatA.gif differ
diff --git a/docs/gifs/doc_reverseA.gif b/docs/gifs/doc_reverseA.gif
Binary files a/docs/gifs/doc_reverseA.gif and b/docs/gifs/doc_reverseA.gif differ
diff --git a/docs/gifs/doc_reverseS.gif b/docs/gifs/doc_reverseS.gif
Binary files a/docs/gifs/doc_reverseS.gif and b/docs/gifs/doc_reverseS.gif differ
diff --git a/docs/gifs/doc_rgbComponents.gif b/docs/gifs/doc_rgbComponents.gif
Binary files a/docs/gifs/doc_rgbComponents.gif and b/docs/gifs/doc_rgbComponents.gif differ
diff --git a/docs/gifs/doc_seqA.gif b/docs/gifs/doc_seqA.gif
Binary files a/docs/gifs/doc_seqA.gif and b/docs/gifs/doc_seqA.gif differ
diff --git a/docs/gifs/doc_signalA.gif b/docs/gifs/doc_signalA.gif
Binary files a/docs/gifs/doc_signalA.gif and b/docs/gifs/doc_signalA.gif differ
diff --git a/docs/gifs/doc_sinebow.gif b/docs/gifs/doc_sinebow.gif
Binary files a/docs/gifs/doc_sinebow.gif and b/docs/gifs/doc_sinebow.gif differ
diff --git a/docs/gifs/doc_sinusoidalP.gif b/docs/gifs/doc_sinusoidalP.gif
Binary files a/docs/gifs/doc_sinusoidalP.gif and b/docs/gifs/doc_sinusoidalP.gif differ
diff --git a/docs/gifs/doc_smallEarth.gif b/docs/gifs/doc_smallEarth.gif
Binary files a/docs/gifs/doc_smallEarth.gif and b/docs/gifs/doc_smallEarth.gif differ
diff --git a/docs/gifs/doc_svgLogo.gif b/docs/gifs/doc_svgLogo.gif
Binary files a/docs/gifs/doc_svgLogo.gif and b/docs/gifs/doc_svgLogo.gif differ
diff --git a/docs/gifs/doc_ternaryPlot.gif b/docs/gifs/doc_ternaryPlot.gif
Binary files a/docs/gifs/doc_ternaryPlot.gif and b/docs/gifs/doc_ternaryPlot.gif differ
diff --git a/docs/gifs/doc_turbo.gif b/docs/gifs/doc_turbo.gif
Binary files a/docs/gifs/doc_turbo.gif and b/docs/gifs/doc_turbo.gif differ
diff --git a/docs/gifs/doc_viridis.gif b/docs/gifs/doc_viridis.gif
Binary files a/docs/gifs/doc_viridis.gif and b/docs/gifs/doc_viridis.gif differ
diff --git a/docs/gifs/doc_wernerP.gif b/docs/gifs/doc_wernerP.gif
Binary files a/docs/gifs/doc_wernerP.gif and b/docs/gifs/doc_wernerP.gif differ
diff --git a/docs/gifs/doc_xelatex.gif b/docs/gifs/doc_xelatex.gif
Binary files a/docs/gifs/doc_xelatex.gif and b/docs/gifs/doc_xelatex.gif differ
diff --git a/docs/gifs/doc_xyzComponents.gif b/docs/gifs/doc_xyzComponents.gif
Binary files a/docs/gifs/doc_xyzComponents.gif and b/docs/gifs/doc_xyzComponents.gif differ
diff --git a/examples/doc_mkImage.hs b/examples/doc_mkImage.hs
new file mode 100644
--- /dev/null
+++ b/examples/doc_mkImage.hs
@@ -0,0 +1,10 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+module Main (main) where
+
+import           Reanimate
+import           Reanimate.Builtin.Documentation
+
+main :: IO ()
+main = reanimate $ docEnv $ staticFrame 1 $
+    mkImage screenWidth screenHeight "../data/haskell.svg"
diff --git a/examples/tut_glue_povray.hs b/examples/tut_glue_povray.hs
--- a/examples/tut_glue_povray.hs
+++ b/examples/tut_glue_povray.hs
@@ -7,7 +7,6 @@
 
 import           Reanimate
 import           Reanimate.Povray      (povraySlow')
-import           Reanimate.Raster
 
 import           Codec.Picture
 import           Codec.Picture.Types
diff --git a/reanimate.cabal b/reanimate.cabal
--- a/reanimate.cabal
+++ b/reanimate.cabal
@@ -3,7 +3,7 @@
 --  see http://haskell.org/cabal/users-guide/
 
 name:                reanimate
-version:             0.3.0.0
+version:             0.3.1.0
 -- synopsis:
 -- description:
 license:             PublicDomain
diff --git a/src/Reanimate.hs b/src/Reanimate.hs
--- a/src/Reanimate.hs
+++ b/src/Reanimate.hs
@@ -141,11 +141,14 @@
     module Reanimate.Svg.BoundingBox,
     module Reanimate.Svg,
     -- ** Raster data
+    mkImage,
     embedImage,
     embedDynamicImage,
     embedPng,
     raster,
+    rasterSized,
     svgAsPngFile,
+    svgAsPngFile',
     vectorize,
     vectorize_,
     -- ** External SVG providers
diff --git a/src/Reanimate/Driver.hs b/src/Reanimate/Driver.hs
--- a/src/Reanimate/Driver.hs
+++ b/src/Reanimate/Driver.hs
@@ -11,11 +11,10 @@
 import           Reanimate.Driver.Server
 import           Reanimate.Parameters
 import           Reanimate.Render         (FPS, Format (..), Height, Width,
-                                            render, renderSnippets, renderSvgs)
+                                            render, renderSnippets, renderSvgs, selectRaster)
 import           System.Directory
 import           System.FilePath
 import           Text.Printf
-import           Data.Either
 
 presetFormat :: Preset -> Format
 presetFormat Youtube    = RenderMp4
@@ -160,17 +159,6 @@
             fps width height (showFormat fmt) target (show raster)
 
           render animation target raster fmt width height fps
-
-selectRaster :: Raster -> IO Raster
-selectRaster RasterAuto = do
-  rsvg <- hasRSvg
-  ink <- hasInkscape
-  conv <- hasConvert
-  if | isRight rsvg -> pure RasterRSvg
-     | isRight ink  -> pure RasterInkscape
-     | isRight conv -> pure RasterConvert
-     | otherwise    -> pure RasterNone
-selectRaster r = pure r
 
 guessParameter :: Maybe a -> Maybe a -> a -> a
 guessParameter a b def = fromMaybe def (a <|> b)
diff --git a/src/Reanimate/Parameters.hs b/src/Reanimate/Parameters.hs
--- a/src/Reanimate/Parameters.hs
+++ b/src/Reanimate/Parameters.hs
@@ -30,7 +30,7 @@
   | RasterInkscape
   | RasterRSvg
   | RasterConvert
-  deriving (Show)
+  deriving (Show, Eq)
 
 {-# NOINLINE pRasterRef #-}
 pRasterRef :: IORef Raster
diff --git a/src/Reanimate/Raster.hs b/src/Reanimate/Raster.hs
--- a/src/Reanimate/Raster.hs
+++ b/src/Reanimate/Raster.hs
@@ -29,6 +29,7 @@
 import           Reanimate.Misc
 import           Reanimate.Render
 import           Reanimate.Parameters
+import           Reanimate.Constants
 import           Reanimate.Svg.Constructors
 import           Reanimate.Svg.Unuse
 import           System.Directory
@@ -37,8 +38,49 @@
 import           System.IO.Temp
 import           System.IO.Unsafe
 
--- FIXME: Embed the image data as inline base64 iff no raster engine is specified.
-mkImage :: Double -> Double -> FilePath -> SVG
+-- | Load an external image. Width and height must be specified,
+--   ignoring the image's aspect ratio. The center of the image is
+--   placed at position (0,0).
+--
+--   For security reasons, must SVG renderer do not allow arbitrary
+--   image links. For some renderers, we can get around this by placing
+--   the images in the same root directory as the parent SVG file. Other
+--   renderers (like Chrome and ffmpeg) requires that the image is inlined
+--   as base64 data. External SVG files are an exception, though, as must
+--   always be inlined directly. `mkImage` attempts to hide all the complexity
+--   but edge-cases may exist.
+--
+--   Example:
+--
+--   > mkImage screenWidth screenHeight "../data/haskell.svg"
+--
+--   <<docs/gifs/doc_mkImage.gif>>
+mkImage :: Double -- ^ Desired image width.
+        -> Double -- ^ Desired image height.
+        -> FilePath -- ^ Path to external image file.
+        -> SVG
+mkImage width height path | takeExtension path == ".svg" = unsafePerformIO $ do
+  svg_data <- B.readFile path
+  case parseSvgFile path svg_data of
+    Nothing  -> error "Malformed svg"
+    Just svg -> return $
+      scaleXY (width/screenWidth) (height/screenHeight) $
+      embedDocument svg
+mkImage width height path | pRaster == RasterNone = unsafePerformIO $ do
+  inp <- LBS.readFile path
+  let imgData = LBS.unpack $ Base64.encode inp
+  return $ flipYAxis $ ImageTree $ defaultSvg
+    & Svg.imageWidth .~ Svg.Num width
+    & Svg.imageHeight .~ Svg.Num height
+    & Svg.imageHref .~ ("data:"++mimeType++";base64,"++imgData)
+    & Svg.imageCornerUpperLeft .~ (Svg.Num (-width/2), Svg.Num (-height/2))
+    & Svg.imageAspectRatio .~ Svg.PreserveAspectRatio False Svg.AlignNone Nothing
+  where
+    -- FIXME: Is there a better way to do this?
+    mimeType =
+      case takeExtension path of
+        ".jpg" -> "image/jpeg"
+        ext    -> "image/" ++ drop 1 ext
 mkImage width height path = unsafePerformIO $ do
     exists <- doesFileExist target
     unless exists $ copyFile path target
@@ -59,13 +101,21 @@
     template = show (hash key) <.> "png"
 
 {-# INLINE embedImage #-}
-embedImage :: PngSavable a => Image a -> Tree
+-- | Embed an in-memory PNG image. Note, the pixel size of the image
+--   is used as the dimensions. As such, embedding a 100x100 PNG will
+--   result in an image 100 units wide and 100 units high. Consider
+--   using with 'scaleToSize'.
+embedImage :: PngSavable a => Image a -> SVG
 embedImage img = embedPng width height (encodePng img)
   where
     width  = fromIntegral $ imageWidth img
     height = fromIntegral $ imageHeight img
 
-embedPng :: Double -> Double -> LBS.ByteString -> Tree
+-- | Embed in-memory PNG bytestring without parsing it.
+embedPng :: Double -- ^ Width
+         -> Double -- ^ Height
+         -> LBS.ByteString -- ^ Raw PNG data
+         -> SVG
 -- embedPng w h png = unsafePerformIO $ do
 --     LBS.writeFile path png
 --     return $ ImageTree $ defaultSvg
@@ -86,7 +136,11 @@
 
 
 {-# INLINE embedDynamicImage #-}
-embedDynamicImage :: DynamicImage -> Tree
+-- | Embed an in-memory image. Note, the pixel size of the image
+--   is used as the dimensions. As such, embedding a 100x100 image will
+--   result in an image 100 units wide and 100 units high. Consider
+--   using with 'scaleToSize'.
+embedDynamicImage :: DynamicImage -> SVG
 embedDynamicImage img = embedPng width height imgData
   where
     width   = fromIntegral $ dynamicMap imageWidth img
@@ -111,21 +165,29 @@
 --           & Svg.imageHref .~ ("file://" ++ path)
 
 
-
-raster :: Tree -> DynamicImage
+-- | Convert an SVG object to a pixel-based image. The default resolution
+--   is 2560x1440. See also 'rasterSized'. Multiple raster engines are supported
+--   and are selected using the '--raster' flag in the driver.
+raster :: SVG -> DynamicImage
 raster = rasterSized 2560 1440
 
-rasterSized :: Int -> Int -> Tree -> DynamicImage
+-- | Convert an SVG object to a pixel-based image.
+rasterSized :: Int -- ^ X resolution in pixels
+            -> Int -- ^ Y resolution in pixels
+            -> SVG -- ^ SVG object
+            -> DynamicImage
 rasterSized w h svg = unsafePerformIO $ do
     png <- B.readFile (svgAsPngFile' w h svg)
     case decodePng png of
       Left{}    -> error "bad image"
       Right img -> return img
 
-vectorize :: FilePath -> Tree
+-- | Use 'potrace' to trace edges in a raster image and convert them to SVG polygons.
+vectorize :: FilePath -> SVG
 vectorize = vectorize_ []
 
-vectorize_ :: [String] -> FilePath -> Tree
+-- | Same as 'vectorize' but takes a list of arguments for 'potrace'.
+vectorize_ :: [String] -> FilePath -> SVG
 vectorize_ _ path | pNoExternals = mkText $ T.pack path
 vectorize_ args path = unsafePerformIO $ do
     root <- getXdgDirectory XdgCache "reanimate"
@@ -154,22 +216,28 @@
 -- imageAsFile :: DynamicImage -> FilePath
 -- imageAsFile img
 
-svgAsPngFile :: Tree -> FilePath
+-- | Convert an SVG object to a pixel-based image and save it to disk, returning
+--   the filepath. The default resolution is 2560x1440. See also 'svgAsPngFile''.
+--   Multiple raster engines are supported and are selected using the '--raster'
+--   flag in the driver.
+svgAsPngFile :: SVG -> FilePath
 svgAsPngFile = svgAsPngFile' width height
   where
     width = 2560
     height = width * 9 `div` 16
 
-svgAsPngFile' :: Int -> Int -> Tree -> FilePath
+-- | Convert an SVG object to a pixel-based image and save it to disk, returning
+--   the filepath.
+svgAsPngFile' :: Int -- ^ Width
+              -> Int -- ^ Height
+              -> SVG -- ^ SVG object
+              -> FilePath
 svgAsPngFile' _ _ _ | pNoExternals = "/svgAsPngFile/has/been/disabled"
 svgAsPngFile' width height svg = unsafePerformIO $ cacheFile template $ \pngPath -> do
     let svgPath = replaceExtension pngPath "svg"
-    -- ffmpeg <- requireExecutable "ffmpeg"
-    -- convert <- requireExecutable "convert"
-    -- inkscape <- requireExecutable "inkscape"
     writeFile svgPath rendered
-    -- FIXME: raster should be configurable.
-    applyRaster RasterRSvg svgPath
+    engine <- requireRaster pRaster
+    applyRaster engine svgPath
   where
     template = show (hash rendered) <.> "png"
     rendered = renderSvg (Just $ Px $ fromIntegral width) (Just $ Px $ fromIntegral height) svg
diff --git a/src/Reanimate/Render.hs b/src/Reanimate/Render.hs
--- a/src/Reanimate/Render.hs
+++ b/src/Reanimate/Render.hs
@@ -1,11 +1,14 @@
+{-# LANGUAGE MultiWayIf #-}
 module Reanimate.Render
   ( render
-  , renderSvgs
-  , renderSnippets
+  , renderSvgs            -- :: Animation -> IO ()
+  , renderSnippets        -- :: Animation -> IO ()
   , Format(..)
   , Raster(..)
   , Width, Height, FPS
-  , applyRaster
+  , requireRaster         -- :: Raster -> IO Raster
+  , selectRaster          -- :: Raster -> IO Raster
+  , applyRaster           -- :: Raster -> FilePath -> IO ()
   ) where
 
 import           Control.Concurrent
@@ -14,18 +17,20 @@
 import qualified Data.Text           as T
 import qualified Data.Text.IO        as T
 import           Data.Time
+import Data.Either
 import           Graphics.SvgTree    (Number (..))
 import           Numeric
 import           Reanimate.Animation
 import           Reanimate.Misc
 import           Reanimate.Parameters
+import           Reanimate.Driver.Check
 import           System.Exit
 import           System.FilePath     ((</>))
 import           System.FilePath     (replaceExtension)
 import           System.IO
 import           Text.Printf         (printf)
 
-renderSvgs :: Animation ->  IO ()
+renderSvgs :: Animation -> IO ()
 renderSvgs ani = do
     print frameCount
     lock <- newMVar ()
@@ -181,6 +186,28 @@
 rasterTemplate :: Raster -> String
 rasterTemplate RasterNone = "render-%05d.svg"
 rasterTemplate _          = "render-%05d.png"
+
+requireRaster :: Raster -> IO Raster
+requireRaster raster = do
+  raster' <- selectRaster (if raster==RasterNone then RasterAuto else raster)
+  case raster' of
+    RasterNone -> do
+      hPutStrLn stderr
+        "Raster required but none could be found. \
+        \Please install either inkscape, imagemagick, or rsvg-convert."
+      exitWith (ExitFailure 1)
+    _ -> pure raster'
+
+selectRaster :: Raster -> IO Raster
+selectRaster RasterAuto = do
+  rsvg <- hasRSvg
+  ink <- hasInkscape
+  conv <- hasConvert
+  if | isRight rsvg -> pure RasterRSvg
+     | isRight ink  -> pure RasterInkscape
+     | isRight conv -> pure RasterConvert
+     | otherwise    -> pure RasterNone
+selectRaster r = pure r
 
 applyRaster :: Raster -> FilePath -> IO ()
 applyRaster RasterNone _ = return ()
