{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module      : Data.Massiv.Array.IO.Image.JuicyPixels.Base
-- Copyright   : (c) Alexey Kuleshevich 2019-2020
-- License     : BSD3
-- Maintainer  : Alexey Kuleshevich <lehins@yandex.ru>
-- Stability   : experimental
-- Portability : non-portable
--
module Data.Massiv.Array.IO.Image.JuicyPixels.Base
  ( showJP
  , convertWith
  , convertWithMetadata
  , convertAutoWith
  , convertAutoWithMetadata
  , convertSequenceWith
  , convertAutoSequenceWith
  , toJPImageY8
  , toJPImageY16
  , toJPImageY32
  , toJPImageYA8
  , toJPImageYA16
  , toJPImageYF
  , toJPImageRGB8
  , toJPImageRGB16
  , toJPImageRGBA8
  , toJPImageRGBA16
  , toJPImageRGBF
  , toJPImageYCbCr8
  , toJPImageCMYK8
  , toJPImageCMYK16
  , maybeJPImageY8
  , maybeJPImageY16
  , maybeJPImageY32
  , maybeJPImageYA8
  , maybeJPImageYA16
  , maybeJPImageYF
  , maybeJPImageRGB8
  , maybeJPImageRGB16
  , maybeJPImageRGBA8
  , maybeJPImageRGBA16
  , maybeJPImageRGBF
  , maybeJPImageYCbCr8
  , maybeJPImageCMYK8
  , maybeJPImageCMYK16
  , fromDynamicImage
  , fromDynamicImageM
  , fromDynamicImageAuto
  -- * Conversion to sRGB
  , toYCbCr8
  , toCMYK8
  , toCMYK16
  , toSRGB8
  , toSRGB16
  , toSRGBA8
  , toSRGBA16
  ) where

import qualified Codec.Picture as JP
import Control.Exception (assert)
import Control.Monad (msum, unless)
import Data.Massiv.Array as A
import Data.Massiv.Array.IO.Base
import Data.Typeable
import qualified Data.Vector.Storable as V
import Foreign.Storable (Storable(sizeOf))
import qualified Graphics.Pixel as CM
import Graphics.Pixel.ColorSpace
import Prelude as P

--------------------------------------------------------------------------------
-- Common JuciyPixels encoding/decoding functions ------------------------------
--------------------------------------------------------------------------------

convertWith ::
     (MonadThrow m, ColorModel cs e, FileFormat f)
  => f
  -> Either String JP.DynamicImage
  -> m (Image S cs e)
convertWith f = either (throwM . DecodeError) (fromMaybeDecodeM f showJP fromDynamicImageM)


convertWithMetadata ::
     (MonadThrow m, ColorModel cs e, FileFormat f)
  => f
  -> Either String (JP.DynamicImage, Metadata f)
  -> m (Image S cs e, Metadata f)
convertWithMetadata f decoded =
  case decoded of
    Left err -> throwM $ DecodeError err
    Right (jp, meta) -> do
      i <- fromMaybeDecodeM f showJP fromDynamicImageM jp
      pure (i, meta)

convertAutoWithMetadata ::
     (MonadThrow m, Mutable r Ix2 (Pixel cs e), ColorSpace cs i e)
  => Auto f
  -> Either String (JP.DynamicImage, Metadata f)
  -> m (Image r cs e, Metadata f)
convertAutoWithMetadata _ decoded =
  case decoded of
    Left err -> throwM $ DecodeError err
    Right (jp, meta) -> do
      i <- fromDynamicImageAuto jp
      pure (i, meta)

convertAutoWith ::
     (MonadThrow m, Mutable r Ix2 (Pixel cs e), ColorSpace cs i e)
  => Auto f
  -> Either String JP.DynamicImage
  -> m (Image r cs e)
convertAutoWith _ = either (throwM . DecodeError) fromDynamicImageAuto


convertSequenceWith ::
     (MonadThrow m, ColorModel cs e, FileFormat (Sequence f))
  => Sequence f
  -> Either String [JP.DynamicImage]
  -> m [Image S cs e]
convertSequenceWith f ejpImgs = do
  jpImgs <- decodeError ejpImgs
  P.traverse (fromMaybeDecodeM f showJP fromDynamicImageM) jpImgs


convertAutoSequenceWith ::
     (MonadThrow m, Mutable r Ix2 (Pixel cs e), ColorSpace cs i e)
  => Auto (Sequence f)
  -> Either String [JP.DynamicImage]
  -> m [Image r cs e]
convertAutoSequenceWith _ ejpImgs = do
  jpImgs <- decodeError ejpImgs
  P.traverse fromDynamicImageAuto jpImgs

fromJPImageM ::
     (Storable (Color cs e), Storable e, JP.Pixel px, MonadThrow m)
  => JP.Image px
  -> Maybe (Pixel cs e :~: Pixel cs' e')
  -> m (Maybe (Image S cs e))
fromJPImageM jimg = P.mapM $ \Refl -> fromJPImageUnsafeM jimg

sequenceMaybe :: Monad m => [m (Maybe a)] -> m (Maybe a)
sequenceMaybe [] = pure Nothing
sequenceMaybe (x:xs) =
  x >>= \case
    Nothing -> sequenceMaybe xs
    ma -> pure ma

fromDynamicImageM ::
     forall cs e m. (ColorModel cs e, MonadThrow m)
  => JP.DynamicImage
  -> m (Maybe (Image S cs e))
fromDynamicImageM jpDynImg =
  case jpDynImg of
    JP.ImageY8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel Y' Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Y D65) Word8))
        ]
    JP.ImageY16 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel Y' Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Y D65) Word16))
        ]
    JP.ImageY32 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word32))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel Y' Word32))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Y D65) Word32))
        ]
    JP.ImageYF jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Float))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel Y' Float))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Y D65) Float))
        ]
    JP.ImageYA8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.Y) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha Y') Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (Y D65)) Word8))
        ]
    JP.ImageYA16 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.Y) Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha Y') Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (Y D65)) Word16))
        ]
    JP.ImageRGB8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (SRGB 'NonLinear) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (AdobeRGB 'NonLinear) Word8))
        ]
    JP.ImageRGB16 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (SRGB 'NonLinear) Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (AdobeRGB 'NonLinear) Word16))
        ]
    JP.ImageRGBF jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Float))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (SRGB 'NonLinear) Float))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (AdobeRGB 'NonLinear) Float))
        ]
    JP.ImageRGBA8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.RGB) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (SRGB 'NonLinear)) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (AdobeRGB 'NonLinear)) Word8))
        ]
    JP.ImageRGBA16 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.RGB) Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (SRGB 'NonLinear)) Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (Alpha (AdobeRGB 'NonLinear)) Word16))
        ]
    JP.ImageYCbCr8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.YCbCr Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (YCbCr (SRGB 'NonLinear)) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (YCbCr (AdobeRGB 'NonLinear)) Word8))
        ]
    JP.ImageCMYK8 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.CMYK Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (CMYK (SRGB 'NonLinear)) Word8))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (CMYK (AdobeRGB 'NonLinear)) Word8))
        ]
    JP.ImageCMYK16 jimg ->
      sequenceMaybe
        [ fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel CM.CMYK Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (CMYK (SRGB 'NonLinear)) Word16))
        , fromJPImageM jimg (eqT :: Maybe (Pixel cs e :~: Pixel (CMYK (AdobeRGB 'NonLinear)) Word16))
        ]

fromDynamicImage ::
     forall cs e. ColorModel cs e
  => JP.DynamicImage
  -> Maybe (Image S cs e)
fromDynamicImage jpDynImg =
  case jpDynImg of
    JP.ImageY8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word8)
      fromJPImageUnsafeM jimg
    JP.ImageY16 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word16)
      fromJPImageUnsafeM jimg
    JP.ImageY32 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Word32)
      fromJPImageUnsafeM jimg
    JP.ImageYF jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.Y Float)
      fromJPImageUnsafeM jimg
    JP.ImageYA8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.Y) Word8)
      fromJPImageUnsafeM jimg
    JP.ImageYA16 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.Y) Word16)
      fromJPImageUnsafeM jimg
    JP.ImageRGB8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Word8)
      fromJPImageUnsafeM jimg
    JP.ImageRGB16 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Word16)
      fromJPImageUnsafeM jimg
    JP.ImageRGBF jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.RGB Float)
      fromJPImageUnsafeM jimg
    JP.ImageRGBA8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.RGB) Word8)
      fromJPImageUnsafeM jimg
    JP.ImageRGBA16 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel (Alpha CM.RGB) Word16)
      fromJPImageUnsafeM jimg
    JP.ImageYCbCr8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.YCbCr Word8)
      fromJPImageUnsafeM jimg
    JP.ImageCMYK8 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.CMYK Word8)
      fromJPImageUnsafeM jimg
    JP.ImageCMYK16 jimg -> do
      Refl <- eqT :: Maybe (Pixel cs e :~: Pixel CM.CMYK Word16)
      fromJPImageUnsafeM jimg
{-# DEPRECATED fromDynamicImage "In favor of `fromDynamicImageM`" #-}

fromDynamicImageAuto ::
     forall r cs i e m. (Mutable r Ix2 (Pixel cs e), ColorSpace cs i e, MonadThrow m)
  => JP.DynamicImage
  -> m (Image r cs e)
fromDynamicImageAuto jpDynImg =
  case jpDynImg of
    JP.ImageY8 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Y D65) Word8))
    JP.ImageY16 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Y D65) Word16))
    JP.ImageY32 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Y D65) Word32))
    JP.ImageYF jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Y D65) Float))
    JP.ImageYA8 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Alpha (Y D65)) Word8))
    JP.ImageYA16 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (Alpha (Y D65)) Word16))
    JP.ImageRGB8 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (SRGB 'NonLinear) Word8))
    JP.ImageRGB16 jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (SRGB 'NonLinear) Word16))
    JP.ImageRGBF jimg ->
      compute . convertImage <$> (fromJPImageUnsafeM jimg :: m (Image S (SRGB 'NonLinear) Float))
    JP.ImageRGBA8 jimg ->
      compute . convertImage <$>
      (fromJPImageUnsafeM jimg :: m (Image S (Alpha (SRGB 'NonLinear)) Word8))
    JP.ImageRGBA16 jimg ->
      compute . convertImage <$>
      (fromJPImageUnsafeM jimg :: m (Image S (Alpha (SRGB 'NonLinear)) Word16))
    JP.ImageYCbCr8 jimg ->
      compute . convertImage <$>
      (fromJPImageUnsafeM jimg :: m (Image S (YCbCr (SRGB 'NonLinear)) Word8))
    JP.ImageCMYK8 jimg ->
      compute . convertImage <$>
      (fromJPImageUnsafeM jimg :: m (Image S (CMYK (SRGB 'NonLinear)) Word8))
    JP.ImageCMYK16 jimg ->
      compute . convertImage <$>
      (fromJPImageUnsafeM jimg :: m (Image S (CMYK (SRGB 'NonLinear)) Word16))



showJP :: JP.DynamicImage -> String
showJP (JP.ImageY8     _) = "Image S Y Word8"
showJP (JP.ImageY16    _) = "Image S Y Word16"
showJP (JP.ImageY32    _) = "Image S Y Word32"
showJP (JP.ImageYF     _) = "Image S Y Float"
showJP (JP.ImageYA8    _) = "Image S YA Word8"
showJP (JP.ImageYA16   _) = "Image S YA Word16"
showJP (JP.ImageRGB8   _) = "Image S RGB Word8"
showJP (JP.ImageRGB16  _) = "Image S RGB Word16"
showJP (JP.ImageRGBF   _) = "Image S RGB Float"
showJP (JP.ImageRGBA8  _) = "Image S RGBA Word8"
showJP (JP.ImageRGBA16 _) = "Image S RGBA Word16"
showJP (JP.ImageYCbCr8 _) = "Image S YCbCr Word8"
showJP (JP.ImageCMYK8  _) = "Image S CMYK Word8"
showJP (JP.ImageCMYK16 _) = "Image S CMYK Word16"


-- Encoding

toJPImageUnsafe
  :: forall r cs a . (JP.Pixel a, Source r Ix2 (Pixel cs (JP.PixelBaseComponent a)),
                      ColorModel cs (JP.PixelBaseComponent a))
  => Image r cs (JP.PixelBaseComponent a)
  -> JP.Image a
toJPImageUnsafe img = JP.Image n m $ V.unsafeCast $ toStorableVector arrS
  where
    !arrS = computeSource img :: Image S cs (JP.PixelBaseComponent a)
    Sz (m :. n) = size img
{-# INLINE toJPImageUnsafe #-}

toJPImageY8 :: Source r Ix2 (Pixel CM.Y Word8) => Image r CM.Y Word8 -> JP.Image JP.Pixel8
toJPImageY8 = toJPImageUnsafe
{-# INLINE toJPImageY8 #-}

maybeJPImageY8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word8))
  => Image S cs Word8
  -> Maybe (JP.Image JP.Pixel8)
maybeJPImageY8 img =
  msum
    [ (\Refl -> toJPImageY8 img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageY8 $ demoteLumaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageY8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageY8 #-}

toJPImageY16 :: Source r Ix2 (Pixel CM.Y Word16) => Image r CM.Y Word16 -> JP.Image JP.Pixel16
toJPImageY16 = toJPImageUnsafe
{-# INLINE toJPImageY16 #-}



maybeJPImageY16 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word16))
  => Image S cs Word16
  -> Maybe (JP.Image JP.Pixel16)
maybeJPImageY16 img =
  msum
    [ (\Refl -> toJPImageY16 img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageY16 $ demoteLumaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageY16 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageY16 #-}

toJPImageY32 :: Source r Ix2 (Pixel CM.Y Word32) => Image r CM.Y Word32 -> JP.Image JP.Pixel32
toJPImageY32 = toJPImageUnsafe
{-# INLINE toJPImageY32 #-}


maybeJPImageY32 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word32))
  => Image S cs Word32
  -> Maybe (JP.Image JP.Pixel32)
maybeJPImageY32 img =
  msum
    [ (\Refl -> toJPImageY32 img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageY32 $ demoteLumaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageY32 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageY32 #-}


toJPImageYF :: Source r Ix2 (Pixel CM.Y Float) => Image r CM.Y Float -> JP.Image JP.PixelF
toJPImageYF = toJPImageUnsafe
{-# INLINE toJPImageYF #-}


maybeJPImageYF ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Float))
  => Image S cs Float
  -> Maybe (JP.Image JP.PixelF)
maybeJPImageYF img =
  msum
    [ (\Refl -> toJPImageYF img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageYF $ demoteLumaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageYF $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageYF #-}


toJPImageYA8 ::
     Source r Ix2 (Pixel (Alpha CM.Y) Word8) => Image r (Alpha CM.Y) Word8 -> JP.Image JP.PixelYA8
toJPImageYA8 = toJPImageUnsafe
{-# INLINE toJPImageYA8 #-}

maybeJPImageYA8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel (Alpha cs) Word8))
  => Image S (Alpha cs) Word8
  -> Maybe (JP.Image JP.PixelYA8)
maybeJPImageYA8 img =
  msum
    [ (\Refl -> toJPImageYA8 img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageYA8 $ demoteLumaAlphaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageYA8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageYA8 #-}


toJPImageYA16 ::
     Source r Ix2 (Pixel (Alpha CM.Y) Word16)
  => Image r (Alpha CM.Y) Word16
  -> JP.Image JP.PixelYA16
toJPImageYA16 = toJPImageUnsafe
{-# INLINE toJPImageYA16 #-}


maybeJPImageYA16 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel (Alpha cs) Word16))
  => Image S (Alpha cs) Word16
  -> Maybe (JP.Image JP.PixelYA16)
maybeJPImageYA16 img =
  msum
    [ (\Refl -> toJPImageYA16 img) <$> (eqT :: Maybe (cs :~: CM.Y))
    , (\Refl -> toJPImageYA16 $ demoteLumaAlphaImage img) <$> (eqT :: Maybe (cs :~: Y'))
    , (\Refl -> toJPImageYA16 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: Y D65))
    ]
{-# INLINE maybeJPImageYA16 #-}



toJPImageRGB8 :: Source r Ix2 (Pixel CM.RGB Word8) => Image r CM.RGB Word8 -> JP.Image JP.PixelRGB8
toJPImageRGB8 = toJPImageUnsafe
{-# INLINE toJPImageRGB8 #-}


maybeJPImageRGB8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word8))
  => Image S cs Word8
  -> Maybe (JP.Image JP.PixelRGB8)
maybeJPImageRGB8 img =
  msum
    [ (\Refl -> toJPImageRGB8 img) <$> (eqT :: Maybe (cs :~: CM.RGB))
    , (\Refl -> toJPImageRGB8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: SRGB 'NonLinear))
    , (\Refl -> toJPImageRGB8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: AdobeRGB 'NonLinear))
    ]
{-# INLINE maybeJPImageRGB8 #-}


toJPImageRGB16 ::
     Source r Ix2 (Pixel CM.RGB Word16) => Image r CM.RGB Word16 -> JP.Image JP.PixelRGB16
toJPImageRGB16 = toJPImageUnsafe
{-# INLINE toJPImageRGB16 #-}

maybeJPImageRGB16 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word16))
  => Image S cs Word16
  -> Maybe (JP.Image JP.PixelRGB16)
maybeJPImageRGB16 img =
  msum
    [ (\Refl -> toJPImageRGB16 img) <$> (eqT :: Maybe (cs :~: CM.RGB))
    , (\Refl -> toJPImageRGB16 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: SRGB 'NonLinear))
    , (\Refl -> toJPImageRGB16 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: AdobeRGB 'NonLinear))
    ]
{-# INLINE maybeJPImageRGB16 #-}


toJPImageRGBF :: Source r Ix2 (Pixel CM.RGB Float) => Image r CM.RGB Float -> JP.Image JP.PixelRGBF
toJPImageRGBF = toJPImageUnsafe
{-# INLINE toJPImageRGBF #-}

maybeJPImageRGBF ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Float))
  => Image S cs Float
  -> Maybe (JP.Image JP.PixelRGBF)
maybeJPImageRGBF img =
  msum
    [ (\Refl -> toJPImageRGBF img) <$> (eqT :: Maybe (cs :~: CM.RGB))
    , (\Refl -> toJPImageRGBF $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: SRGB 'NonLinear))
    , (\Refl -> toJPImageRGBF $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: AdobeRGB 'NonLinear))
    ]
{-# INLINE maybeJPImageRGBF #-}


toJPImageRGBA8 ::
     Source r Ix2 (Pixel (Alpha CM.RGB) Word8)
  => Image r (Alpha CM.RGB) Word8
  -> JP.Image JP.PixelRGBA8
toJPImageRGBA8 = toJPImageUnsafe
{-# INLINE toJPImageRGBA8 #-}

maybeJPImageRGBA8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel (Alpha cs) Word8))
  => Image S (Alpha cs) Word8
  -> Maybe (JP.Image JP.PixelRGBA8)
maybeJPImageRGBA8 img =
  msum
    [ (\Refl -> toJPImageRGBA8 img) <$> (eqT :: Maybe (cs :~: CM.RGB))
    , (\Refl -> toJPImageRGBA8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: SRGB 'NonLinear))
    , (\Refl -> toJPImageRGBA8 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: AdobeRGB 'NonLinear))
    ]
{-# INLINE maybeJPImageRGBA8 #-}


toJPImageRGBA16 ::
     Source r Ix2 (Pixel (Alpha CM.RGB) Word16)
  => Image r (Alpha CM.RGB) Word16
  -> JP.Image JP.PixelRGBA16
toJPImageRGBA16 = toJPImageUnsafe
{-# INLINE toJPImageRGBA16 #-}

maybeJPImageRGBA16 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel (Alpha cs) Word16))
  => Image S (Alpha cs) Word16
  -> Maybe (JP.Image JP.PixelRGBA16)
maybeJPImageRGBA16 img =
  msum
    [ (\Refl -> toJPImageRGBA16 img) <$> (eqT :: Maybe (cs :~: CM.RGB))
    , (\Refl -> toJPImageRGBA16 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: SRGB 'NonLinear))
    , (\Refl -> toJPImageRGBA16 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: AdobeRGB 'NonLinear))
    ]
{-# INLINE maybeJPImageRGBA16 #-}

toJPImageYCbCr8 ::
     Source r Ix2 (Pixel CM.YCbCr Word8) => Image r CM.YCbCr Word8 -> JP.Image JP.PixelYCbCr8
toJPImageYCbCr8 = toJPImageUnsafe
{-# INLINE toJPImageYCbCr8 #-}


maybeJPImageYCbCr8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word8))
  => Image S cs Word8
  -> Maybe (JP.Image JP.PixelYCbCr8)
maybeJPImageYCbCr8 img =
  msum
    [ (\Refl -> toJPImageYCbCr8 img) <$> (eqT :: Maybe (cs :~: CM.YCbCr))
    , (\Refl -> toJPImageYCbCr8 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: YCbCr (SRGB 'NonLinear)))
    --, (\Refl -> toJPImageYCbCr8 $ toImageBaseModel img) <$> (eqT :: Maybe (cs :~: YCbCr (AdobeRGB 'NonLinear)))
    ]
{-# INLINE maybeJPImageYCbCr8 #-}


toJPImageCMYK8 ::
     Source r Ix2 (Pixel CM.CMYK Word8) => Image r CM.CMYK Word8 -> JP.Image JP.PixelCMYK8
toJPImageCMYK8 = toJPImageUnsafe
{-# INLINE toJPImageCMYK8 #-}


maybeJPImageCMYK8 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word8))
  => Image S cs Word8
  -> Maybe (JP.Image JP.PixelCMYK8)
maybeJPImageCMYK8 img =
  msum
    [ (\Refl -> toJPImageCMYK8 img) <$> (eqT :: Maybe (cs :~: CM.CMYK))
    , (\Refl -> toJPImageCMYK8 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: CMYK (SRGB 'NonLinear)))
    , (\Refl -> toJPImageCMYK8 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: CMYK (AdobeRGB 'NonLinear)))
    ]
{-# INLINE maybeJPImageCMYK8 #-}


toJPImageCMYK16 ::
     Source r Ix2 (Pixel CM.CMYK Word16) => Image r CM.CMYK Word16 -> JP.Image JP.PixelCMYK16
toJPImageCMYK16 = toJPImageUnsafe
{-# INLINE toJPImageCMYK16 #-}


maybeJPImageCMYK16 ::
     forall cs. (Typeable cs, Source S Ix2 (Pixel cs Word16))
  => Image S cs Word16
  -> Maybe (JP.Image JP.PixelCMYK16)
maybeJPImageCMYK16 img =
  msum
    [ (\Refl -> toJPImageCMYK16 img) <$> (eqT :: Maybe (cs :~: CM.CMYK))
    , (\Refl -> toJPImageCMYK16 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: CMYK (SRGB 'NonLinear)))
    , (\Refl -> toJPImageCMYK16 $ toImageBaseModel img) <$>
      (eqT :: Maybe (cs :~: CMYK (AdobeRGB 'NonLinear)))
    ]
{-# INLINE maybeJPImageCMYK16 #-}


-- General decoding and helper functions

fromJPImageUnsafeM ::
     forall jpx cs e m. (Storable (Pixel cs e), Storable e, JP.Pixel jpx, MonadThrow m)
  => JP.Image jpx
  -> m (Image S cs e)
fromJPImageUnsafeM (JP.Image n m !v) = do
  let numberOfComponentsFromSize = sizeOf (undefined :: Pixel cs e) `div` sizeOf (undefined :: e)
      numComponentsPerPixel = JP.componentCount (undefined :: jpx)
  unless (numComponentsPerPixel == numberOfComponentsFromSize) $
    throwM $ ConvertError $
    concat
      [ "Mismatched sizes beteen JuicyPixels: "
      , show numComponentsPerPixel
      , " and massiv: "
      , show numberOfComponentsFromSize
      ]
  assert (n * m * numComponentsPerPixel == V.length v) $
    unsafeFromStorableVectorM (Sz (m :. n)) v


-- Conversion to sRGB color space based color models

toYCbCr8 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel CM.YCbCr Word8
toYCbCr8 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (YCbCr (SRGB 'NonLinear)) Word8)

toCMYK8 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel CM.CMYK Word8
toCMYK8 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (CMYK (SRGB 'NonLinear)) Word8)

toCMYK16 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel CM.CMYK Word16
toCMYK16 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (CMYK (SRGB 'NonLinear)) Word16)

toSRGB8 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel CM.RGB Word8
toSRGB8 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (SRGB 'NonLinear) Word8)

toSRGB16 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel CM.RGB Word16
toSRGB16 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (SRGB 'NonLinear) Word16)

toSRGBA8 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel (Alpha CM.RGB) Word8
toSRGBA8 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (Alpha (SRGB 'NonLinear)) Word8)

toSRGBA16 :: forall cs i e . ColorSpace cs i e => Pixel cs e -> Pixel (Alpha CM.RGB) Word16
toSRGBA16 = toPixelBaseModel . (convertPixel :: Pixel cs e -> Pixel (Alpha (SRGB 'NonLinear)) Word16)