\section{Color}
\begin{code}
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 ()
mapRGB :: (RSdouble -> RSdouble) -> RGB -> RGB
mapRGB f (RGB r g b) = RGB (f r) (f g) (f b)
filterRGB :: RGB -> RGB -> RGB
filterRGB = zipRGB (*)
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}