{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-}
module Codec.Picture (
                     
                       readImage
                     , readImageWithMetadata
                     , decodeImage
                     , decodeImageWithMetadata
                     , decodeImageWithPaletteAndMetadata
                     , pixelMap
                     , dynamicMap
                     , dynamicPixelMap
                     , generateImage
                     , generateFoldImage
                     , withImage
                     , palettedToTrueColor
                      
                     , convertRGB8
                     , convertRGB16
                     , convertRGBA8
                     
                     , Traversal
                     , imagePixels
                     , imageIPixels
                     
                     , saveBmpImage
                     , saveJpgImage
                     , saveGifImage
                     , savePngImage
                     , saveTiffImage
                     , saveRadianceImage
                     
                     
                     , BmpEncodable
                     , writeBitmap
                     , encodeBitmap
                     , readBitmap
                     , decodeBitmap
                     , encodeDynamicBitmap
                     , writeDynamicBitmap
                     
                     , readGif
                     , readGifImages
                     , decodeGif
                     , decodeGifImages
                     , encodeGifImage
                     , writeGifImage
                     , encodeGifImageWithPalette
                     , writeGifImageWithPalette
                     , encodeColorReducedGifImage
                     , writeColorReducedGifImage
                     , encodeGifImages
                     , writeGifImages
                     
                     , GifDelay
                     , GifLooping( .. )
                     , encodeGifAnimation
                     , writeGifAnimation
                     
                     , readJpeg
                     , decodeJpeg
                     , encodeJpeg
                     , encodeJpegAtQuality
                     
                     , PngSavable( .. )
                     , readPng
                     , decodePng
                     , writePng
                     , encodePalettedPng
                     , encodeDynamicPng
                     , writeDynamicPng
                     
                     , readTGA
                     , decodeTga
                     , TgaSaveable
                     , encodeTga
                     , writeTga
                     
                     , readTiff
                     , TiffSaveable
                     , decodeTiff
                     , encodeTiff
                     , writeTiff
                     
                     , readHDR
                     , decodeHDR
                     , encodeHDR
                     , writeHDR
                     
                     , PaletteCreationMethod(..)
                     , PaletteOptions(..)
                     , palettize
                     
                     
                     , Image( .. )
                     , DynamicImage( .. )
                     , Palette
                     
                     , Pixel( .. )
                     
                     , Pixel8
                     , Pixel16
                     , Pixel32
                     , PixelF
                     , PixelYA8( .. )
                     , PixelYA16( .. )
                     , PixelRGB8( .. )
                     , PixelRGB16( .. )
                     , PixelRGBF( .. )
                     , PixelRGBA8( .. )
                     , PixelRGBA16( .. )
                     , PixelYCbCr8( .. )
                     , PixelCMYK8( .. )
                     , PixelCMYK16( .. )
                     
                     , imageFromUnsafePtr
                     ) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<$>) )
#endif
import Control.Arrow( first )
import Data.Bits( unsafeShiftR )
import Control.DeepSeq( NFData, deepseq )
import qualified Control.Exception as Exc ( catch, IOException )
import Codec.Picture.Metadata( Metadatas )
import Codec.Picture.Bitmap( BmpEncodable
                           , decodeBitmap
                           , decodeBitmapWithPaletteAndMetadata
                           , writeBitmap, encodeBitmap
                           , encodeDynamicBitmap, writeDynamicBitmap )
import Codec.Picture.Jpg( decodeJpeg
                        , decodeJpegWithMetadata
                        , encodeJpeg
                        , encodeJpegAtQuality )
import Codec.Picture.Png( PngSavable( .. )
                        , decodePng
                        , decodePngWithPaletteAndMetadata
                        , writePng
                        , encodeDynamicPng
                        , encodePalettedPng
                        , writeDynamicPng
                        )
import Codec.Picture.Gif( GifDelay
                        , GifLooping( .. )
                        , decodeGif
                        , decodeGifWithPaletteAndMetadata
                        , decodeGifImages
                        , encodeGifImage
                        , encodeGifImageWithPalette
                        , encodeGifImages
                        , writeGifImage
                        , writeGifImageWithPalette
                        , writeGifImages
                        )
import Codec.Picture.HDR( decodeHDR
                        , decodeHDRWithMetadata
                        , encodeHDR
                        , writeHDR
                        )
import Codec.Picture.Tiff( decodeTiff
                         , decodeTiffWithPaletteAndMetadata
                         , TiffSaveable
                         , encodeTiff
                         , writeTiff )
import Codec.Picture.Tga( TgaSaveable
                        , decodeTga
                        , decodeTgaWithPaletteAndMetadata
                        , encodeTga
                        , writeTga
                        )
import Codec.Picture.Saving
import Codec.Picture.Types
import Codec.Picture.ColorQuant
import Codec.Picture.VectorByteConversion( imageFromUnsafePtr )
#ifdef WITH_MMAP_BYTESTRING
import System.IO.MMap ( mmapFileByteString )
#endif
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Vector.Storable as VS
eitherLoad :: c -> [(String, c -> Either String b)] -> Either String b
eitherLoad v = inner ""
    where inner errAcc [] = Left $ "Cannot load file\n" ++ errAcc
          inner errAcc ((hdr, f) : rest) = case f v of
                Left  err  -> inner (errAcc ++ hdr ++ " " ++ err ++ "\n") rest
                Right rez  -> Right rez
encodeColorReducedGifImage :: Image PixelRGB8 -> Either String L.ByteString
encodeColorReducedGifImage img = encodeGifImageWithPalette indexed pal
  where (indexed, pal) = palettize defaultPaletteOptions img
writeColorReducedGifImage :: FilePath -> Image PixelRGB8 -> Either String (IO ())
writeColorReducedGifImage path img =
    L.writeFile path <$> encodeColorReducedGifImage img
encodeGifAnimation :: GifDelay -> GifLooping
                   -> [Image PixelRGB8] -> Either String L.ByteString
encodeGifAnimation delay looping lst =
    encodeGifImages looping
        [(pal, delay, img)
                | (img, pal) <- palettize defaultPaletteOptions <$> lst]
writeGifAnimation :: FilePath -> GifDelay -> GifLooping
                  -> [Image PixelRGB8] -> Either String (IO ())
writeGifAnimation path delay looping img =
    L.writeFile path <$> encodeGifAnimation delay looping img
withImageDecoder :: (NFData a)
                 => (B.ByteString -> Either String a) -> FilePath
                 -> IO (Either String a)
withImageDecoder decoder path = Exc.catch doit
                    (\e -> return . Left $ show (e :: Exc.IOException))
    where doit = force . decoder <$> get
#ifdef WITH_MMAP_BYTESTRING
          get = mmapFileByteString path Nothing
#else
          get = B.readFile path
#endif
          
          
          force x = x `deepseq` x
readImage :: FilePath -> IO (Either String DynamicImage)
readImage = withImageDecoder decodeImage
readImageWithMetadata :: FilePath -> IO (Either String (DynamicImage, Metadatas))
readImageWithMetadata = withImageDecoder decodeImageWithMetadata
decodeImage :: B.ByteString -> Either String DynamicImage
decodeImage = fmap fst . decodeImageWithMetadata
class Decimable px1 px2 where
   decimateBitDepth :: Image px1 -> Image px2
decimateWord16 :: ( Pixel px1, Pixel px2
                  , PixelBaseComponent px1 ~ Pixel16
                  , PixelBaseComponent px2 ~ Pixel8
                  ) => Image px1 -> Image px2
decimateWord16 (Image w h da) =
  Image w h $ VS.map (\v -> fromIntegral $ v `unsafeShiftR` 8) da
decimateWord3216 :: ( Pixel px1, Pixel px2
                  , PixelBaseComponent px1 ~ Pixel32
                  , PixelBaseComponent px2 ~ Pixel16
                  ) => Image px1 -> Image px2
decimateWord3216 (Image w h da) =
  Image w h $ VS.map (\v -> fromIntegral $ v `unsafeShiftR` 16) da
decimateWord32 :: ( Pixel px1, Pixel px2
                  , PixelBaseComponent px1 ~ Pixel32
                  , PixelBaseComponent px2 ~ Pixel8
                  ) => Image px1 -> Image px2
decimateWord32 (Image w h da) =
  Image w h $ VS.map (\v -> fromIntegral $ v `unsafeShiftR` 24) da
decimateFloat :: ( Pixel px1, Pixel px2
                 , PixelBaseComponent px1 ~ PixelF
                 , PixelBaseComponent px2 ~ Pixel8
                 ) => Image px1 -> Image px2
decimateFloat (Image w h da) =
  Image w h $ VS.map (floor . (255*) . max 0 . min 1) da
decimateFloat16 :: ( Pixel px1, Pixel px2
                 , PixelBaseComponent px1 ~ PixelF
                 , PixelBaseComponent px2 ~ Pixel16
                 ) => Image px1 -> Image px2
decimateFloat16 (Image w h da) =
  Image w h $ VS.map (floor . (65535*) . max 0 . min 1) da
instance Decimable Pixel16 Pixel8 where
   decimateBitDepth = decimateWord16
instance Decimable Pixel32 Pixel16 where
   decimateBitDepth = decimateWord3216
instance Decimable Pixel32 Pixel8 where
   decimateBitDepth = decimateWord32
instance Decimable PixelYA16 PixelYA8 where
   decimateBitDepth = decimateWord16
instance Decimable PixelRGB16 PixelRGB8 where
   decimateBitDepth = decimateWord16
instance Decimable PixelRGBA16 PixelRGBA8 where
   decimateBitDepth = decimateWord16
instance Decimable PixelCMYK16 PixelCMYK8 where
   decimateBitDepth = decimateWord16
instance Decimable PixelF Pixel8 where
   decimateBitDepth = decimateFloat
instance Decimable PixelF Pixel16 where
   decimateBitDepth = decimateFloat16
instance Decimable PixelRGBF PixelRGB8 where
   decimateBitDepth = decimateFloat
instance Decimable PixelRGBF PixelRGB16 where
   decimateBitDepth = decimateFloat16
convertRGBA8 :: DynamicImage -> Image PixelRGBA8
convertRGBA8 dynImage = case dynImage of
  ImageY8     img -> promoteImage img
  ImageY16    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageY32    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageYF     img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageYA8    img -> promoteImage img
  ImageYA16   img -> promoteImage (decimateBitDepth img :: Image PixelYA8)
  ImageRGB8   img -> promoteImage img
  ImageRGB16  img -> promoteImage (decimateBitDepth img :: Image PixelRGB8)
  ImageRGBF   img -> promoteImage (decimateBitDepth img :: Image PixelRGB8)
  ImageRGBA8  img -> promoteImage img
  ImageRGBA16 img -> decimateBitDepth img
  ImageYCbCr8 img -> promoteImage (convertImage img :: Image PixelRGB8)
  ImageCMYK8  img -> promoteImage (convertImage img :: Image PixelRGB8)
  ImageCMYK16 img ->
    promoteImage (convertImage (decimateBitDepth img :: Image PixelCMYK8) :: Image PixelRGB8)
convertRGB8 :: DynamicImage -> Image PixelRGB8
convertRGB8 dynImage = case dynImage of
  ImageY8     img -> promoteImage img
  ImageY16    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageY32    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageYF     img -> promoteImage (decimateBitDepth img :: Image Pixel8)
  ImageYA8    img -> promoteImage img
  ImageYA16   img -> promoteImage (decimateBitDepth img :: Image PixelYA8)
  ImageRGB8   img -> img
  ImageRGB16  img -> decimateBitDepth img
  ImageRGBF   img -> decimateBitDepth img :: Image PixelRGB8
  ImageRGBA8  img -> dropAlphaLayer img
  ImageRGBA16 img -> dropAlphaLayer (decimateBitDepth img :: Image PixelRGBA8)
  ImageYCbCr8 img -> convertImage img
  ImageCMYK8  img -> convertImage img
  ImageCMYK16 img -> convertImage (decimateBitDepth img :: Image PixelCMYK8)
convertRGB16 :: DynamicImage -> Image PixelRGB16
convertRGB16 dynImage = case dynImage of
  ImageY8     img -> promoteImage img
  ImageY16    img -> promoteImage img
  ImageY32    img -> promoteImage (decimateBitDepth img :: Image Pixel16)
  ImageYF     img -> promoteImage (decimateBitDepth img :: Image Pixel16)
  ImageYA8    img -> promoteImage img
  ImageYA16   img -> promoteImage img
  ImageRGB8   img -> promoteImage img
  ImageRGB16  img -> img
  ImageRGBF   img -> decimateBitDepth img :: Image PixelRGB16
  ImageRGBA8  img -> dropAlphaLayer (promoteImage img :: Image PixelRGBA16)
  ImageRGBA16 img -> dropAlphaLayer img
  ImageYCbCr8 img -> promoteImage (convertImage img :: Image PixelRGB8)
  ImageCMYK8  img -> promoteImage (convertImage img :: Image PixelRGB8)
  ImageCMYK16 img -> convertImage img
decodeImageWithPaletteAndMetadata :: B.ByteString -> Either String (PalettedImage, Metadatas)
decodeImageWithPaletteAndMetadata str = eitherLoad str
    [ ("Jpeg", fmap (first TrueColorImage) . decodeJpegWithMetadata)
    , ("PNG", decodePngWithPaletteAndMetadata)
    , ("Bitmap", decodeBitmapWithPaletteAndMetadata)
    , ("GIF", decodeGifWithPaletteAndMetadata)
    , ("HDR", fmap (first TrueColorImage) . decodeHDRWithMetadata)
    , ("Tiff", decodeTiffWithPaletteAndMetadata)
    , ("TGA", decodeTgaWithPaletteAndMetadata)
    ]
decodeImageWithMetadata :: B.ByteString -> Either String (DynamicImage, Metadatas)
decodeImageWithMetadata =
    fmap (first palettedToTrueColor) . decodeImageWithPaletteAndMetadata
readPng :: FilePath -> IO (Either String DynamicImage)
readPng = withImageDecoder decodePng
readGif :: FilePath -> IO (Either String DynamicImage)
readGif = withImageDecoder decodeGif
readTiff :: FilePath -> IO (Either String DynamicImage)
readTiff = withImageDecoder decodeTiff
readGifImages :: FilePath -> IO (Either String [DynamicImage])
readGifImages = withImageDecoder decodeGifImages
readJpeg :: FilePath -> IO (Either String DynamicImage)
readJpeg = withImageDecoder decodeJpeg
readBitmap :: FilePath -> IO (Either String DynamicImage)
readBitmap = withImageDecoder decodeBitmap
readHDR :: FilePath -> IO (Either String DynamicImage)
readHDR = withImageDecoder decodeHDR
readTGA :: FilePath -> IO (Either String DynamicImage)
readTGA = withImageDecoder decodeTga
saveJpgImage :: Int -> FilePath -> DynamicImage -> IO ()
saveJpgImage quality path img = L.writeFile path $ imageToJpg quality img
saveGifImage :: FilePath -> DynamicImage -> Either String (IO ())
saveGifImage path img = L.writeFile path <$> imageToGif img
saveTiffImage :: FilePath -> DynamicImage -> IO ()
saveTiffImage path img = L.writeFile path $ imageToTiff img
saveRadianceImage :: FilePath -> DynamicImage -> IO ()
saveRadianceImage path = L.writeFile path . imageToRadiance
savePngImage :: FilePath -> DynamicImage -> IO ()
savePngImage path img = L.writeFile path $ imageToPng img
saveBmpImage :: FilePath -> DynamicImage -> IO ()
saveBmpImage path img = L.writeFile path $ imageToBitmap img