splot-0.2.7: Tools/ColorMap.hs
-- |Color cycling routines.
-- This module provides functions for maintaining a map from arbitrary strings to colors and
-- generating new colors for unknown names by cycling over the RGB spectrum.
--
-- P.S. We use the spelling "color" in our identifiers for no particular reason
-- other than internal consistency.
module Tools.ColorMap (
ColorMap,
defaultColorMap,
cycleColor,
computeColor)
where
import Data.Bits
import Data.Colour
import Data.Colour.SRGB
import Data.Colour.Names
import qualified Data.ByteString.Char8 as S
import qualified Data.Map as M
data (Show b, Eq b, Ord b, Floating b) => ColorMap b = ColorMap {
colorMap :: M.Map S.ByteString (RGB b), -- ^ Current map from arbitrary strings to color descriptions
colorWheel :: [RGB b] -- ^ Next colors for assigning to as yet unknown names
} deriving (Eq, Show)
-- | Starts with empty names-colors map and mid-range grey
defaultColorMap = ColorMap M.empty defaultColorWheel
defaultColorWheel = map toSRGB [green, blue, red, brown, orange, magenta, grey, purple, violet, lightblue, crimson, burlywood] ++ map nextColor defaultColorWheel
-- | Compute color for a given name within the associated map.
-- This function encapsulates the following rules:
--
-- * If @color@ is a 6-digit hexadecimal value of the form '#FA34B7' then this is used as aan immediate
-- RGB color,
--
-- * If @color@ is a color name from SVG1.1 specification (http://www.w3.org/TR/SVG11/types.html#ColorKeywords)
-- then the corresponding color is returned,
--
-- * Otherwise, the @color@ name is looked up in the @map@ and if it is not found, a new color is generated
-- using a simple cycling function.
computeColor map color = case readColor color of
Nothing -> cycleColor map color
Just c -> (c, map)
readColor = readColor' . S.unpack
readColor' ('#':r1:r2:g1:g2:b1:b2:[]) = Just (RGB r g b)
where
r = fromIntegral $ unhex r2 + 16*unhex r1
g = fromIntegral $ unhex g2 + 16*unhex g1
b = fromIntegral $ unhex b2 + 16*unhex b1
unhex c | c >= '0' && c <= '9' = fromEnum c - fromEnum '0'
| c >= 'a' && c <= 'z' = fromEnum c - fromEnum 'a'
| c >= 'A' && c <= 'Z' = 10 + fromEnum c - fromEnum 'A'
readColor' cs = toSRGB `fmap` readColourName cs
-- | Compute the color associated to a given name, providing an updated map with
-- possibly new colors in the cycle.
cycleColor :: (RealFrac b, Show b, Eq b, Ord b, Floating b) =>
ColorMap b
-> S.ByteString
-> (RGB b,ColorMap b)
cycleColor map name = case M.lookup name (colorMap map) of
Just c -> (c, map)
Nothing -> (next, augment map (name,next) wheel')
where
(next:wheel') = colorWheel map
nextColor :: (RealFrac b, Floating b) =>
RGB b ->
RGB b
nextColor (RGB r g b) = RGB (r+7) (g+17) (b+23)
augment :: (Show b, Eq b, Ord b, Floating b) =>
ColorMap b ->
(S.ByteString, RGB b) ->
[RGB b] ->
ColorMap b
augment map (name,col) wheel = ColorMap (M.insert name col (colorMap map)) wheel