{-# LINE 1 "Codec/Image/DevIL.hsc" #-}
module Codec.Image.DevIL
{-# LINE 2 "Codec/Image/DevIL.hsc" #-}
    ( ilInit
    , readImage, writeImage, writeImageFromPtr
    , Word8
    )
where

import Data.Int
import Data.Word

import Foreign hiding (newArray)
import Foreign.C

import Data.Array.Storable
import Data.Array.Unboxed
import Control.Applicative
import Control.Monad

import System.IO.Unsafe (unsafeInterleaveIO)


{-# LINE 22 "Codec/Image/DevIL.hsc" #-}


type ILuint    = Word32
{-# LINE 25 "Codec/Image/DevIL.hsc" #-}
type ILsizei   = Word32
{-# LINE 26 "Codec/Image/DevIL.hsc" #-}
type ILboolean = Word8
{-# LINE 27 "Codec/Image/DevIL.hsc" #-}
type ILenum    = Word32
{-# LINE 28 "Codec/Image/DevIL.hsc" #-}
type ILint     = Int32
{-# LINE 29 "Codec/Image/DevIL.hsc" #-}
type ILubyte   = Word8
{-# LINE 30 "Codec/Image/DevIL.hsc" #-}

newtype ImageName = ImageName { fromImageName :: ILuint }


-- | Initialize the library.
foreign import CALLTYPE "ilInit" ilInitC :: IO ()
foreign import CALLTYPE "ilOriginFunc" ilOriginFuncC :: ILenum -> IO ILboolean
foreign import CALLTYPE "ilEnable" ilEnableC :: ILenum -> IO ILboolean

ilInit :: IO ()
ilInit = do
    ilInitC
    ilOriginFuncC (1537)
{-# LINE 43 "Codec/Image/DevIL.hsc" #-}
    ilEnableC (1536)
{-# LINE 44 "Codec/Image/DevIL.hsc" #-}
    return ()

-- | Reads an image into an RGBA array.  Indices are (row,column,color-channel).
readImage :: FilePath -> IO (UArray (Int,Int,Int) Word8)
readImage x =
  do [inname] <- ilGenImages 1
     ilBindImage inname
     ilLoadImage x
     a <- toArrayRGBA
     ilDeleteImages [inname]
     return a

-- | Writes an RGBA array to a file.  Indices are (row,column,color-channel).
writeImage :: FilePath -> UArray (Int,Int,Int) Word8 -> IO ()
writeImage f a =
  do [outname] <- ilGenImages 1
     ilBindImage outname
     fromArrayRGBA a
     ilSaveImage f
     ilDeleteImages [outname]

foreign import CALLTYPE "ilGenImages" ilGenImagesC
  :: ILsizei -> Ptr ILuint -> IO ()


ilGenImages :: Int -> IO [ImageName]
ilGenImages num = do
    ar <- newArray (0, num-1) 0
    withStorableArray ar $ \p -> do
        ilGenImagesC (fromIntegral num) p
    map ImageName <$> getElems ar


foreign import CALLTYPE "ilBindImage" ilBindImageC :: ILuint -> IO ()

ilBindImage :: ImageName -> IO ()
ilBindImage (ImageName name) = ilBindImageC name


foreign import CALLTYPE "ilDeleteImages" ilDeleteImagesC
    :: ILsizei -> Ptr ILuint -> IO ()

ilDeleteImages :: [ImageName] -> IO ()
ilDeleteImages names = do
    ar <- newListArray (0, length names-1) (fromImageName <$> names)
    withStorableArray ar $ \p -> do
        ilDeleteImagesC (fromIntegral $ length names) p


foreign import CALLTYPE "ilLoadImage" ilLoadImageC :: CString -> IO ILboolean

ilLoadImage :: FilePath -> IO Bool
ilLoadImage file = do
    (0 /=) <$> withCString file ilLoadImageC


foreign import CALLTYPE "ilSaveImage" ilSaveImageC :: CString -> IO ILboolean

ilSaveImage :: FilePath -> IO Bool
ilSaveImage file = do
    (0 /=) <$> withCString file ilSaveImageC


foreign import CALLTYPE "ilConvertImage" ilConvertImageC
    :: ILenum -> ILenum -> IO Bool

foreign import CALLTYPE "ilGetInteger" ilGetIntegerC
    :: ILenum -> IO ILint

foreign import CALLTYPE "ilCopyPixels" ilCopyPixelsC
    :: ILuint -> ILuint -> ILuint   -- x y z
    -> ILuint -> ILuint -> ILuint   -- w h depth
    -> ILenum -> ILenum             -- format type
    -> Ptr ()                       -- data (copy into this pointer)
    -> IO ()

foreign import CALLTYPE "ilSetPixels" ilSetPixelsC
    :: ILuint -> ILuint -> ILuint   -- x y z
    -> ILuint -> ILuint -> ILuint   -- w h depth
    -> ILenum -> ILenum             -- format type
    -> Ptr ()                       -- data (copy from this pointer)
    -> IO ()

foreign import CALLTYPE "ilTexImage" ilTexImageC
    :: ILuint -> ILuint -> ILuint   -- w h depth
    -> ILubyte -> ILenum -> ILenum  -- numberOfChannels format type
    -> Ptr ()                       -- data (copy from this pointer)
    -> IO Bool

il_RGBA = (6408) :: ILenum
{-# LINE 134 "Codec/Image/DevIL.hsc" #-}
il_UNSIGNED_BYTE = (5121) :: ILenum
{-# LINE 135 "Codec/Image/DevIL.hsc" #-}
il_IMAGE_HEIGHT = (3557) :: ILenum
{-# LINE 136 "Codec/Image/DevIL.hsc" #-}
il_IMAGE_WIDTH  = (3556)  :: ILenum
{-# LINE 137 "Codec/Image/DevIL.hsc" #-}

-- array indices are (x,y,channel) where channel: 0=Red, 1=Green, 2=Blue, 3=Alpha
toArrayRGBA :: IO (UArray (Int,Int,Int) Word8)
toArrayRGBA = do
    -- Arrays are stored in row major, so we have to be fiddly with
    -- our use of terminology.
    columns <- ilGetIntegerC il_IMAGE_WIDTH
    rows <- ilGetIntegerC il_IMAGE_HEIGHT
    let bounds = ((0,0,0), (fromIntegral rows-1, fromIntegral columns-1, 3))
    ar <- newArray_ bounds
    withStorableArray ar $ \p -> do
        ilCopyPixelsC 0 0 0 
                      (fromIntegral columns) (fromIntegral rows) 1 
                      il_RGBA il_UNSIGNED_BYTE 
                      (castPtr p)
    listArray bounds <$> lazyElems ar

lazyElems ar = do
    ixs <- range <$> getBounds ar
    go ixs
  where
    go [] = return []
    go (ix:ixs) = unsafeInterleaveIO $ liftM2 (:) (readArray ar ix) (go ixs)

-- same as toArrayRGBA
fromArrayRGBA :: UArray (Int,Int,Int) Word8 -> IO ()
fromArrayRGBA dat = do
    let ((0,0,0), (maxrow,maxcol,3)) = bounds dat
    ar <- unsafeThaw dat
    withStorableArray ar $ \p -> do
        ilTexImageC (fromIntegral maxcol+1) (fromIntegral maxrow+1) 1
                    4 il_RGBA il_UNSIGNED_BYTE
                    (castPtr p)
    return ()

-- | Write an image from a pointer to raw RGBA data.  Careful!  
-- The size tuple is (rows, columns), not (width, height).
writeImageFromPtr :: FilePath -> (Int,Int) -> Ptr Word8 -> IO ()
writeImageFromPtr f (rows,cols) p = do 
    [outname] <- ilGenImages 1
    ilBindImage outname
    fromPtrRGBA (rows,cols) p
    ilSaveImage f
    ilDeleteImages [outname]

fromPtrRGBA :: (Int,Int) -> Ptr Word8 -> IO ()
fromPtrRGBA (rows,cols) p = do
    ilTexImageC (fromIntegral cols) (fromIntegral rows) 1
                4 il_RGBA il_UNSIGNED_BYTE
                (castPtr p)
    return ()


-- vim: ft=haskell :