# 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.

November 2021
Status: Stable

### Imports

http://mathling.com/colour/space
```import 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: ACESdeclare 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: ACESvdeclare 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: Hablicdeclare 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: Hablicvdeclare 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: Hablicdeclare 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: Hablicvdeclare 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: Hablicdeclare 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: Hablicvdeclare 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: Hablicdeclare 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: Hablicvdeclare 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-Reinharddeclare 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-Reinhardvdeclare 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: Reinharddeclare 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: Reinhardvdeclare 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: luminancedeclare 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: luminancevdeclare 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: luminatedeclare 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: luminatevdeclare 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: shadedeclare function shade(\$colour as map(xs:string,item()*), \$brightness as xs:double) as map(xs:string,item()*)```

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()*)
{
}```

#### ```Function: shadevdeclare function shadev(\$colour as xs:double*, \$brightness as xs:double) as xs:double*```

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: saturatedeclare 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: saturatevdeclare 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: gammadeclare 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: gammavdeclare 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-snapdeclare 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.
:
: @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 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
:)
\$colour as map(xs:string,item()*),
\$brightness as xs:double
) as map(xs:string,item()*)
{
};

(:~
: 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
:)
\$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()
};```