weekdaze-0.0.0.1: src-lib/WeekDaze/Colour/RGB.hs
{-
Copyright (C) 2015 Dr. Alistair Ward
This file is part of WeekDaze.
WeekDaze is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
WeekDaze is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with WeekDaze. If not, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@] Exports a representation of a colour as primary components.
-}
module WeekDaze.Colour.RGB(
-- * Types
-- ** Data-types
RGB(),
-- * Constants
black,
white24Bit,
radix,
-- saturated,
-- * Functions
absDifference,
-- combine,
toTriple,
-- ** Constructors
mkRGB,
-- mkRGBUnitInterval,
mkRGB24Bit,
fromIntegral',
-- ** Operators
-- (>-<),
(>+<),
-- ** Translation
toRGBUnitInterval,
fromRGBUnitInterval
) where
import qualified Control.Arrow
infixl 6 >+< -- Like (+).
infixl 6 >-< -- Like (-).
-- | The representation of a colour as three primary components.
data RGB primaryColour = MkRGB primaryColour primaryColour primaryColour deriving (Eq, Show)
-- | Constructor.
mkRGB
:: a -- ^ Red.
-> a -- ^ Green.
-> a -- ^ Blue.
-> RGB a
mkRGB = MkRGB
instance Functor RGB where
f `fmap` MkRGB red green blue = mkRGB (f red) (f green) (f blue) -- CAVEAT: the bounds are neither checked nor even known.
-- | Smart-constructor.
mkRGBUnitInterval
:: (Fractional a, Ord a, Show a)
=> a -- ^ Red.
-> a -- ^ Green.
-> a -- ^ Blue.
-> RGB a
mkRGBUnitInterval red green blue
| any ($ [red, green, blue]) [any (< 0), any (> 1)] = error $ "WeekDaze.Colour.RGB.mkRGBUnitInterval:\teach of the specified fractional component-colours must be in the unit-interval; " ++ show (red, green, blue)
| otherwise = mkRGB red green blue
-- | Smart-constructor.
mkRGB24Bit
:: (Integral a, Show a)
=> a -- ^ Red.
-> a -- ^ Green.
-> a -- ^ Blue.
-> RGB a
mkRGB24Bit red green blue
| any ($ [red, green, blue]) [any (< 0), any (> saturated)] = error $ "WeekDaze.Colour.RGB.mkRGB24Bit:\teach of the specified integral component-colours must be in the closed interval [0," ++ show (saturated :: Int) ++ "]; " ++ show (red, green, blue)
| otherwise = mkRGB red green blue
-- | Constant base of number-system used to represent 24-bit colours in HTML.
radix :: Integral i => i
radix = 2 ^ (8 :: Int)
-- | Represents the specified positive integral value as RGB 24-bit.
fromIntegral' :: (Integral i, Show i) => i -> RGB i
fromIntegral' i
| i < 0 = error $ "WeekDaze.Colour.RGB.fromIntegral':\tthe specified value can't be negative; " ++ show i
| i > radix ^ (3 :: Int) = error $ "WeekDaze.Colour.RGB.fromIntegral':\tthe specified value is too large; " ++ show i
| otherwise = (
\((red, green), blue) -> mkRGB24Bit red green blue
) . Control.Arrow.first (`divMod` radix) $ i `divMod` radix
-- | Deconstructor.
toTriple :: RGB a -> (a, a, a)
toTriple (MkRGB red green blue) = (red, green, blue)
-- | The constant base of number-system used to represent colours in HTML.
saturated :: Integral i => i
saturated = pred radix
-- | The constant representation of /black/ in RGB.
black :: Num n => RGB n
black = mkRGB 0 0 0
-- | The constant representation of /white/ in RGB.
white24Bit :: (Integral n, Show n) => RGB n
white24Bit = mkRGB24Bit saturated saturated saturated
-- | Combine two RGB-representations using the specified function to combine each primary colour.
combine
:: (a -> a -> a)
-> RGB a
-> RGB a
-> RGB a
combine f (MkRGB rL gL bL) (MkRGB rR gR bR) = mkRGB (rL `f` rR) (gL `f` gR) (bL `f` bR)
{- |
* Add the matching component colours.
* CAVEAT: this may result in overflow.
-}
(>+<) :: Num n => RGB n -> RGB n -> RGB n
(>+<) = combine (+)
{- |
* Find the difference between the matching component colours.
* CAVEAT: negative values may result.
-}
(>-<) :: Num n => RGB n -> RGB n -> RGB n
(>-<) = combine (-)
-- | Find the absolute difference between the matching component colours.
absDifference :: Num n => RGB n -> RGB n -> RGB n
absDifference x y = abs `fmap` (x >-< y)
-- | Convert RGB 24-bit to RGB UnitInterval, i.e. map from the closed interval [0,255] to the semi-closed interval [0,1).
toRGBUnitInterval :: (Integral i, Fractional f) => RGB i -> RGB f
toRGBUnitInterval = fmap $ (/ fromInteger radix) . fromIntegral
-- | Convert RGB UnitInterval to RGB 24-bit, i.e. map from the semi-closed interval [0,1) to the closed interval [0,255].
fromRGBUnitInterval :: (Integral i, RealFrac f) => RGB f -> RGB i
fromRGBUnitInterval = fmap $ min saturated {-CAVEAT: regrettably, we actually have to work with the closed interval [0,1] and 1 must still map to 255 not 256 (i.e. 0 in modulo-256 arithmetic-} . floor . (* fromInteger radix)