Difference between revisions of "Colour"
(Manipulating colours) 
(black is now exported from Data.Colour) 

(23 intermediate revisions by 3 users not shown)  
Line 1:  Line 1:  
+  [[Category:Libraries]] 

+  
This page provides a short introduction to using the [http://hackage.haskell.org/package/colour colour package] on hackage. 
This page provides a short introduction to using the [http://hackage.haskell.org/package/colour colour package] on hackage. 

Line 11:  Line 13:  
You may wish to make a type synonym for this type in your program if you will use it everywhere. 
You may wish to make a type synonym for this type in your program if you will use it everywhere. 

−  You can always use 
+  You can always use <hask>colourConvert</hask> to change to a different internal representation type. 
== Creating colours == 
== Creating colours == 

Line 25:  Line 27:  
will create the colour with those [http://en.wikipedia.org/wiki/SRGB sRGB] colour coordinates. 
will create the colour with those [http://en.wikipedia.org/wiki/SRGB sRGB] colour coordinates. 

−  If you have three <hask>Double</hask>s named <hask>red</hask>, <hask>green</hask>, and <hask>blue</hask>, then 
+  If you have three <hask>Double</hask>s (or whatever you are using for your internal representation) named <hask>red</hask>, <hask>green</hask>, and <hask>blue</hask>, then 
<haskell> 
<haskell> 

Line 31:  Line 33:  
</haskell> 
</haskell> 

−  will produce the colour with those colour coordinates. These <hask>Double</hask> should be in the range [0,1] otherwise the resulting colour would be out of gamut (a colour gamut is a collection of representable colours on a device, such as your monitor). 
+  will produce the colour with those colour coordinates. These <hask>Double</hask> should be in the range [0,1] otherwise the resulting colour would be out of gamut (a [http://en.wikipedia.org/wiki/Gamut colour gamut] is a collection of representable colours on a device, such as your monitor). 
Lastly, <hask>sRGB24read</hask> and <hask>sRGB24reads</hask> can create colour from string specifications of the form <hask>"#00aaff"</hask> or <hask>"00aaff"</hask>. 
Lastly, <hask>sRGB24read</hask> and <hask>sRGB24reads</hask> can create colour from string specifications of the form <hask>"#00aaff"</hask> or <hask>"00aaff"</hask>. 

Line 43:  Line 45:  
<haskell> 
<haskell> 

blend 0.25 red green 
blend 0.25 red green 

−  <haskell> 
+  </haskell> 
−  will create a new colour that is 25% red, and 75% green. The weight parameter (the first parameter) should be between 0 and 1, otherwise an out 
+  will create a new colour that is 25% red, and 75% green. The weight parameter (the first parameter) should be between 0 and 1, otherwise an outofgamut colour could result. 
If you need to blend more than two colours, you can use multiple applications of <hask>blend</hask>, or you can use <hask>affineCombo</hask>. For example, 
If you need to blend more than two colours, you can use multiple applications of <hask>blend</hask>, or you can use <hask>affineCombo</hask>. For example, 

Line 51:  Line 53:  
<haskell> 
<haskell> 

affineCombo [(0.25,red),(0.5,green)] violet 
affineCombo [(0.25,red),(0.5,green)] violet 

−  <haskell> 
+  </haskell> 
−  will create a new colour that is 25% red, 50% green, and 25% violet. Again the weights should all be nonnegative and the sum of the weights should be no more than 1, otherwise an out 
+  will create a new colour that is 25% red, 50% green, and 25% violet. Again the weights should all be nonnegative and the sum of the weights should be no more than 1, otherwise an outofgamut colour could result. 
−  +  Colour intensity can be changed by using <hask>darken</hask>. For example, 

<haskell> 
<haskell> 

darken 0.4 turquoise 
darken 0.4 turquoise 

−  <haskell> 
+  </haskell> 
will produce a turquoise that is only 40% of the intensity of normal turquoise. 
will produce a turquoise that is only 40% of the intensity of normal turquoise. 

−  The weight parameter (the first parameter) should be between 0 and 1, otherwise an out 
+  The weight parameter (the first parameter) should be between 0 and 1, otherwise an outofgamut colour could result. However if you know that the intensity is low enough, you may safely "darken" by values greater than 1 (which will actually lighten the colour). 
+  
+  Lastly, colours are instance of a [[Monoid]] so colours can be "added" by using <hask>mappend</hask> (and <hask>mempty</hask> is a quick way to get black). However, like spotlights, adding colours makes more intense colours. Adding colours could take you out of gamut by making colours too intense. Unless you specifically know you want to be adding colours (for example, when writing a raytracer), you probably want to be using <hask>blend</hask> instead. 

+  
+  == Getting colour coordinates out == 

+  
+  To retrieve the [http://en.wikipedia.org/wiki/SRGB sRGB] coordinates of a colour, use the functions found in the <hask>Data.Colour.SRGB</hask> module. To get coordinates as <hask>Double</hask>s (or whatever your internal representation is) use <hask>toSRGB</hask>. For example 

+  
+  <haskell> 

+  toSRGB chartreuse 

+  </haskell> 

+  
+  will produce a value of type <hask>RGB Double</hask>. 

+  
+  === RGB triples === 

+  
+  The type <hask>RGB</hask> is special type of (strict) triple used to store colour coordinates. The functions <hask>channelRed</hask>, <hask>channelGreen</hask>, and <hask>channelBlue</hask> can be used to access the three fields. The constructor <hask>RGB</hask> will create such a triple. For example, 

+  
+  <haskell> 

+  RGB 0.5 0.4 0.6 

+  </haskell> 

+  
+  You might find the functions 

+  
+  <haskell> 

+  curryRGB :: (RGB a > b) > a > a > a > b 

+  uncurryRGB :: (a > a > a > b) > RGB a > b 

+  </haskell> 

+  
+  useful when working with functions that operate on RGB triples. These functions can be found in <hask>Data.Colour.RGBSpace</hask>. 

+  
+  === Back to colour coordinates === 

+  
+  Recall that 

+  
+  <haskell> 

+  toSRGB chartreuse 

+  </haskell> 

+  
+  produces an <hask>RGB Double</hask>. The coordinates output by <hask>toSRGB</hask> will all be between 0 and 1 unless the colour is out of gamut. 

+  
+  If you want to retrieve the colour coordinates as <hask>Word8</hask>s, use <hask>toSRGB24</hask> 

+  
+  <haskell> 

+  toSRGB24 khaki 

+  </haskell> 

+  
+  will produce an <hask>RGB Word8</hask>. Outofgamut channels will be clamped to the range 0 to 255. 

+  
+  Lastly, the functions <hask>sRGB24show</hask> and <hask>sRGB24shows</hask> will produce colour strings of the form <hask>"#00aaff"</hask>. 

+  
+  == Transparent Colour == 

+  
+  Colours that are semi transparent are represented by the <hask>AlphaColour a</hask> type found in <hask>Data.Colour</hask>. Again the <hask>a</hask> type parameter represents the data type used for the internal representation and would typically be <hask>Double</hask>. You can use <hask>alphaColourConvert</hask> to change the internal representation type of a semitransparent colour. 

+  
+  Opaque <hask>AlphaColour</hask>s are created from <hask>Colour</hask>s using <hask>opaque</hask>. For example. 

+  
+  <haskell> 

+  opaque goldenrod 

+  </haskell> 

+  
+  creates an opaque goldenrod. Semi transparent colours can be made using <hask>withOpacity</hask> 

+  
+  <haskell> 

+  moccasin `withOpacity` 0.7 

+  </haskell> 

+  
+  creates a colour that is 70% opaque and hence 30% transparent. 

+  
+  The value <hask>transparent</hask> is 100% transparent and <hask>transparent == anyColour `withOpacity` 0</hask>. 

+  
+  Like regular colours, semitransparent colours can be blended using <hask>blend</hask> and <hask>affineCombo</hask>. The function <hask>darken</hask> will darken a semitransparent colour without affecting its opacity. 

+  
+  To make an existing semitransparent colour more transparent use <hask>dissolve</hask>. For example, 

+  
+  <haskell> 

+  dissolve 0.6 ac 

+  </haskell> 

+  
+  will return a semitransparent colour that is 60% of the opacity of <hask>ac</hask>. 

+  
+  One should avoid dissolving with weights (the first parameter) greater than 1, as you may create invalid "superopaque" colours. If you know the opacity is less than <hask>x</hask> then you can safely use weights no more than <hask>(recip x)</hask>. Negative weights will also produce invalid "supertransparent" colours. 

+  
+  <haskell> 

+  anyColour `withOpacity` opacity == disolve opacity (opaque anyColour) 

+  </haskell> 

+  
+  Lastly, the key operation on transparent colours is compositing. Given two semitransparent colours <hask>acTop</hask> and <hask>acBottom</hask> 

+  
+  <haskell> 

+  acTop `over` acBottom 

+  </haskell> 

+  
+  will produce the semitransparent colour resulting from acTop being composited over top of <hask>acBottom</hask>. The bottom layer, <hask>acBottom</hask> can be a nontransparent colour (of type <hask>Colour</hask>). In this case the result will also be a nontransparent colour. However, the top layer must be of semitransparent type (although it could, of course, be opaque). 

+  
+  Compositing is such important operation on semitransparent colours, that it is the [[Monoid]] instance for <hask>AlphaColour a</hask>. The function <hask>mappend</hask> is <hask>over</hask>, and <hask>mempty</hask> is <hask>transparent</hask>. 

+  
+  === Getting semitransparent coordinates === 

+  
+  The opacity of a semitransparent colour can be retrieved by the <hask>alphaChannel</hask> function. 

+  
+  The pure colour of a semitransparent colour <hask>ac</hask> can be retrieved by first compositing the colour over black, then darkening by the reciprocal of the alpha channel. 

+  
+  <haskell> 

+  pureColour :: AlphaColour a > Colour a 

+  pureColour ac  a > 0 = darken (recip a) (ac `over` black) 

+   otherwise = error "transparent has no pure colour" 

+  where 

+  a = alphaChannel ac 

+  </haskell> 

+  
+  Note however, that transparent has no pure colour, and this case needs to be handled specially. 

−  Lastly, colours are instance of a <hask>Monoid</hask> so colours can be "added" by using <hask>mappend</hask> (and <hask>mempty</hask> is a quick way to get black). However, like spotlights, adding colours makes more intense colours. Adding colours could take you out of gamut. Unless you specifically know you want to be adding colours, you probably want to be using <hask>blend</hask> instead.</haskell> 

+  This operation is not natively provided because it is an operation that should be avoided. It is only really useful for interfacing with libraries that require pure colour components. Ideally it would be these libraries that implement conversion to and from <hask>Colour</hask>. However, you may find it necessary to implement the conversion functions yourself, in which case you can use the above "trick" to write the conversion function. 
Latest revision as of 19:35, 29 August 2009
This page provides a short introduction to using the colour package on hackage.
Contents
The Colour data type
The Colour a
data type and its basic operations are found in the Data.Colour
module. The type variable a
is used to specify the numeric type used for the internal representation of the data. Typically one will use:
Colour Double
You may wish to make a type synonym for this type in your program if you will use it everywhere.
You can always use colourConvert
to change to a different internal representation type.
Creating colours
A collections of colours given by name can be found in the Data.Colour.Names
module. There is also a readColourName
to convert a string with one of these names into a colour. Be aware that the colour tan
will conflict with the Prelude function unless you hide the Prelude function or import the module qualified.
Another way to make a colour is by specifying an RGB triple. These functions can be found in the Data.Colour.SRGB
library. For example, if you have three Word8
s named red
, green
, and blue
, then
sRGB24 red green blue
will create the colour with those sRGB colour coordinates.
If you have three Double
s (or whatever you are using for your internal representation) named red
, green
, and blue
, then
sRGB red green blue
will produce the colour with those colour coordinates. These Double
should be in the range [0,1] otherwise the resulting colour would be out of gamut (a colour gamut is a collection of representable colours on a device, such as your monitor).
Lastly, sRGB24read
and sRGB24reads
can create colour from string specifications of the form "#00aaff"
or "00aaff"
.
Manipulating Colours
The colour operations are found in the Data.Colour
module.
The most common operation on colours is blend
. For example,
the function
blend 0.25 red green
will create a new colour that is 25% red, and 75% green. The weight parameter (the first parameter) should be between 0 and 1, otherwise an outofgamut colour could result.
If you need to blend more than two colours, you can use multiple applications of blend
, or you can use affineCombo
. For example,
affineCombo [(0.25,red),(0.5,green)] violet
will create a new colour that is 25% red, 50% green, and 25% violet. Again the weights should all be nonnegative and the sum of the weights should be no more than 1, otherwise an outofgamut colour could result.
Colour intensity can be changed by using darken
. For example,
darken 0.4 turquoise
will produce a turquoise that is only 40% of the intensity of normal turquoise. The weight parameter (the first parameter) should be between 0 and 1, otherwise an outofgamut colour could result. However if you know that the intensity is low enough, you may safely "darken" by values greater than 1 (which will actually lighten the colour).
Lastly, colours are instance of a Monoid so colours can be "added" by using mappend
(and mempty
is a quick way to get black). However, like spotlights, adding colours makes more intense colours. Adding colours could take you out of gamut by making colours too intense. Unless you specifically know you want to be adding colours (for example, when writing a raytracer), you probably want to be using blend
instead.
Getting colour coordinates out
To retrieve the sRGB coordinates of a colour, use the functions found in the Data.Colour.SRGB
module. To get coordinates as Double
s (or whatever your internal representation is) use toSRGB
. For example
toSRGB chartreuse
will produce a value of type RGB Double
.
RGB triples
The type RGB
is special type of (strict) triple used to store colour coordinates. The functions channelRed
, channelGreen
, and channelBlue
can be used to access the three fields. The constructor RGB
will create such a triple. For example,
RGB 0.5 0.4 0.6
You might find the functions
curryRGB :: (RGB a > b) > a > a > a > b
uncurryRGB :: (a > a > a > b) > RGB a > b
useful when working with functions that operate on RGB triples. These functions can be found in Data.Colour.RGBSpace
.
Back to colour coordinates
Recall that
toSRGB chartreuse
produces an RGB Double
. The coordinates output by toSRGB
will all be between 0 and 1 unless the colour is out of gamut.
If you want to retrieve the colour coordinates as Word8
s, use toSRGB24
toSRGB24 khaki
will produce an RGB Word8
. Outofgamut channels will be clamped to the range 0 to 255.
Lastly, the functions sRGB24show
and sRGB24shows
will produce colour strings of the form "#00aaff"
.
Transparent Colour
Colours that are semi transparent are represented by the AlphaColour a
type found in Data.Colour
. Again the a
type parameter represents the data type used for the internal representation and would typically be Double
. You can use alphaColourConvert
to change the internal representation type of a semitransparent colour.
Opaque AlphaColour
s are created from Colour
s using opaque
. For example.
opaque goldenrod
creates an opaque goldenrod. Semi transparent colours can be made using withOpacity
moccasin `withOpacity` 0.7
creates a colour that is 70% opaque and hence 30% transparent.
The value transparent
is 100% transparent and transparent == anyColour `withOpacity` 0
.
Like regular colours, semitransparent colours can be blended using blend
and affineCombo
. The function darken
will darken a semitransparent colour without affecting its opacity.
To make an existing semitransparent colour more transparent use dissolve
. For example,
dissolve 0.6 ac
will return a semitransparent colour that is 60% of the opacity of ac
.
One should avoid dissolving with weights (the first parameter) greater than 1, as you may create invalid "superopaque" colours. If you know the opacity is less than x
then you can safely use weights no more than (recip x)
. Negative weights will also produce invalid "supertransparent" colours.
anyColour `withOpacity` opacity == disolve opacity (opaque anyColour)
Lastly, the key operation on transparent colours is compositing. Given two semitransparent colours acTop
and acBottom
acTop `over` acBottom
will produce the semitransparent colour resulting from acTop being composited over top of acBottom
. The bottom layer, acBottom
can be a nontransparent colour (of type Colour
). In this case the result will also be a nontransparent colour. However, the top layer must be of semitransparent type (although it could, of course, be opaque).
Compositing is such important operation on semitransparent colours, that it is the Monoid instance for AlphaColour a
. The function mappend
is over
, and mempty
is transparent
.
Getting semitransparent coordinates
The opacity of a semitransparent colour can be retrieved by the alphaChannel
function.
The pure colour of a semitransparent colour ac
can be retrieved by first compositing the colour over black, then darkening by the reciprocal of the alpha channel.
pureColour :: AlphaColour a > Colour a
pureColour ac  a > 0 = darken (recip a) (ac `over` black)
 otherwise = error "transparent has no pure colour"
where
a = alphaChannel ac
Note however, that transparent has no pure colour, and this case needs to be handled specially.
This operation is not natively provided because it is an operation that should be avoided. It is only really useful for interfacing with libraries that require pure colour components. Ideally it would be these libraries that implement conversion to and from Colour
. However, you may find it necessary to implement the conversion functions yourself, in which case you can use the above "trick" to write the conversion function.