\section{Color} \begin{code}
{-# LANGUAGE MultiParamTypeClasses #-}
module RSAGL.Modeling.Color
    (RGB(..),
     RGBA(..),
     rgba,
     rgba256,
     gray,
     gray256,
     brightness,
     meanBrightness,
     zipRGB3,
     maxRGB,
     minRGB,
     maximizeRGB,
     ColorClass(..),
     addRGB,
     mapRGB,
     invertRGB,
     filterRGB,
     filterRGBLinear,
     scaleRGB,
     rgbToOpenGL,
     rgbaToOpenGL)
    where

import Control.Parallel.Strategies
import Graphics.Rendering.OpenGL.GL hiding (RGB,RGBA)
import RSAGL.Math.AbstractVector
import RSAGL.Math.Interpolation
import RSAGL.Types
\end{code} \texttt{addColor} paints a color on top of another color using the additive color system. For example, \texttt{red `addColor` green == yellow}. \texttt{filterColor} paints a color on top of another color using the subtractive color system (actually, multiplicative). For example, \texttt{yellow `filterColor` green == green}, but \texttt{red `filterColor` green == black}. \texttt{rgb256} works for colors specified in hexadecimal. For example, X11 ForestGreen is \texttt{rgb256 0x22 0x8B 0x22}. \begin{code}
data RGB = RGB { rgb_red, rgb_green, rgb_blue :: !RSdouble }
    deriving (Eq,Show)

data RGBA = RGBA { rgba_a :: !RSdouble, rgba_rgb :: !RGB }
    deriving (Eq,Show)

instance NFData RGB where

instance NFData RGBA where

class (AbstractVector c) => ColorClass c where
    rgb :: RSdouble -> RSdouble -> RSdouble -> c
    rgb256 :: (Integral i) => i -> i -> i -> c
    alpha :: RSdouble -> c -> RGBA
    alpha256 :: (Integral i) => i -> c -> RGBA
    clampColor :: c -> c
    zipRGB :: (RSdouble -> RSdouble -> RSdouble) -> RGB -> c -> c
    zipColor :: (RSdouble -> RSdouble -> RSdouble) -> c -> c -> c
    toPremultipliedRGB :: c -> RGB
    colorToOpenGL :: c -> Color4 GLdouble
    toRGBA :: c -> RGBA
    fromRGB :: RGB -> c

instance ColorClass RGB where
    rgb = RGB
    rgb256 r g b = rgb (i2f256 r) (i2f256 g) (i2f256 b)
    alpha = RGBA
    alpha256 a = alpha (i2f256 a)
    clampColor = mapRGB (min 1 . max 0)
    zipRGB = zipColor
    zipColor f (RGB r1 g1 b1) (RGB r2 g2 b2) = RGB (f r1 r2) (f g1 g2) (f b1 b2)
    toPremultipliedRGB = id
    colorToOpenGL = rgbToOpenGL
    toRGBA = RGBA 1
    fromRGB = id

instance ColorClass RGBA where
    rgb = rgba 1.0
    rgb256 = rgba256 255
    alpha x (RGBA a rgb_color) = RGBA (x*a) rgb_color
    alpha256 i (RGBA a rgb_color) = RGBA ((i2f256 i)*a) rgb_color
    clampColor (RGBA a rgb_color) = RGBA (min 1 $ max 0 a) $ clampColor rgb_color
    zipRGB f x (RGBA a y) = RGBA a $ zipRGB f x y
    zipColor f (RGBA a1 c1) (RGBA a2 c2) = RGBA (f a1 a2) (zipColor f c1 c2)
    toPremultipliedRGB (RGBA a rgb_color) = scaleRGB a rgb_color
    colorToOpenGL = rgbaToOpenGL
    toRGBA = id
    fromRGB = alpha 1.0

i2f256 :: (Integral i) => i -> RSdouble
i2f256 = (/ 255) . fromIntegral

rgba :: RSdouble -> RSdouble -> RSdouble -> RSdouble -> RGBA
rgba r g b a = alpha a $ (rgb r g b :: RGB)

rgba256 :: (Integral i) => i -> i -> i -> i -> RGBA
rgba256 r g b a = alpha256 a $ (rgb256 r g b :: RGB)

gray :: RSdouble -> RGB
gray x = rgb x x x

gray256 :: (Integral i) => i -> RGB
gray256 x = rgb256 x x x

brightness :: (ColorClass c) => c -> RSdouble
brightness c = 0.2126 * r + 0.7152 * g + 0.0722 * b
    where RGB r g b = toPremultipliedRGB c

meanBrightness :: (ColorClass c) => c -> RSdouble
meanBrightness c = (r + g + b) / 3
    where RGB r g b = toPremultipliedRGB c

zipRGB3 :: (RSdouble -> RSdouble -> RSdouble -> RSdouble) -> RGB -> RGB -> RGB -> RGB
zipRGB3 f (RGB ar ag ab) (RGB br bg bb) (RGB cr cg cb) = RGB (f ar br cr) (f ag bg cg) (f ab bb cb)

maxRGB :: RGB -> RSdouble
maxRGB (RGB r g b) = max r (max g b)

minRGB :: RGB -> RSdouble
minRGB (RGB r g b) = min r (min g b)

maximizeRGB :: RGB -> RGB
maximizeRGB (RGB r g b) | r <= 0 && g <= 0 && b <= 0 = gray 0
maximizeRGB c = mapRGB (/ maxRGB c) c

addRGB :: RGB -> RGB -> RGB
addRGB = addColor

addColor :: (ColorClass c) => c -> c -> c
addColor = zipColor (+)

subColor :: (ColorClass c) => c -> c -> c
subColor = zipColor (-)

{-# INLINE mapRGB #-}
mapRGB :: (RSdouble -> RSdouble) -> RGB -> RGB
mapRGB f (RGB r g b) = RGB (f r) (f g) (f b)

{-# INLINE filterRGB #-}
filterRGB :: RGB -> RGB -> RGB
filterRGB = zipRGB (*)

{-# INLINE scaleRGB #-}
scaleRGB :: RSdouble -> RGB -> RGB
scaleRGB x = mapRGB (*x)

invertRGB :: RGB -> RGB
invertRGB = mapRGB (1-)
\end{code} \texttt{filterRGBLinear} maps an RGB color between a black point and a white point. The black point will map to RGB 0 0 0, while the white point will map to RGB 1 1 1. \begin{code}
filterRGBLinear :: RGB -> RGB -> RGB -> RGB
filterRGBLinear black_point white_point = zipRGB3 (\b w c -> lerpBetweenClamped (b,c,w) (0,1)) black_point white_point

scaleRGBA :: RSdouble -> RGBA -> RGBA
scaleRGBA x c = c { rgba_a = x * rgba_a c,
                    rgba_rgb = scaleRGB x (rgba_rgb c) }

rgbToOpenGL :: RGB -> Color4 GLdouble
rgbToOpenGL (RGB r g b) = Color4 (toGLdouble r) (toGLdouble g) (toGLdouble b) 1

rgbaToOpenGL :: RGBA -> Color4 GLdouble
rgbaToOpenGL (RGBA a (RGB r g b)) = Color4 (toGLdouble r) (toGLdouble g) (toGLdouble b) (toGLdouble a)

instance AbstractZero RGB where
    zero = rgb 0 0 0

instance AbstractAdd RGB RGB where
    add = addColor

instance AbstractSubtract RGB RGB where
    sub = subColor

instance AbstractScale RGB where
    scalarMultiply d = scaleRGB $ f2f d

instance AbstractVector RGB

instance AbstractZero RGBA where
    zero = rgba 0 0 0 0

instance AbstractAdd RGBA RGBA where
    add = addColor

instance AbstractSubtract RGBA RGBA where
    sub = subColor

instance AbstractScale RGBA where
    scalarMultiply d = scaleRGBA $ f2f d

instance AbstractVector RGBA
\end{code}