http://mathling.com/colour/tonemap library module
http://mathling.com/colour/tonemap
Tonemapping
References:
https://64.github.io/tonemapping/
https://bartwronski.com/2016/09/01/dynamic-range-and-evs/
https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
There are two parallel APIs here: the higher level one takes XYZ points
and produces XYZ points, and the lower level one takes and produces
raw XYZ coordinates. You could use raw RGB coordinates instead, but
but the results aren't as nice (same as when you try to do interpolations
over raw RGB). If you are going to perform a chain of operations, the
lower-level API will be more efficient (less conversion/construction
between steps).
You will need to call saturate() or saturatev() before you convert back
to RGB or risk getting illegal RGB values.
Copyright© Mary Holstege 2021-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Stable
Imports
http://mathling.com/colour/spaceimport module namespace cs="http://mathling.com/colour/space" at "../colourspace/colour-space.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/core/vector
import module namespace v="http://mathling.com/core/vector" at "../core/vector.xqy"http://mathling.com/colour/xyz
import module namespace xyz="http://mathling.com/colour/xyz" at "../colourspace/xyz.xqy"
Variables
Variable: $HABLIC-DEFAULTS as xs:double*
Default Hablic constants
A=shoulder strength, B=linear strength, C=linear angle,
D=toe strength, E=toe numerator, F=toe denominator, E/F=toe angle
Functions
Function: ACES
declare function ACES($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function ACES($colour as map(xs:string,item()*)) as map(xs:string,item()*)
ACES()
Perform ACES (Academy Color Encoding System) filmic tone mapping
Params
- colour as map(xs:string,item()*): XYZ colour
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:ACES( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:ACESv($colour=>xyz:raw-coordinates())=>xyz:to-xyz() }
Function: ACESv
declare function ACESv($colour as xs:double*) as xs:double*
declare function ACESv($colour as xs:double*) as xs:double*
ACESv()
Perform ACES (Academy Color Encoding System) filmic tone mapping
Params
- colour as xs:double*: XYZ input colour as raw coordinates
Returns
- xs:double*: remapped XYZ colour vector
declare function this:ACESv( $colour as xs:double* ) as xs:double* { (: Multiply input matrix by input colour :) let $v := ( v:dot((0.59719E0, 0.35458E0, 0.04823E0), $colour), v:dot((0.07600E0, 0.90834E0, 0.01566E0), $colour), v:dot((0.02840E0, 0.13383E0, 0.84777E0), $colour) ) (: Curve fitting :) let $a := (: v * (v + 0.0245786) - 0.000090537 :) $v=> v:multiply($v=>v:plus(0.0245786E0))=> v:plus(-0.000090537E0) let $b := (: v * (0.983729 * v + 0.4329510) + 0.238081 :) $v=> v:multiply(($v=>v:times(0.983729E0))=>v:plus(0.4329510E0))=> v:plus(0.238081E0) let $ab := $a=>v:divide($b) (: Multiply by output matrix to get output colour :) let $m2ab := ( (1.60475E0, -0.53108E0, -0.07367E0)=>v:dot($ab), (-0.10208E0, 1.10813E0, -0.00605E0)=>v:dot($ab), (-0.00327E0, -0.07276E0, 1.07602E0)=>v:dot($ab) ) return ( $m2ab ) }
Function: Hablic
declare function Hablic($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function Hablic($colour as map(xs:string,item()*)) as map(xs:string,item()*)
Hablic()
Hablic tone mapping with default values.
Params
- colour as map(xs:string,item()*): XYZ point
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:Hablic( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:Hablic($colour, 2.0E0, 11.2E0, $this:HABLIC-DEFAULTS) }
Function: Hablicv
declare function Hablicv($colour as xs:double*) as xs:double*
declare function Hablicv($colour as xs:double*) as xs:double*
Hablicv()
Hablic tone mapping with default values.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
Returns
- xs:double*: remapped XYZ colour vector
declare function this:Hablicv($colour as xs:double*) as xs:double* { this:Hablicv($colour, 2.0E0, 11.2E0, $this:HABLIC-DEFAULTS) }
Function: Hablic
declare function Hablic($colour as map(xs:string,item()*),
$exposure as xs:double) as map(xs:string,item()*)
declare function Hablic($colour as map(xs:string,item()*), $exposure as xs:double) as map(xs:string,item()*)
Hablic()
Hablic tone mapping with default values except for exposure.
Params
- colour as map(xs:string,item()*): XYZ input colour as raw coordinates
- exposure as xs:double: amount of exposure
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double ) as map(xs:string,item()*) { this:Hablic($colour, $exposure, 11.2E0, $this:HABLIC-DEFAULTS) }
Function: Hablicv
declare function Hablicv($colour as xs:double*,
$exposure as xs:double) as xs:double*
declare function Hablicv($colour as xs:double*, $exposure as xs:double) as xs:double*
Hablicv()
Hablic tone mapping with default values except for exposure.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- exposure as xs:double: amount of exposure
Returns
- xs:double*: remapped XYZ colour vector
declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double ) as xs:double* { this:Hablicv($colour, $exposure, 11.2E0, $this:HABLIC-DEFAULTS) }
Function: Hablic
declare function Hablic($colour as map(xs:string,item()*),
$exposure as xs:double,
$white as xs:double) as map(xs:string,item()*)
declare function Hablic($colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double) as map(xs:string,item()*)
Hablic()
Hablic tone mapping with default curve constants.
Params
- colour as map(xs:string,item()*): XYZ colour point
- exposure as xs:double: amount of exposure
- white as xs:double: white point
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double ) as map(xs:string,item()*) { this:Hablic($colour, $exposure, $white, $this:HABLIC-DEFAULTS) }
Function: Hablicv
declare function Hablicv($colour as xs:double*,
$exposure as xs:double,
$white as xs:double) as xs:double*
declare function Hablicv($colour as xs:double*, $exposure as xs:double, $white as xs:double) as xs:double*
Hablicv()
Hablic tone mapping with default curve constants.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- exposure as xs:double: amount of exposure
- white as xs:double: white point
Returns
- xs:double*: remapped XYZ colour vector
declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double, $white as xs:double ) as xs:double* { this:Hablicv($colour, $exposure, $white, $this:HABLIC-DEFAULTS) }
Function: Hablic
declare function Hablic($colour as map(xs:string,item()*),
$exposure as xs:double,
$white as xs:double,
$curve as xs:double*) as map(xs:string,item()*)
declare function Hablic($colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double, $curve as xs:double*) as map(xs:string,item()*)
Hablic()
Hablic tone mapping with specific parameters.
Params
- colour as map(xs:string,item()*): XYZ colour point
- exposure as xs:double: amount of exposure
- white as xs:double: white point
- curve as xs:double*: Hablic constants in order (A, B, C, D, E, F)
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double, $curve as xs:double* ) as map(xs:string,item()*) { xyz:to-xyz( this:Hablic-partial(xyz:raw-coordinates($colour), $curve)=> v:divide(this:Hablic-partial($white, $curve)) ) }
Function: Hablicv
declare function Hablicv($colour as xs:double*,
$exposure as xs:double,
$white as xs:double,
$curve as xs:double*) as xs:double*
declare function Hablicv($colour as xs:double*, $exposure as xs:double, $white as xs:double, $curve as xs:double*) as xs:double*
Hablicv()
Hablic tone mapping with specific parameters.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- exposure as xs:double: amount of exposure
- white as xs:double: white point
- curve as xs:double*: Hablic constants in order (A, B, C, D, E, F)
Returns
- xs:double*: remapped XYZ colour vector
declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double, $white as xs:double, $curve as xs:double* ) as xs:double* { this:Hablic-partial($colour, $curve)=> v:divide(this:Hablic-partial($white, $curve)) }
Function: simple-Reinhard
declare function simple-Reinhard($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function simple-Reinhard($colour as map(xs:string,item()*)) as map(xs:string,item()*)
simple-Reinhard()
Basic Reinhard tonemapping: tends to grey out colours
Params
- colour as map(xs:string,item()*): XYZ colour point
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:simple-Reinhard( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { xyz:to-xyz(this:simple-Reinhardv(xyz:raw-coordinates($colour))) }
Function: simple-Reinhardv
declare function simple-Reinhardv($colour as xs:double) as xs:double*
declare function simple-Reinhardv($colour as xs:double) as xs:double*
simple-Reinhardv()
Basic Reinhard tonemapping: tends to grey out colours
Params
- colour as xs:double: XYZ input colour as raw coordinates
Returns
- xs:double*: remapped XYZ colour vector
declare function this:simple-Reinhardv( $colour as xs:double ) as xs:double* { $colour=>v:divide($colour=>v:plus(1E0)) }
Function: Reinhard
declare function Reinhard($colour as map(xs:string,item()*),
$white as xs:double) as map(xs:string,item()*)
declare function Reinhard($colour as map(xs:string,item()*), $white as xs:double) as map(xs:string,item()*)
Reinhard()
(Extended) Reinhard tonemapping with luminance
Params
- colour as map(xs:string,item()*): XYZ colour point
- white as xs:double: white point
Returns
- map(xs:string,item()*): remapped XYZ colour point
declare function this:Reinhard( $colour as map(xs:string,item()*), $white as xs:double ) as map(xs:string,item()*) { xyz:to-xyz(this:Reinhardv(xyz:raw-coordinates($colour), $white)) }
Function: Reinhardv
declare function Reinhardv($colour as xs:double*,
$white as xs:double) as xs:double*
declare function Reinhardv($colour as xs:double*, $white as xs:double) as xs:double*
Reinhardv()
(Extended) Reinhard tonemapping with luminance
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- white as xs:double: white point
Returns
- xs:double*: remapped XYZ colour vector
declare function this:Reinhardv( $colour as xs:double*, $white as xs:double ) as xs:double* { let $l-old := this:luminancev($colour) let $numerator := $l-old * (1E0 + ($l-old div ($white*$white))) let $l-new := $numerator div (1E0 + $l-old) return this:luminatev($colour, $l-new) }
Function: luminance
declare function luminance($colour as map(xs:string,item()*)) as xs:double
declare function luminance($colour as map(xs:string,item()*)) as xs:double
luminance()
Get luminance of an XYZ value.
Params
- colour as map(xs:string,item()*): XYZ colour point
Returns
- xs:double: luminance of the colour
declare function this:luminance( $colour as map(xs:string,item()*) ) as xs:double { xyz:raw-coordinates($colour)=>v:dot((0.2125E0, 0.7152E0, 0.0722E0)) }
Function: luminancev
declare function luminancev($colour as xs:double*) as xs:double
declare function luminancev($colour as xs:double*) as xs:double
luminance()
Get luminance of an XYZ value.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
Returns
- xs:double: luminance of the colour
declare function this:luminancev($colour as xs:double*) as xs:double { let $res := $colour=>v:dot((0.2125E0, 0.7152E0, 0.0722E0)) return ( if (some $c in $res satisfies $c != $c) then ( trace((), "luninance XYZ=>NaN: "||util:quote($colour)) ) else (), $res ) }
Function: luminate
declare function luminate($colour as map(xs:string,item()*),
$luminance as xs:double) as map(xs:string,item()*)
declare function luminate($colour as map(xs:string,item()*), $luminance as xs:double) as map(xs:string,item()*)
luminate()
Change the luminance to the new luminance.
Params
- colour as map(xs:string,item()*): XYZ input colour as raw coordinates
- luminance as xs:double: Output luminance
Returns
- map(xs:string,item()*): luminated XYZ colour point
declare function this:luminate( $colour as map(xs:string,item()*), $luminance as xs:double ) as map(xs:string,item()*) { this:luminatev(xyz:raw-coordinates($colour), $luminance)=>xyz:to-xyz() }
Function: luminatev
declare function luminatev($colour as xs:double*,
$luminance as xs:double) as xs:double*
declare function luminatev($colour as xs:double*, $luminance as xs:double) as xs:double*
luminatev()
Change the luminance to the new luminance.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- luminance as xs:double: Output luminance
Returns
- xs:double*: luminated XYZ colour vector
declare function this:luminatev( $colour as xs:double*, $luminance as xs:double ) as xs:double* { let $l := this:luminancev($colour) return if ($l = 0) then (0.0, 0.0, 0.0) (: black :) else $colour=>v:times($luminance div $l) }
Function: shade
declare function shade($colour as map(xs:string,item()*),
$brightness as xs:double) as map(xs:string,item()*)
declare function shade($colour as map(xs:string,item()*), $brightness as xs:double) as map(xs:string,item()*)
shade()
Shade the colour by multiplying by the brightness value.
Unlike luminate in that we don't normalize by the actual brightness.
Params
- colour as map(xs:string,item()*): XYZ input colour as raw coordinates
- brightness as xs:double: Output brightness [0.0,1.0]
Returns
- map(xs:string,item()*): shaded XYZ colour point
declare function this:shade( $colour as map(xs:string,item()*), $brightness as xs:double ) as map(xs:string,item()*) { this:shadev(xyz:raw-coordinates($colour), $brightness)=>xyz:to-xyz() }
Function: shadev
declare function shadev($colour as xs:double*,
$brightness as xs:double) as xs:double*
declare function shadev($colour as xs:double*, $brightness as xs:double) as xs:double*
shadev()
Shade the colour by multiplying by the brightness value.
Params
- colour as xs:double*: XYZ input colour as raw coordinates
- brightness as xs:double: Output brightness [0.0,1.0]
Returns
- xs:double*: shaded XYZ colour vector
declare function this:shadev( $colour as xs:double*, $brightness as xs:double ) as xs:double* { $colour=>v:times($brightness) }
Function: saturate
declare function saturate($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function saturate($colour as map(xs:string,item()*)) as map(xs:string,item()*)
saturate()
Generally the final step in tone-mapping: clamping colour values
If you use just this you'll get washed out whites
Params
- colour as map(xs:string,item()*): XYZ colour point
Returns
- map(xs:string,item()*): clamped XYZ colour point
declare function this:saturate( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { xyz:saturate($colour) }
Function: saturatev
declare function saturatev($colour as xs:double*) as xs:double*
declare function saturatev($colour as xs:double*) as xs:double*
saturatev()
Generally the final step in tone-mapping: clamping colour values
If you use just this you'll get washed out whites
Params
- colour as xs:double*: XYZ input colour as raw coordinates
Returns
- xs:double*: clamped XYZ colour vector
declare function this:saturatev($colour as xs:double*) as xs:double* { v:clamp($colour, 0.0E0, 1.0E0) }
Function: gamma
declare function gamma($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function gamma($colour as map(xs:string,item()*)) as map(xs:string,item()*)
gamma()
Inverse OETF gamma correction assuming 2.2 gamma display
Params
- colour as map(xs:string,item()*): XYZ colour point
Returns
- map(xs:string,item()*): corrected XYZ colour point
declare function this:gamma( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:gammav(xyz:raw-coordinates($colour))=>xyz:to-xyz() }
Function: gammav
declare function gammav($colour as xs:double*) as xs:double*
declare function gammav($colour as xs:double*) as xs:double*
gammav()
Inverse OETF gamma correction assuming 2.2 gamma display
Params
- colour as xs:double*: XYZ colour vector
Returns
- xs:double*: corrected XYZ colour vector
declare function this:gammav($colour as xs:double*) as xs:double* { v:map( function($c as xs:double) { math:pow($c, 1E0 div 2.2E0) }, $colour ) }
Function: cmyk-snap
declare function cmyk-snap($colour as map(xs:string,item()*)) as map(xs:string,item()*)
declare function cmyk-snap($colour as map(xs:string,item()*)) as map(xs:string,item()*)
cmyk-snap()
Roundtrip to CMYK and back to get colours as printed.
Params
- colour as map(xs:string,item()*): RGB colour
Returns
- map(xs:string,item()*): RGB colour mapped through CMYK and back
declare function this:cmyk-snap( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { $colour=>cs:rgb-to-cmyk()=>cs:cmyk-to-rgb() }
Original Source Code
xquery version "3.1"; (:~ : Tonemapping : References: : https://64.github.io/tonemapping/ : https://bartwronski.com/2016/09/01/dynamic-range-and-evs/ : https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ : : There are two parallel APIs here: the higher level one takes XYZ points : and produces XYZ points, and the lower level one takes and produces : raw XYZ coordinates. You could use raw RGB coordinates instead, but : but the results aren't as nice (same as when you try to do interpolations : over raw RGB). If you are going to perform a chain of operations, the : lower-level API will be more efficient (less conversion/construction : between steps). : : You will need to call saturate() or saturatev() before you convert back : to RGB or risk getting illegal RGB values. : : Copyright© Mary Holstege 2021-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since November 2021 : @custom:Status Stable :) module namespace this="http://mathling.com/colour/tonemap"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace v="http://mathling.com/core/vector" at "../core/vector.xqy"; import module namespace cs="http://mathling.com/colour/space" at "../colourspace/colour-space.xqy"; import module namespace xyz="http://mathling.com/colour/xyz" at "../colourspace/xyz.xqy"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; (:~ : ACES() : Perform ACES (Academy Color Encoding System) filmic tone mapping : : @param $colour: XYZ colour : @return remapped XYZ colour point :) declare function this:ACES( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:ACESv($colour=>xyz:raw-coordinates())=>xyz:to-xyz() }; (:~ : ACESv() : Perform ACES (Academy Color Encoding System) filmic tone mapping : : @param $colour: XYZ input colour as raw coordinates : @return remapped XYZ colour vector :) declare function this:ACESv( $colour as xs:double* ) as xs:double* { (: Multiply input matrix by input colour :) let $v := ( v:dot((0.59719E0, 0.35458E0, 0.04823E0), $colour), v:dot((0.07600E0, 0.90834E0, 0.01566E0), $colour), v:dot((0.02840E0, 0.13383E0, 0.84777E0), $colour) ) (: Curve fitting :) let $a := (: v * (v + 0.0245786) - 0.000090537 :) $v=> v:multiply($v=>v:plus(0.0245786E0))=> v:plus(-0.000090537E0) let $b := (: v * (0.983729 * v + 0.4329510) + 0.238081 :) $v=> v:multiply(($v=>v:times(0.983729E0))=>v:plus(0.4329510E0))=> v:plus(0.238081E0) let $ab := $a=>v:divide($b) (: Multiply by output matrix to get output colour :) let $m2ab := ( (1.60475E0, -0.53108E0, -0.07367E0)=>v:dot($ab), (-0.10208E0, 1.10813E0, -0.00605E0)=>v:dot($ab), (-0.00327E0, -0.07276E0, 1.07602E0)=>v:dot($ab) ) return ( $m2ab ) }; (:~ : Hablic-partial() : Basic Hablic operator with default parameters : : @param $colour: XYZ input colour as raw coordinates : @param $curve: Hablic constants in order (A, B, C, D, E, F) :) declare %private function this:Hablic-partial( $colour as xs:double*, $curve as xs:double* ) as xs:double* { let $A := $curve[1] let $B := $curve[2] let $C := $curve[3] let $D := $curve[4] let $E := $curve[5] let $F := $curve[6] return ( v:map( function($x as xs:double) as xs:double { (($x*($A*$x+$C*$B)+$D*$E) div ($x*($A*$x+$B)+$D*$F))-$E div $F }, $colour ) ) }; (:~ : Default Hablic constants : A=shoulder strength, B=linear strength, C=linear angle, : D=toe strength, E=toe numerator, F=toe denominator, E/F=toe angle :) declare variable $this:HABLIC-DEFAULTS as xs:double* := ( (: A :) 0.15E0, (: 0.22 :) (: B :) 0.50E0, (: 0.30 :) (: C :) 0.10E0, (: D :) 0.20E0, (: E :) 0.02E0, (: 0.01 :) (: F :) 0.30E0 ); (:~ : Hablic() : Hablic tone mapping with default values. : : @param $colour: XYZ point : @return remapped XYZ colour point :) declare function this:Hablic( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:Hablic($colour, 2.0E0, 11.2E0, $this:HABLIC-DEFAULTS) }; (:~ : Hablicv() : Hablic tone mapping with default values. : : @param $colour: XYZ input colour as raw coordinates : @return remapped XYZ colour vector :) declare function this:Hablicv($colour as xs:double*) as xs:double* { this:Hablicv($colour, 2.0E0, 11.2E0, $this:HABLIC-DEFAULTS) }; (:~ : Hablic() : Hablic tone mapping with default values except for exposure. : : @param $colour: XYZ input colour as raw coordinates : @param $exposure: amount of exposure : @return remapped XYZ colour point :) declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double ) as map(xs:string,item()*) { this:Hablic($colour, $exposure, 11.2E0, $this:HABLIC-DEFAULTS) }; (:~ : Hablicv() : Hablic tone mapping with default values except for exposure. : : @param $colour: XYZ input colour as raw coordinates : @param $exposure: amount of exposure : @return remapped XYZ colour vector :) declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double ) as xs:double* { this:Hablicv($colour, $exposure, 11.2E0, $this:HABLIC-DEFAULTS) }; (:~ : Hablic() : Hablic tone mapping with default curve constants. : : @param $colour: XYZ colour point : @param $exposure: amount of exposure : @param $white: white point : @return remapped XYZ colour point :) declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double ) as map(xs:string,item()*) { this:Hablic($colour, $exposure, $white, $this:HABLIC-DEFAULTS) }; (:~ : Hablicv() : Hablic tone mapping with default curve constants. : : @param $colour: XYZ input colour as raw coordinates : @param $exposure: amount of exposure : @param $white: white point : @return remapped XYZ colour vector :) declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double, $white as xs:double ) as xs:double* { this:Hablicv($colour, $exposure, $white, $this:HABLIC-DEFAULTS) }; (:~ : Hablic() : Hablic tone mapping with specific parameters. : : @param $colour: XYZ colour point : @param $exposure: amount of exposure : @param $white: white point : @param $curve: Hablic constants in order (A, B, C, D, E, F) : @return remapped XYZ colour point :) declare function this:Hablic( $colour as map(xs:string,item()*), $exposure as xs:double, $white as xs:double, $curve as xs:double* ) as map(xs:string,item()*) { xyz:to-xyz( this:Hablic-partial(xyz:raw-coordinates($colour), $curve)=> v:divide(this:Hablic-partial($white, $curve)) ) }; (:~ : Hablicv() : Hablic tone mapping with specific parameters. : : @param $colour: XYZ input colour as raw coordinates : @param $exposure: amount of exposure : @param $white: white point : @param $curve: Hablic constants in order (A, B, C, D, E, F) : @return remapped XYZ colour vector :) declare function this:Hablicv( $colour as xs:double*, $exposure as xs:double, $white as xs:double, $curve as xs:double* ) as xs:double* { this:Hablic-partial($colour, $curve)=> v:divide(this:Hablic-partial($white, $curve)) }; (:~ : simple-Reinhard() : Basic Reinhard tonemapping: tends to grey out colours : : @param $colour: XYZ colour point : @return remapped XYZ colour point :) declare function this:simple-Reinhard( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { xyz:to-xyz(this:simple-Reinhardv(xyz:raw-coordinates($colour))) }; (:~ : simple-Reinhardv() : Basic Reinhard tonemapping: tends to grey out colours : : @param $colour: XYZ input colour as raw coordinates : @return remapped XYZ colour vector :) declare function this:simple-Reinhardv( $colour as xs:double ) as xs:double* { $colour=>v:divide($colour=>v:plus(1E0)) }; (:~ : Reinhard() : (Extended) Reinhard tonemapping with luminance : : @param $colour: XYZ colour point : @param $white: white point : @return remapped XYZ colour point :) declare function this:Reinhard( $colour as map(xs:string,item()*), $white as xs:double ) as map(xs:string,item()*) { xyz:to-xyz(this:Reinhardv(xyz:raw-coordinates($colour), $white)) }; (:~ : Reinhardv() : (Extended) Reinhard tonemapping with luminance : : @param $colour: XYZ input colour as raw coordinates : @param $white: white point : @return remapped XYZ colour vector :) declare function this:Reinhardv( $colour as xs:double*, $white as xs:double ) as xs:double* { let $l-old := this:luminancev($colour) let $numerator := $l-old * (1E0 + ($l-old div ($white*$white))) let $l-new := $numerator div (1E0 + $l-old) return this:luminatev($colour, $l-new) }; (:~ : luminance() : Get luminance of an XYZ value. : : @param $colour: XYZ colour point : @return luminance of the colour :) declare function this:luminance( $colour as map(xs:string,item()*) ) as xs:double { xyz:raw-coordinates($colour)=>v:dot((0.2125E0, 0.7152E0, 0.0722E0)) }; (:~ : luminance() : Get luminance of an XYZ value. : : @param $colour: XYZ input colour as raw coordinates : @return luminance of the colour :) declare function this:luminancev($colour as xs:double*) as xs:double { let $res := $colour=>v:dot((0.2125E0, 0.7152E0, 0.0722E0)) return ( if (some $c in $res satisfies $c != $c) then ( trace((), "luninance XYZ=>NaN: "||util:quote($colour)) ) else (), $res ) }; (:~ : luminate() : Change the luminance to the new luminance. : : @param $colour: XYZ input colour as raw coordinates : @param $luminance: Output luminance : @return luminated XYZ colour point :) declare function this:luminate( $colour as map(xs:string,item()*), $luminance as xs:double ) as map(xs:string,item()*) { this:luminatev(xyz:raw-coordinates($colour), $luminance)=>xyz:to-xyz() }; (:~ : luminatev() : Change the luminance to the new luminance. : : @param $colour: XYZ input colour as raw coordinates : @param $luminance: Output luminance : @return luminated XYZ colour vector :) declare function this:luminatev( $colour as xs:double*, $luminance as xs:double ) as xs:double* { let $l := this:luminancev($colour) return if ($l = 0) then (0.0, 0.0, 0.0) (: black :) else $colour=>v:times($luminance div $l) }; (:~ : shade() : Shade the colour by multiplying by the brightness value. : Unlike luminate in that we don't normalize by the actual brightness. : : @param $colour: XYZ input colour as raw coordinates : @param $brightness: Output brightness [0.0,1.0] : @return shaded XYZ colour point :) declare function this:shade( $colour as map(xs:string,item()*), $brightness as xs:double ) as map(xs:string,item()*) { this:shadev(xyz:raw-coordinates($colour), $brightness)=>xyz:to-xyz() }; (:~ : shadev() : Shade the colour by multiplying by the brightness value. : : @param $colour: XYZ input colour as raw coordinates : @param $brightness: Output brightness [0.0,1.0] : @return shaded XYZ colour vector :) declare function this:shadev( $colour as xs:double*, $brightness as xs:double ) as xs:double* { $colour=>v:times($brightness) }; (:~ : saturate() : Generally the final step in tone-mapping: clamping colour values : If you use just this you'll get washed out whites : : @param $colour: XYZ colour point : @return clamped XYZ colour point :) declare function this:saturate( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { xyz:saturate($colour) }; (:~ : saturatev() : Generally the final step in tone-mapping: clamping colour values : If you use just this you'll get washed out whites : : @param $colour: XYZ input colour as raw coordinates : @return clamped XYZ colour vector :) declare function this:saturatev($colour as xs:double*) as xs:double* { v:clamp($colour, 0.0E0, 1.0E0) }; (:~ : gamma() : Inverse OETF gamma correction assuming 2.2 gamma display : : @param $colour: XYZ colour point : @return corrected XYZ colour point :) declare function this:gamma( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { this:gammav(xyz:raw-coordinates($colour))=>xyz:to-xyz() }; (:~ : gammav() : Inverse OETF gamma correction assuming 2.2 gamma display : : @param $colour: XYZ colour vector : @return corrected XYZ colour vector :) declare function this:gammav($colour as xs:double*) as xs:double* { v:map( function($c as xs:double) { math:pow($c, 1E0 div 2.2E0) }, $colour ) }; (:~ : cmyk-snap() : Roundtrip to CMYK and back to get colours as printed. : : @param $colour: RGB colour : @return RGB colour mapped through CMYK and back :) declare function this:cmyk-snap( $colour as map(xs:string,item()*) ) as map(xs:string,item()*) { $colour=>cs:rgb-to-cmyk()=>cs:cmyk-to-rgb() };