{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeSynonymInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Prizm.Color.SRGB -- Copyright : (C) 2013 Parnell Springmeyer -- License : BSD3 -- Maintainer : Parnell Springmeyer -- Stability : stable -- -- Transformation functions and convenience functions to do the base -- conversion between S'RGB' and 'CIEXYZ'. ---------------------------------------------------------------------------- module Data.Prizm.Color.SRGB ( clamp , parse , showRGB , toXYZMatrix , transform ) where import Control.Applicative import Data.Convertible.Base import Data.Monoid import Data.Prizm.Color.Matrices.RGB import Data.Prizm.Color.Transform import Data.Prizm.Types import Data.String import qualified Data.Text as T import Data.Text.Read as R import Data.Word import Numeric (showHex) instance PresetColor RGB where white = RGB 255 255 255 black = RGB 0 0 0 ------------------------------------------------------------------------------ -- Utilities ------------------------------------------------------------------------------ -- | Transform an RGB integer to be computed against a matrix. transform :: Integer -> Double transform v | dv > 0.04045 = (((dv + 0.055) / ap) ** 2.4) * 100 | otherwise = (dv / 12.92) * 100 where dv = fromIntegral v / 255 ap = 1.0 + 0.055 -- | Clamp a 'Word8' with an upper-bound of 255 (the maximum RGB -- value). clamp :: Word8 -> Word8 clamp i = max (min i 255) 0 -- All credit for the below three functions go to the HSColour module. -- | Show a colour in hexadecimal form, e.g. @#00aaff@ showRGB :: RGB -> Hex showRGB c = (("#"++) . showHex2 r' . showHex2 g' . showHex2 b') "" where (RGB r' g' b') = c showHex2 x | x <= 0xf = ("0"++) . showHex x | otherwise = showHex x -- | Parse a 'Hex' into an s'RGB' type. parse :: T.Text -> RGB parse t = case T.uncons t of Just ('#', cs) | T.all isHex cs -> case T.unpack cs of [a, b, c, d, e, f, _g, _h] -> RGB (hex a b) (hex c d) (hex e f) [a, b, c, d, e, f ] -> RGB (hex a b) (hex c d) (hex e f) [a, b, c, _d ] -> RGB (hex a a) (hex b b) (hex c c) [a, b, c ] -> RGB (hex a a) (hex b b) (hex c c) _ -> err _ -> err where hex a b = either err fst (R.hexadecimal (T.singleton a <> T.singleton b)) isHex a = (a >= 'a' && a <= 'f') || (a >= 'A' && a <= 'F') || (a >= '0' && a <= '9') err = error "Invalid color string" ------------------------------------------------------------------------------ -- Convertible ------------------------------------------------------------------------------ instance Convertible RGB CIEXYZ where -- | Convert an S'RGB' value to a 'CIEXYZ' value with the default -- @d65@ illuminant matrix. safeConvert = Right . (toXYZMatrix d65SRGB) instance Convertible RGB Hex where -- | Convert an S'RGB' value to a hexadecimal representation. safeConvert = Right . showRGB instance Convertible Hex RGB where -- | Convert a hexadecimal value to an S'RGB'. safeConvert = Right . parse . fromString -- | Convert an s'RGB' value to a 'CIEXYZ' given a pre-calculated -- illuminant matrix. toXYZMatrix :: RGBtoXYZ -> RGB -> CIEXYZ toXYZMatrix (RGBtoXYZ m) (RGB r g b) = let t = ZipList ((transform . fromIntegral) <$> (clamp <$> [r,g,b])) [x,y,z] = (roundN 3) <$> ((zipTransform t) <$> m) in CIEXYZ x y z