--
-- Module      : Codec.Image.STB
-- Version     : 0.1
-- License     : Public Domain
-- Author      : Balazs Komuves
-- Maintainer  : bkomuves@gmail.com
-- Stability   : experimental
-- Portability : portable(?), requires FFI and CPP
-- Tested with : GHC 6.8.2
--

-- |A wrapper around @stb_image@, Sean Barrett's public domain JPEG\/PNG decoder.
-- The original can be found at <http://nothings.org/stb_image.c>.
-- The version of @stb_image@ used here is @stbi-1.18@. 
-- The current list of (partially) supported formats is JPEG, PNG, TGA, BMP, PSD.
-- Please note that the library is not (fully) thread-safe!

{-# LANGUAGE ForeignFunctionInterface #-} 
{-# CFILES Codec/Image/stbi/stb_image.c #-}  -- for Hugs (?)
module Codec.Image.STB
  ( Image
  , withImage
  , rawImage
  , resolution
  , components
  , decodeImage
  , loadImage
  ) where

import Control.Monad (liftM)
import Control.Exception
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Foreign
import Foreign.C
import System.IO
import System.IO.Error

#if (__GLASGOW_HASKELL__ == 606)
import Data.ByteString.Base
#else
import Data.ByteString.Internal
#endif

-- |The type representing a simple rectangular image.
-- Internally it stores the resolution, the pixel format, and the raw pixel data 
-- as a strict 'ByteString'.
data Image = Image 
  { i_ptr :: ImgPtr             -- ^ \"pointer\" to the raw data
  , i_res :: (Int,Int)          -- ^ resolution 
  , i_fmt :: Format             -- ^ pixel format
  }
  
type Format = Int         -- number of components, see below
type ImgPtr = ByteString

withImgPtr :: ImgPtr -> (Ptr Word8 -> Int -> IO a) -> IO a
withImgPtr bs f = withForeignPtr fptr g where
  (fptr,ofs,len) = toForeignPtr bs
  g q = f (plusPtr q ofs) len

{-# SPECIALIZE withImage :: Image -> (Ptr Word8 -> (Int,Int) -> Int -> IO b) -> IO b #-}

-- |Access to the raw data. The user action receives a pointer, the spatial resolution and the
-- number of (8-bit) components per pixel.
--
-- Data format (bytes per pixel -> components):
--
--  * 1 -> grey
--
--  * 2 -> grey, alpha
--
--  * 3 -> red, green, blue
--
--  * 4 -> red, green, blue, alpha
withImage :: (Integral a, Integral b) => Image -> (Ptr Word8 -> (a,a) -> b -> IO c) -> IO c
withImage (Image imgptr (x,y) comp) f = withImgPtr imgptr g where
  g p _ = f p (fromIntegral x , fromIntegral y) (fromIntegral comp)

-- |Access the raw data as a strict 'ByteString'.
rawImage :: Image -> ByteString
rawImage (Image bs _ _) = bs
  
{-# SPECIALIZE resolution :: Image -> (Int,Int) #-}
-- |Returns the spatial resolution of an image.
resolution :: Integral a => Image -> (a,a)
resolution (Image _ (x,y) _) = (fromIntegral x , fromIntegral y)

{-# SPECIALIZE components :: Image -> Int #-}
-- |Returns the number of (8-bit) components per pixel.
components :: Integral a => Image -> a
components (Image _ _ c) = fromIntegral c

foreign import ccall safe "stb_image.h stbi_load_from_memory" 
  stbi_load_from_memory :: Ptr Word8 -> CInt -> Ptr CInt -> Ptr CInt -> Ptr CInt -> CInt -> IO (Ptr Word8)

foreign import ccall safe "stb_image.h &stbi_image_free" 
  stbi_image_free :: FunPtr (Ptr a -> IO ())

foreign import ccall safe "stb_image.h stbi_failure_reason"
  stbi_failure_reason :: IO (Ptr CChar)

-- |Decodes an image from a compressed format stored in a strict 'ByteString'.
-- Supported formats (see @stb_image.c@ for details!): 
--
--   * JPEG baseline (no JPEG progressive, no oddball channel decimations)
--
--   * PNG 8-bit only (8 bit per component, that is)
--
--   * BMP non-1bpp, non-RLE
--
--   * TGA (not sure what subset, if a subset)
--
--   * PSD (composite view only, no extra channels)
--
-- If the operation fails, returns an error message.
decodeImage :: ByteString -> IO (Either String Image) 
decodeImage = decodeImage' 0  

-- |Decodes an image, with the number of components per pixel forced by the user.
decodeImage' :: Int -> ByteString -> IO (Either String Image)
decodeImage' forcecomp bs = let (fptr,ofs,len) = toForeignPtr bs in withForeignPtr fptr $ \q -> do
  let ptr = plusPtr q ofs
  alloca $ \pxres -> alloca $ \pyres -> alloca $ \pcomp -> do 
    r <- stbi_load_from_memory ptr (fromIntegral len) pxres pyres pcomp (fromIntegral forcecomp)
    if r == nullPtr
      then do
        e <- stbi_failure_reason
        msg <- peekCString e
        return $ Left msg
      else do
        fr <- newForeignPtr stbi_image_free r 
        xres <- liftM fromIntegral $ peek pxres
        yres <- liftM fromIntegral $ peek pyres
        comp <- liftM fromIntegral $ peek pcomp
        let imgptr = fromForeignPtr fr 0 (xres*yres*comp)
        return $ Right $ Image imgptr (xres,yres) comp

ioHandler :: Exception -> IO (Either String a)
ioHandler (IOException ioerror) = return $ Left $ ioeGetErrorString ioerror
ioHandler _ = return $ Left "Unknown error"

-- |Loads an image from a file. Catches IO exceptions and converts them to an error message.  
loadImage :: FilePath -> IO (Either String Image)
loadImage path = handle ioHandler $ do
  h <- openBinaryFile path ReadMode 
  b <- B.hGetContents h
  hClose h
  decodeImage b