{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module      : Data.Massiv.Array.IO
-- Copyright   : (c) Alexey Kuleshevich 2018-2020
-- License     : BSD3
-- Maintainer  : Alexey Kuleshevich <lehins@yandex.ru>
-- Stability   : experimental
-- Portability : non-portable
--
module Data.Massiv.Array.IO
  ( -- * Supported Image Formats
    module Graphics.Pixel.ColorSpace
  , Image
    -- $supported

    -- * Reading
  , readArray
  , readArrayWithMetadata
  , readImage
  , readImageAuto
  -- * Writing
  , writeArray
  , writeImage
  , writeImageAuto
  -- * Displaying
  , ExternalViewer(..)
  , displayImage
  , displayImageUsing
  , displayImageUsingAdhoc
  , displayImageFile
  -- ** Common viewers
  , defaultViewer
  , eogViewer
  , gpicviewViewer
  , fehViewer
  , gimpViewer
  -- * Supported Image Formats
  , module Data.Massiv.Array.IO.Image
  -- * All other common reading/writing components
  , module Base
  ) where

import Control.Monad (void)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Data.Massiv.Array as A
import Data.Massiv.Array.IO.Base (Image)
import Data.Massiv.Array.IO.Base as Base (Auto(..), ConvertError(..),
                                          DecodeError(..), EncodeError(..),
                                          FileFormat(..), MonadThrow(..),
                                          Readable(..), Sequence(..),
                                          Writable(..), Default(..),
                                          convertEither,
                                          convertImage, coerceBinaryImage,
                                          decode', decodeError,
                                          defaultWriteOptions,
                                          demoteLumaAlphaImage, demoteLumaImage,
                                          encode', encodeError,
                                          fromImageBaseModel, fromMaybeDecode,
                                          fromMaybeEncode,
                                          promoteLumaAlphaImage,
                                          promoteLumaImage, toImageBaseModel,
                                          toProxy)
import Data.Massiv.Array.IO.Image
import Graphics.Pixel.ColorSpace
import Prelude
import Prelude as P hiding (readFile, writeFile)
import System.FilePath ((</>), (<.>))
import System.IO (IOMode(..), hClose, openBinaryTempFile)
import UnliftIO.Concurrent (forkIO)
import UnliftIO.Directory (createDirectoryIfMissing, getTemporaryDirectory)
import UnliftIO.Exception (catchAny, bracket)
import UnliftIO.IO.File
import UnliftIO.Process (readProcess)


-- | External viewing application to use for displaying images.
data ExternalViewer =
  ExternalViewer FilePath [String] Int
    -- ^ Any custom viewer, which can be specified:
    --
    -- * @FilePath@ - to the actual viewer executable.
    -- * @[String]@ - command line arguments that will be passed to the executable.
    -- * @Int@ - position index in the above list where `FilePath` to an image should be
    -- injected
  deriving Int -> ExternalViewer -> ShowS
[ExternalViewer] -> ShowS
ExternalViewer -> String
(Int -> ExternalViewer -> ShowS)
-> (ExternalViewer -> String)
-> ([ExternalViewer] -> ShowS)
-> Show ExternalViewer
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ExternalViewer] -> ShowS
$cshowList :: [ExternalViewer] -> ShowS
show :: ExternalViewer -> String
$cshow :: ExternalViewer -> String
showsPrec :: Int -> ExternalViewer -> ShowS
$cshowsPrec :: Int -> ExternalViewer -> ShowS
Show



-- | Read an array from one of the supported `Readable` file formats.
--
-- For example `readImage` assumes all images to be in sRGB color space, but if you know
-- that the image is actually encoded in some other color space, for example `AdobeRGB`,
-- then you can read it in manually into a matching color model and then cast into a color
-- space you know it is encoded in:
--
-- >>> :set -XDataKinds
-- >>> import qualified Graphics.ColorModel as CM
-- >>> frogRGB <- readArray JPG "files/_frog.jpg" :: IO (Image S CM.RGB Word8)
-- >>> let frogAdobeRGB = (fromImageBaseModel frogRGB :: Image S (AdobeRGB 'NonLinear) Word8)
--
-- @since 0.1.0
readArray :: (Readable f arr, MonadIO m) =>
             f -- ^ File format that should be used while decoding the file
          -> FilePath -- ^ Path to the file
          -> m arr
readArray :: f -> String -> m arr
readArray f
format String
path = IO arr -> m arr
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO ByteString
B.readFile String
path IO ByteString -> (ByteString -> IO arr) -> IO arr
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= f -> ByteString -> IO arr
forall f arr (m :: * -> *).
(Readable f arr, MonadThrow m) =>
f -> ByteString -> m arr
decodeM f
format)
{-# INLINE readArray #-}

-- | Read an array from one of the supported file formats. Some formats are capable of
-- preducing format specific metadata.
--
-- @since 0.2.0
readArrayWithMetadata ::
     (Readable f arr, MonadIO m)
  => f -- ^ File format that should be used while decoding the file
  -> FilePath -- ^ Path to the file
  -> m (arr, Metadata f)
readArrayWithMetadata :: f -> String -> m (arr, Metadata f)
readArrayWithMetadata f
format String
path = IO (arr, Metadata f) -> m (arr, Metadata f)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO ByteString
B.readFile String
path IO ByteString
-> (ByteString -> IO (arr, Metadata f)) -> IO (arr, Metadata f)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= f -> ByteString -> IO (arr, Metadata f)
forall f arr (m :: * -> *).
(Readable f arr, MonadThrow m) =>
f -> ByteString -> m (arr, Metadata f)
decodeWithMetadataM f
format)
{-# INLINE readArrayWithMetadata #-}

writeLazyAtomically :: FilePath -> BL.ByteString -> IO ()
writeLazyAtomically :: String -> ByteString -> IO ()
writeLazyAtomically String
filepath ByteString
bss =
  String -> IOMode -> (Handle -> IO ()) -> IO ()
forall (m :: * -> *) r.
MonadUnliftIO m =>
String -> IOMode -> (Handle -> m r) -> m r
withBinaryFileDurableAtomic String
filepath IOMode
WriteMode ((Handle -> IO ()) -> IO ()) -> (Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Handle
h -> (ByteString -> IO ()) -> [ByteString] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
Prelude.mapM_ (Handle -> ByteString -> IO ()
B.hPut Handle
h) (ByteString -> [ByteString]
BL.toChunks ByteString
bss)
{-# INLINE writeLazyAtomically #-}

-- | Write an array to disk.
--
-- >>> :set -XDataKinds
-- >>> frogYCbCr <- readImage "files/frog.jpg" :: IO (Image S (Y'CbCr SRGB) Word8)
-- >>> frogAdobeRGB = convertImage frogYCbCr :: Image D (AdobeRGB 'NonLinear) Word8
-- >>> writeArray JPG def "files/_frog.jpg" $ toImageBaseModel $ computeAs S frogAdobeRGB
--
-- /Note/ - On UNIX operating systems writing will happen with guarantees of atomicity and
-- durability, see `withBinaryFileDurableAtomic`.
--
-- @since 0.2.0
writeArray :: (Writable f arr, MonadIO m) =>
              f -- ^ Format to use while encoding the array
           -> WriteOptions f -- ^ Any file format related encoding options. Use `def` for default.
           -> FilePath
           -> arr
           -> m ()
writeArray :: f -> WriteOptions f -> String -> arr -> m ()
writeArray f
format WriteOptions f
opts String
filepath arr
arr =
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (f -> WriteOptions f -> arr -> IO ByteString
forall f arr (m :: * -> *).
(Writable f arr, MonadThrow m) =>
f -> WriteOptions f -> arr -> m ByteString
encodeM f
format WriteOptions f
opts arr
arr IO ByteString -> (ByteString -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> ByteString -> IO ()
writeLazyAtomically String
filepath)
{-# INLINE writeArray #-}


-- | Tries to guess an image format from file's extension, then attempts to decode it as
-- such. It also assumes an image is encoded in sRGB color space or its alternate
-- representation. In order to supply the format manually or choose a different color
-- space, eg. `AdobeRGB`, use `readArray` instead. Color space and precision of the result
-- image must match exactly that of the actual image.
--
-- May throw `ConvertError`, `DecodeError` and other standard errors related to file IO.
--
-- Resulting image will be read as specified by the type signature:
--
-- >>> :set -XDataKinds
-- >>> frog <- readImage "files/frog.jpg" :: IO (Image S (Y'CbCr SRGB) Word8)
-- >>> size frog
-- Sz (200 :. 320)
--
-- @__>>> displayImage frog__ @
--
-- ![frog](files/frog.jpg)
--
-- In case when the result image type does not match the color space or precision of the
-- actual image file, `ConvertError` will be thrown.
--
-- >>> frog <- readImage "files/frog.jpg" :: IO (Image S (SRGB 'NonLinear) Word8)
-- *** Exception: ConvertError "Cannot decode JPG image <Image S YCbCr Word8> as <Image S SRGB 'NonLinear Word8>"
--
-- Whenever image is not in the color space or precision that we need, either use
-- `readImageAuto` or manually convert to the desired one by using the appropriate
-- conversion functions:
--
-- >>> frogYCbCr <- readImage "files/frog.jpg" :: IO (Image S (Y'CbCr SRGB) Word8)
-- >>> let frogSRGB = convertImage frogYCbCr :: Image D (SRGB 'NonLinear) Word8
--
-- A simpler approach to achieve the same effect would be to use `readImageAuto`:
--
-- >>> frogSRGB' <- readImageAuto "files/frog.jpg" :: IO (Image S (SRGB 'NonLinear) Word8)
-- >>> compute frogSRGB == frogSRGB'
-- True
--
-- @since 0.1.0
readImage ::
     (ColorModel cs e, MonadIO m)
  => FilePath -- ^ File path for an image
  -> m (Image S cs e)
readImage :: String -> m (Image S cs e)
readImage String
path = IO (Image S cs e) -> m (Image S cs e)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO ByteString
B.readFile String
path IO ByteString
-> (ByteString -> IO (Image S cs e)) -> IO (Image S cs e)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [Decode (Image S cs e)]
-> String -> ByteString -> IO (Image S cs e)
forall (m :: * -> *) r cs e.
MonadThrow m =>
[Decode (Image r cs e)] -> String -> ByteString -> m (Image r cs e)
decodeImageM [Decode (Image S cs e)]
forall cs e. ColorModel cs e => [Decode (Image S cs e)]
imageReadFormats String
path)
{-# INLINE readImage #-}


-- | Similar to `readImage`, but will perform all necessary color space conversion
-- and precision adjustment in order to match the result image type. Very useful whenever
-- image format isn't known ahead of time.
--
-- >>> frogCMYK <- readImageAuto "files/frog.jpg" :: IO (Image S (CMYK (SRGB 'NonLinear)) Double)
-- >>> size frogCMYK
-- Sz (200 :. 320)
--
-- @since 0.1.0
readImageAuto ::
     (Mutable r Ix2 (Pixel cs e), ColorSpace cs i e, MonadIO m)
  => FilePath -- ^ File path for an image
  -> m (Image r cs e)
readImageAuto :: String -> m (Image r cs e)
readImageAuto String
path = IO (Image r cs e) -> m (Image r cs e)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO ByteString
B.readFile String
path IO ByteString
-> (ByteString -> IO (Image r cs e)) -> IO (Image r cs e)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [Decode (Image r cs e)]
-> String -> ByteString -> IO (Image r cs e)
forall (m :: * -> *) r cs e.
MonadThrow m =>
[Decode (Image r cs e)] -> String -> ByteString -> m (Image r cs e)
decodeImageM [Decode (Image r cs e)]
forall r cs e i.
(Mutable r Ix2 (Pixel cs e), ColorSpace cs i e) =>
[Decode (Image r cs e)]
imageReadAutoFormats String
path)
{-# INLINE readImageAuto #-}



-- | This function will guess an output file format from the file extension and will write
-- to file any image with the color model that is supported by that format. In case that
-- automatic precision adjustment or colors space conversion is also desired,
-- `writeImageAuto` can be used instead.
--
-- Can throw `ConvertError`, `EncodeError` and other usual IO errors.
--
-- /Note/ - On UNIX operating systems writing will happen with guarantees of atomicity and
-- durability, see `withBinaryFileDurableAtomic`.
--
-- @since 0.1.0
writeImage ::
     (Source r Ix2 (Pixel cs e), ColorModel cs e, MonadIO m) => FilePath -> Image r cs e -> m ()
writeImage :: String -> Image r cs e -> m ()
writeImage String
path Image r cs e
img = IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO ([Encode (Image r cs e)] -> String -> Image r cs e -> IO ByteString
forall (m :: * -> *) r cs e.
MonadThrow m =>
[Encode (Image r cs e)] -> String -> Image r cs e -> m ByteString
encodeImageM [Encode (Image r cs e)]
forall r cs e.
(Source r Ix2 (Pixel cs e), ColorModel cs e) =>
[Encode (Image r cs e)]
imageWriteFormats String
path Image r cs e
img IO ByteString -> (ByteString -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> ByteString -> IO ()
writeLazyAtomically String
path)


-- | Write an image encoded in sRGB color space into a file while performing all necessary
-- precision and color space conversions. If a file supports color model that the image is
-- on then it will be encoded as such. For example writing a TIF file in CMYK color model,
-- 8bit precision and an sRGB color space:
--
-- >>> frogYCbCr <- readImage "files/frog.jpg" :: IO (Image S (Y'CbCr SRGB) Word8)
-- >>> writeImageAuto "files/frog.tiff" (convertImage frogYCbCr :: Image D (CMYK (AdobeRGB 'NonLinear)) Word8)
--
-- Regardless that the color space supplied was `AdobeRGB` auto conversion will ensure it
-- is stored as `SRGB`, except in `CM.CMYK` color model, since `TIF` file format supports it.
--
-- @since 0.1.0
writeImageAuto ::
     (Source r Ix2 (Pixel cs e), ColorSpace cs i e, ColorSpace (BaseSpace cs) i e, MonadIO m)
  => FilePath
  -> Image r cs e
  -> m ()
writeImageAuto :: String -> Image r cs e -> m ()
writeImageAuto String
path Image r cs e
img =
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO ([Encode (Image r cs e)] -> String -> Image r cs e -> IO ByteString
forall (m :: * -> *) r cs e.
MonadThrow m =>
[Encode (Image r cs e)] -> String -> Image r cs e -> m ByteString
encodeImageM [Encode (Image r cs e)]
forall r cs e i.
(Source r Ix2 (Pixel cs e), ColorSpace cs i e,
 ColorSpace (BaseSpace cs) i e) =>
[Encode (Image r cs e)]
imageWriteAutoFormats String
path Image r cs e
img IO ByteString -> (ByteString -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> ByteString -> IO ()
writeLazyAtomically String
path)



-- | An image is written as a @.tiff@ file into an operating system's temporary
-- directory and passed as an argument to the external viewer program.
--
-- @since 0.1.0
displayImageUsing ::
     (Writable (Auto TIF) (Image r cs e), MonadIO m)
  => ExternalViewer -- ^ Image viewer program
  -> Bool -- ^ Should this function block the current thread until viewer is
          -- closed. Supplying `False` is only safe in the ghci session.
  -> Image r cs e -- ^ Image to display
  -> m ()
displayImageUsing :: ExternalViewer -> Bool -> Image r cs e -> m ()
displayImageUsing ExternalViewer
viewer Bool
block =
  ExternalViewer
-> Bool -> Encode (Image r cs e) -> Image r cs e -> m ()
forall (m :: * -> *) img.
MonadIO m =>
ExternalViewer -> Bool -> Encode img -> img -> m ()
displayImageUsingAdhoc  ExternalViewer
viewer Bool
block (Auto TIF -> Encode (Image r cs e)
forall f out. Writable f out => f -> Encode out
writableAdhoc (TIF -> Auto TIF
forall f. f -> Auto f
Auto TIF
TIF))


-- | Encode an image using an adhoc into an operating system's temporary
-- directory and passed as an argument to the external viewer program.
--
-- @since 4.1.0
displayImageUsingAdhoc ::
     MonadIO m
  => ExternalViewer -- ^ Image viewer program
  -> Bool -- ^ Should this function block the current thread until viewer is
          -- closed. Supplying `False` is only safe in the ghci session.
  -> Encode img
  -> img -- ^ Image to display
  -> m ()
displayImageUsingAdhoc :: ExternalViewer -> Bool -> Encode img -> img -> m ()
displayImageUsingAdhoc ExternalViewer
viewer Bool
block Encode img
adhoc img
img =
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    ByteString
bs <- Encode img -> img -> IO ByteString
forall (m :: * -> *) out.
MonadThrow m =>
Encode out -> out -> m ByteString
encodeAdhocM Encode img
adhoc img
img
    -- this function is meant to be used in ghci, therefore it is ok to not cleanup
    -- dangling threads after display is called.
    (if Bool
block then IO () -> IO ()
forall a. a -> a
id else IO ThreadId -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO ThreadId -> IO ()) -> (IO () -> IO ThreadId) -> IO () -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ThreadId
forall (m :: * -> *). MonadUnliftIO m => m () -> m ThreadId
forkIO (IO () -> IO ThreadId) -> (IO () -> IO ()) -> IO () -> IO ThreadId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
reportErrors) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ ByteString -> IO ()
display ByteString
bs
  where
    reportErrors :: IO () -> IO ()
reportErrors IO ()
action =
      IO () -> (SomeException -> IO ()) -> IO ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
catchAny IO ()
action ((SomeException -> IO ()) -> IO ())
-> (SomeException -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ SomeException
exc ->
        String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"<displayImageUsingAdhoc>: Failed with: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> SomeException -> String
forall e. Exception e => e -> String
displayException SomeException
exc
    display :: ByteString -> IO ()
display ByteString
bs = do
      String
tmpDir <- ShowS -> IO String -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (String -> ShowS
</> String
"massiv-io") IO String
forall (m :: * -> *). MonadIO m => m String
getTemporaryDirectory
      Bool -> String -> IO ()
forall (m :: * -> *). MonadIO m => Bool -> String -> m ()
createDirectoryIfMissing Bool
True String
tmpDir
      IO (String, Handle)
-> ((String, Handle) -> IO ())
-> ((String, Handle) -> IO ())
-> IO ()
forall (m :: * -> *) a b c.
MonadUnliftIO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket
        (String -> String -> IO (String, Handle)
openBinaryTempFile String
tmpDir (String
"tmp-img" String -> ShowS
<.> Encode img -> String
forall f. FileFormat f => f -> String
ext Encode img
adhoc))
        (Handle -> IO ()
hClose (Handle -> IO ())
-> ((String, Handle) -> Handle) -> (String, Handle) -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, Handle) -> Handle
forall a b. (a, b) -> b
snd)
        (\(String
imgPath, Handle
imgHandle) -> do
           Handle -> ByteString -> IO ()
BL.hPut Handle
imgHandle ByteString
bs
           Handle -> IO ()
hClose Handle
imgHandle
           ExternalViewer -> String -> IO ()
forall (m :: * -> *). MonadIO m => ExternalViewer -> String -> m ()
displayImageFile ExternalViewer
viewer String
imgPath)


-- | Displays an image file by calling an external image viewer. It will block until the
-- external viewer is closed.
--
-- @since 0.1.0
displayImageFile :: MonadIO m => ExternalViewer -> FilePath -> m ()
displayImageFile :: ExternalViewer -> String -> m ()
displayImageFile (ExternalViewer String
exe [String]
args Int
ix) String
imgPath =
  m String -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m String -> m ()) -> m String -> m ()
forall a b. (a -> b) -> a -> b
$ IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO String -> m String) -> IO String -> m String
forall a b. (a -> b) -> a -> b
$ String -> [String] -> String -> IO String
forall (m :: * -> *).
MonadIO m =>
String -> [String] -> String -> m String
readProcess String
exe ([String]
argsBefore [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
imgPath] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
argsAfter) String
""
  where ([String]
argsBefore, [String]
argsAfter) = Int -> [String] -> ([String], [String])
forall a. Int -> [a] -> ([a], [a])
P.splitAt Int
ix [String]
args


-- | Writes an image to a temporary file and makes a call to an external viewer that is
-- set as a default image viewer by the OS. This is a non-blocking function call, so it
-- might take some time before an image will appear.
--
-- /Note/ - This function should only be used in ghci, otherwise use @`displayImage`
-- `defaultViewer` `True`@
--
-- @since 0.1.0
displayImage :: (Writable (Auto TIF) (Image r cs e), MonadIO m) => Image r cs e -> m ()
displayImage :: Image r cs e -> m ()
displayImage = ExternalViewer -> Bool -> Image r cs e -> m ()
forall r cs e (m :: * -> *).
(Writable (Auto TIF) (Image r cs e), MonadIO m) =>
ExternalViewer -> Bool -> Image r cs e -> m ()
displayImageUsing ExternalViewer
defaultViewer Bool
False

-- | Default viewer is inferred from the operating system.
--
-- @since 0.1.0
defaultViewer :: ExternalViewer
defaultViewer :: ExternalViewer
defaultViewer =
#if defined(OS_Win32)
  ExternalViewer "explorer.exe" [] 0
#elif defined(OS_Linux)
  String -> [String] -> Int -> ExternalViewer
ExternalViewer String
"xdg-open" [] Int
0
#elif defined(OS_Mac)
  ExternalViewer "open" [] 0
#else
  error "Graphics.Image.IO.defaultViewer: Could not determine default viewer."
#endif


-- | @eog \/tmp\/massiv\/img.tiff@
--
-- <https://help.gnome.org/users/eog/stable/ Eye of GNOME>
eogViewer :: ExternalViewer
eogViewer :: ExternalViewer
eogViewer = String -> [String] -> Int -> ExternalViewer
ExternalViewer String
"eog" [] Int
0


-- | @feh --fullscreen --auto-zoom \/tmp\/massiv\/img.tiff@
--
-- <https://feh.finalrewind.org/ FEH>
fehViewer :: ExternalViewer
fehViewer :: ExternalViewer
fehViewer = String -> [String] -> Int -> ExternalViewer
ExternalViewer String
"feh" [String
"--fullscreen", String
"--auto-zoom"] Int
2


-- | @gpicview \/tmp\/massiv\/img.tiff@
--
-- <http://lxde.sourceforge.net/gpicview/ GPicView>
gpicviewViewer :: ExternalViewer
gpicviewViewer :: ExternalViewer
gpicviewViewer = String -> [String] -> Int -> ExternalViewer
ExternalViewer String
"gpicview" [] Int
0


-- | @gimp \/tmp\/massiv\/img.tiff@
--
-- <https://www.gimp.org/ GIMP>
gimpViewer :: ExternalViewer
gimpViewer :: ExternalViewer
gimpViewer = String -> [String] -> Int -> ExternalViewer
ExternalViewer String
"gimp" [] Int
0


{- $supported

Encoding and decoding of images is done using
<http://hackage.haskell.org/package/JuicyPixels JuicyPixels> and
<http://hackage.haskell.org/package/netpbm netpbm> packages.

List of image formats that are currently supported, and their exact 'ColorModel's with
precision for reading and writing without any conversion:

* 'BMP':

    * __read__: ('PixelX' 'Bit'), ('PixelY' 'Word8'), ('PixelRGB' 'Word8'), ('PixelRGBA' 'Word8')
    * __write__: ('PixelY' 'Word8'), ('PixelRGB' 'Word8'), ('PixelRGBA' 'Word8')

* 'GIF':

    * __read__: ('PixelRGB' 'Word8'), ('PixelRGBA' 'Word8')
    * __write__: ('PixelY' 'Word8'), ('PixelRGB' 'Word8')
    * Also supports reading and writing animated images

* 'HDR':

    * __read__: ('PixelRGB' 'Float')
    * __write__: ('PixelRGB' 'Float')

* 'JPG':

    * __read__: ('PixelY' 'Word8'), ('PixelYA' 'Word8'), ('PixelRGB' 'Word8'), ('PixelCMYK' 'Word8'),
    (`PixelY'CbCr`, 'Word8')
    * __write__: ('PixelY' 'Word8'), ('PixelRGB' 'Word8'), ('PixelCMYK' 'Word8'),
    (`PixelY'CbCr`, 'Word8')

* 'PNG':

    * __read__: ('PixelX' 'Bit'), ('PixelY' 'Word8'), ('PixelY' 'Word16'), ('PixelYA' 'Word8'), ('PixelYA' 'Word16'),
    ('PixelRGB' 'Word8'), ('PixelRGB' 'Word16'), ('PixelRGBA' 'Word8'), ('PixelRGBA' 'Word16')
    * __write__: ('PixelY' 'Word8'), ('PixelY' 'Word16'), ('PixelYA' 'Word8'), ('PixelYA' 'Word16'),
    ('PixelRGB' 'Word8'), ('PixelRGB' 'Word16'), ('PixelRGBA' 'Word8'), ('PixelRGBA' 'Word16')

* 'TGA':

    * __read__: ('PixelX' 'Bit'), ('PixelY' 'Word8'), ('PixelRGB' 'Word8'), ('PixelRGBA' 'Word8')
    * __write__: ('PixelY' 'Word8'), ('PixelRGB' 'Word8'), ('PixelRGBA' 'Word8')

* 'TIF':

    * __read__:
    ('PixelY' 'Word8'), ('PixelY' 'Word16'), ('PixelY' 'Word32'), ('PixelY' 'Float'),
    ('PixelYA' 'Word8'), ('PixelYA' 'Word16'),
    ('PixelRGB' 'Word8'), ('PixelRGB' 'Word16'), ('PixelRGBA' 'Word8'), ('PixelRGBA' 'Word16'),
    ('PixelCMYK' 'Word8'), ('PixelCMYK' 'Word16')
    * __write__:
    ('PixelY' 'Word8'), ('PixelY' 'Word16'), ('PixelY' 'Word32'), ('PixelY' 'Float'),
    ('PixelYA' 'Word8'), ('PixelYA' 'Word16'),
    ('PixelRGB' 'Word8'), ('PixelRGB' 'Word16'), ('PixelRGBA' 'Word8'), ('PixelRGBA' 'Word16')
    ('PixelCMYK' 'Word8'), ('PixelCMYK' 'Word16'), (`PixelY'CbCr` 'Word8')

* 'PBM':

    * __read__: ('PixelY' 'Bit')
    * Also supports `Sequence` of images in one file, when read as @['PBM']@

* 'PGM':

    * __read__: ('PixelY' 'Word8'), ('PixelY' 'Word16')
    * Also supports `Sequence` of images in one file, when read as @['PGM']@

* 'PPM':

    * __read__: ('PixelRGB' 'Word8'), ('PixelRGB' 'Word16')
    * Also supports `Sequence` of images in one file, when read as @['PPM']@

-}