haskellhip

How to generate an image using Haskell Image Processing


I just started learning Haskell, so I am likely missing something very trivial. I am attempting to generate images using Haskell Image Processing. I am adapting the code from sample snippets from the docs. My code is as follows.

{-# LANGUAGE NoImplicitPrelude #-}
module Main where

import Prelude as P
import Graphics.Image as I

getPixel :: (Int, Int) -> Pixel RGB Word8
getPixel (i, j) = PixelRGB (fromIntegral i) (fromIntegral j) (fromIntegral (i + j))

getImage :: (Int, Int) -> Image VS RGB Word8
getImage (w, h) = makeImageR VS (w, h) getPixel

main :: IO ()
main = writeImage "image.png" image
    where image = getImage (1024, 1024)

when I try to build it, I get the following

• No instance for (Writable (Image VS RGB Word8) OutputFormat)
    arising from a use of ‘writeImage’
• In the expression: writeImage "image.png" image
  In an equation for ‘main’:
      main
        = writeImage "image.png" image
        where
            image = getImage (1024, 1024)

Cannot seem to figure out what I am doing wrong.


Solution

  • The HIP package makes extensive use of the type system to handle multiple external image formats and internal image layouts. In particular, the writeImage function has signature:

    writeImage
      :: (Array VS cs e, Array arr cs e, Writable (Image VS cs e) OutputFormat)  
      => FilePath   
      -> Image arr cs e 
      -> IO ()
    

    The key takeaway here is that, if you want to write an Image arr cs e using this function, that type has to obey the constraint (i.e., have a type class instance for):

    Writable (Image VS cs e) OutputFormat
    

    To see what instances the library makes available, you can take a look at the documentation for Writable which has a big list of available instances. However, the only Writable x y instance with y equal to OutputFormat (which means "a general format unknown at compile time") is:

    instance AllWritable arr cs => Writable (Image arr cs Double) OutputFormat   
    

    This means that only Image arr cs e with e equal to Double are compatible with the writeImage function. The reasoning behind this is probably that, because the writeImage function only identifies the file type to be written at runtime (by inspecting the file extension of the supplied file path), it only wants to accept an image whose channel data is already in a high-precision type (e.g., Double), in case it determines that the final format can handle high-precision channel data; you could argue that this restriction doesn't really make a whole lot of sense, but that's the way it is.

    So, as @DanielWagner pointed out, you need to provide writeImage with an image having Double channel data. Alternatively, you could use writeImageExact which specifies the format at compile time and allows the use of any Writable instance for the appropriate format. So, for example, replacing the writeImage call with:

    writeImageExact PNG [] "image.png" image
    

    would have worked fine, because there's an appropriate instance for it:

    instance Writable (Image VS RGB Word8) PNG