-- | This module provides a basic color data type that can be used to build apps without needing to -- include additional packages into your project. This module is not imported automatically when you -- import the "Happlets" module, so if you would prefer to use a better alternative color data type -- it is easier to do so. This module can be imported with the "Happlets.Draw" module. module Happlets.Draw.Color ( PackedRGBA32(..), FillColor, LineColor, unpackRGBA32Color, packRGBA32Color, packRGBA32, unpackRGBA32, quantizeColorChannel, unquantizeColorChannel, -- * British Spellings FillColour, LineColour, unpackRGBA32Colour, packRGBA32Colour, quantizeColourChannel, unquantizeColourChannel, -- * Fundamental Colors and Shades, red, green, blue, cyan, magenta, yellow, white, black, grey, gray, light, dark, -- * Color Channels redChannel, greenChannel, blueChannel, alphaChannel, ) where import Control.Arrow import Control.Lens import Data.Bits import Data.Char import Data.Word import Numeric ---------------------------------------------------------------------------------------------------- -- | For using a type name that describes how the 'PackedRGBA32' value is being used for. type FillColor = PackedRGBA32 -- | For using a type name that describes how the 'PackedRGBA32' value is being used for. type LineColor = PackedRGBA32 newtype PackedRGBA32 = PackedRGBA32{ unwrapPackedRGBA32 :: Word32 } deriving Eq instance Show PackedRGBA32 where showsPrec _ (PackedRGBA32 w) = (++) ( case w of w | w <= 0xF -> "#0000000" w | w <= 0xFF -> "#000000" w | w <= 0xFFF -> "#00000" w | w <= 0xFFFF -> "#0000" w | w <= 0xFFFFF -> "#000" w | w <= 0xFFFFFF -> "#00" w | w <= 0xFFFFFFF -> "#0" _ -> "#" ) . showHex w instance Read PackedRGBA32 where readsPrec _ str = case str of '#' : str -> do (a, str) <- readHex str let done str = (PackedRGBA32 a, str) case str of "" -> [done ""] s:str | isSpace s -> [done $ dropWhile isSpace str] _ -> [] _ -> [] -- | This function evaluates 'unpackRGBA32' and then performs a 'unquantizeColorChannel' -- computation on each channel, including the alpha channel. unpackRGBA32Color :: PackedRGBA32 -> (Double, Double, Double, Double) unpackRGBA32Color p = let (r,g,b,a) = unpackRGBA32 p un = unquantizeColorChannel in (un r, un g, un b, un a) -- | This function evaluates 'qunatizeColorChannel' on each given 'Prelude.Double'-precision color -- channel value, then evaluates 'packRGBA32' on the four resulting quantized values. packRGBA32Color :: Double -> Double -> Double -> Double -> PackedRGBA32 packRGBA32Color r g b a = packRGBA32 (q r) (q g) (q b) (q a) where q = quantizeColorChannel -- | This function performs no transformation on the given 'Data.Word.Word8' values, they are simply -- bit-packed into a 'Data.Word.Word32' data value by bit-shifting and the bitwise-OR operator. packRGBA32 :: Word8 -> Word8 -> Word8 -> Word8 -> PackedRGBA32 packRGBA32 r g b a = PackedRGBA32 $! sh r 24 .|. sh g 16 .|. sh b 8 .|. sh a 0 where sh c s = shift (fromIntegral c) s -- | This is the inverse operation of 'packRGBA32'. unpackRGBA32 :: PackedRGBA32 -> (Word8, Word8, Word8, Word8) unpackRGBA32 (PackedRGBA32 w) = (unsh 24, unsh 16, unsh 8, unsh 0) where unsh s = fromIntegral $! 0x000000FF .&. shift w (negate s) -- | This function takes a linear color value expressed as a 'Prelude-Double'-precision -- floating-point value, clamps it to a value between 0 and 1, then computes the square-root on this -- value, then compresses the square-root to a 'Data.Word.Word8' value (because the human eye is -- more sensitive to changes in brigher colors than in darker colors). Use this function to convert -- a linear double-precision color channel intensity value to a value that can be stored efficiently -- in memory or some other digital computing medium. quantizeColorChannel :: Double -> Word8 quantizeColorChannel = round . (* (realToFrac (maxBound::Word8) :: Double)) . sqrt . min 1.0 . max 0.0 -- | This is the inverse operation of 'quantizeColorChannel', the 'Data.Word.Word8' value is -- converted to a 'Prelude.Double'-precision floating point value between the numbers 0 and 1, then -- returns the square of this value. unquantizeColorChannel :: Word8 -> Double unquantizeColorChannel = (** 2.0) . (/ (realToFrac (maxBound :: Word8) :: Double)) . realToFrac ---------------------------------------------------------------------------------------------------- -- | British spelling of 'FillColor' type FillColour = FillColor -- | British spelling of 'LineColor' type LineColour = LineColor -- | British spelling of 'unpackRGBA32Color' unpackRGBA32Colour :: PackedRGBA32 -> (Double, Double, Double, Double) unpackRGBA32Colour = unpackRGBA32Color -- | British spelling of 'packRGBA32Color' packRGBA32Colour :: Double -> Double -> Double -> Double -> PackedRGBA32 packRGBA32Colour = packRGBA32Color -- | British spelling of 'quantizeColorChannel' quantizeColourChannel :: Double -> Word8 quantizeColourChannel = quantizeColorChannel -- | British spelling of 'unquantizeColorChannel' unquantizeColourChannel :: Word8 -> Double unquantizeColourChannel = unquantizeColorChannel ---------------------------------------------------------------------------------------------------- -- | red :: PackedRGBA32 red = PackedRGBA32 0xFF0000FF -- | green :: PackedRGBA32 green = PackedRGBA32 0x00FF00FF -- | blue :: PackedRGBA32 blue = PackedRGBA32 0x0000FFFF -- | cyan :: PackedRGBA32 cyan = PackedRGBA32 0x00FFFFFF -- | magenta :: PackedRGBA32 magenta = PackedRGBA32 0xFF00FFFF -- | yellow :: PackedRGBA32 yellow = PackedRGBA32 0xFFFF00FF -- | white :: PackedRGBA32 white = PackedRGBA32 0xFFFFFFFF -- | black :: PackedRGBA32 black = PackedRGBA32 0x000000FF -- | grey :: PackedRGBA32 grey = PackedRGBA32 0x808080FF -- | gray :: PackedRGBA32 gray = grey ---------------------------------------------------------------------------------------------------- bitShiftColorChannelLens :: Int -> Lens' PackedRGBA32 Double bitShiftColorChannelLens i = lens (\ (PackedRGBA32 c) -> unquantizeColorChannel $ fromIntegral $ shift (shift 0xFF i .&. c) (negate i) ) (\ (PackedRGBA32 c) a -> PackedRGBA32 $ (c .&. xor 0xFFFFFFFF (shift 0xFF i)) .|. shift (fromIntegral $ quantizeColorChannel a) i ) -- | A lens to change the linear (unquantized) alpha channel value without needing to unpack then -- repack the 'PackedRGBA32' value. alphaChannel :: Lens' PackedRGBA32 Double alphaChannel = bitShiftColorChannelLens 0 -- | A lens to change the linear (unquantized) red channel value without needing to unpack then -- repack the 'PackedRGBA32' value. redChannel :: Lens' PackedRGBA32 Double redChannel = bitShiftColorChannelLens 24 -- | A lens to change the linear (unquantized) green channel value without needing to unpack then -- repack the 'PackedRGBA32' value. greenChannel :: Lens' PackedRGBA32 Double greenChannel = bitShiftColorChannelLens 16 -- | A lens to change the linear (unquantized) blue channel value without needing to unpack then -- repack the 'PackedRGBA32' value. blueChannel :: Lens' PackedRGBA32 Double blueChannel = bitShiftColorChannelLens 8 -- | Shift the color toward white. For example @('light' 'red')@ may look like a "pink" -- color. Please be aware that this is not necessarily the inverse function of 'dark' due to loss of -- precision when packing the color channels. light :: Double -> PackedRGBA32 -> PackedRGBA32 light = min 1.0 . max 0.0 >>> \ p -> unpackRGBA32Color >>> \ (r,g,b,a) -> packRGBA32Color (p * (1 - r) + r) (p * (1 - g) + g) (p * (1 - b) + b) a -- | Shift the color toward black. For example @('dark' 'blue')@ may look like a "navy" color. -- Please be aware that this is not necessarily the inverse function of 'light' due to loss of -- precision when packing the color channels. dark :: Double -> PackedRGBA32 -> PackedRGBA32 dark = min 1.0 . max 0.0 >>> \ p -> unpackRGBA32Color >>> \ (r,g,b,a) -> packRGBA32Color (p * r) (p * g) (p * b) a