{-|
Module      : Graphics.Mosaico.Imagen
Description : Lectura de imágenes como matrices de píxeles
Copyright   : ⓒ Manuel Gómez, 2015
License     : BSD3
Maintainer  : targen@gmail.com
Stability   : experimental
Portability : portable

Tipos para representar imágenes como matrices (listas anidadas rectangulares) de
píxeles de colores, y una función para cargar una imagen a esta representación a
partir de un archivo.
-}

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UnicodeSyntax #-}

module Graphics.Mosaico.Imagen
  ( Color(Color, rojo, verde, azul)
  , Imagen(Imagen, altura, anchura, datos)
  , leerImagen
  )
  where

import Codec.Picture         (readImage)
import Codec.Picture.Types   (DynamicImage(ImageRGB8, ImageYCbCr8), PixelRGB8(PixelRGB8), convertImage, imageWidth, imageHeight, pixelFold)
import Control.Applicative   (pure)
import Control.Monad.Error   (throwError)
import Control.Monad.Unicode ((=))
import Data.Either           (Either)
import Data.Function         (($))
import Data.Function.Unicode (())
import Data.Functor          ((<$>))
import Data.List             (reverse)
import Data.List.Split       (chunksOf)
import Data.List.Unicode     (())
import Data.String           (String)
import Data.Word             (Word8)
import Prelude               (Integer, fromIntegral)
import System.IO             (IO)
import Text.Show             (Show, show)



-- | Un punto en el espacio de colores RGB donde cada componente de color
-- se especifica por un entero entre 0 y 255 (8 bits).  Como el color es
-- toda la información almacenada en un píxel, este mismo tipo se usa para
-- representar píxeles individuales en una 'Imagen'.
data Color
  = Color
    { rojo, verde, azul  Word8
    }
  deriving Show



-- | La representación de una imagen como una matriz de píxeles dados por
-- el 'Color' de cada uno.
--
-- Las dimensiones de la imagen se guardan por separado para no tener que
-- recorrer las listas de píxeles cada vez que haga falta conocer sus
-- longitudes.
data Imagen
  = Imagen
    { anchura  Integer
    -- ^ El número de píxeles en cada fila de la 'Imagen'.

    , altura  Integer
    -- ^ El número de filas de la 'Imagen'.

    , datos  [[Color]]
    -- ^ Los datos de color de cada píxel de la imagen.
    --
    -- Cada píxel se representa con un valor del tipo 'Color' que especifica el
    -- color del píxel.  Los datos se organizan en una lista de filas de la
    -- imagen, donde cada fila es a su vez una lista de cada píxel individual de
    -- esa fila.
    --
    -- Esta lista de listas debe mantenerse rectangular: todas las filas deben
    -- ser listas con la misma longitud, que además debe ser igual a la
    -- 'anchura' de la misma 'Imagen'.  Además, la 'altura' debe ser igual a la
    -- longitud de la lista de filas.
    }

instance Show Imagen where
  show Imagen {..}
    = "Imagen {"
     " anchura = "  show anchura  ";"
     " altura = "   show altura   ";"
     " datos = […];"
     " }"



-- | Leer un archivo e intentar convertirlo en una 'Imagen'.  Se soportan
-- varios formatos de archivo de imagen, incluyendo PNG y JPEG.
leerImagen
   String
  -- ^ El nombre del archivo a leer.

   IO (Either String Imagen)
  -- ^ Si el archivo pudo leerse exitosamente y representarse como un valor
  -- del tipo 'Imagen', se produce el resultado @'Data.Either.Right' imagen@.
  -- Si no, se produce el resultado @'Data.Either.Left' razón@, donde @razón@
  -- será un 'String' con la razón por la cual la imagen no se pudo leer.

leerImagen filename
  = do
    imagen  (toImageRGB8 =) <$> readImage filename
    pure $ toImagen <$> imagen
  where
    toImageRGB8
      = \ case
        ImageRGB8   i  pure i
        ImageYCbCr8 i  pure $ convertImage i
        -- TODO: more conversions!
        _  throwError "conversión del formato de imagen a RGB8 no implantada"

    toImagen image
      = Imagen {..}
      where
        anchura = fromIntegral $ imageWidth  image
        altura  = fromIntegral $ imageHeight image
        datos
          = chunksOf (imageWidth image)
           reverse
          $ pixelFold f [] image
          where
            f acc _ _ (PixelRGB8 rojo verde azul)
              = Color {..} : acc