http://mathling.com/geometric/pixels library module
http://mathling.com/geometric/pixels
Module for drawing various things out onto pixel array. Not intended for
heavy rendering, more for creating seeds for diffusion-reaction or the like.
Copyright© Mary Holstege 2020-2024
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Bleeding edge
Imports
http://mathling.com/geometric/edgeimport module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/sdf
import module namespace sdf="http://mathling.com/sdf" at "../sdf/sdf.xqy"http://mathling.com/core/callable
import module namespace f="http://mathling.com/core/callable" at "../core/callable.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"
Functions
Function: max
declare function max($pixels as array(array(*)),
$other as array(array(*))) as array(array(*))
declare function max($pixels as array(array(*)), $other as array(array(*))) as array(array(*))
max()
Compute the maximum of the values at each corresponding point in the array
and return an array with those maximums.
A way to union greyscale values, for example.
Params
- pixels as array(array(*)) pixel array
- other as array(array(*)) another pixel array
Returns
- array(array(*)): pixel array containing maximum value at every point
declare function this:max( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { max(($this, $that)) } ) } ) }
Function: avg
declare function avg($pixels as array(array(*)),
$other as array(array(*))) as array(array(*))
declare function avg($pixels as array(array(*)), $other as array(array(*))) as array(array(*))
avg()
Compute the average of the values at each corresponding point in the array
and return an array with those maximums.
A way to union greyscale values, for example.
Params
- pixels as array(array(*)) pixel array
- other as array(array(*)) another pixel array
Returns
- array(array(*)): pixel array containing average value at every point
declare function this:avg( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { avg(($this, $that)) } ) } ) }
Function: mask
declare function mask($pixels as array(array(*)),
$other as array(array(*))) as array(array(*))
declare function mask($pixels as array(array(*)), $other as array(array(*))) as array(array(*))
mask()
Mask the pixels from one grid by another.
Params
- pixels as array(array(*)) pixel array
- other as array(array(*)) another pixel array
Returns
- array(array(*)): pixel array containing the values of the first array minus the values of the second, clamped in [0,1] range
declare function this:mask( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { util:clamp($this - $that, 0.0, 1.0) } ) } ) }
Function: edge-pixel-points
declare function edge-pixel-points($edges as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare function edge-pixel-points($edges as map(xs:string,item()*)*) as map(xs:string,item()*)*
edge-pixel-points()
Determine which points we need to represent the straight edges.
Bresenham algorithm.
Params
- edges as map(xs:string,item()*)* the edges (straight edges)
Returns
- map(xs:string,item()*)*: the pixel points touching those edges
declare function this:edge-pixel-points( $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)* { for $edge in $edges let $start := edge:start($edge) let $end := edge:end($edge) return ( if (point:x($start)=point:x($end)) then ( let $x := point:x($start) for $y in min((point:y($start), point:y($end))) to max((point:y($start), point:y($end))) return point:point($x, $y) ) else if (point:y($start)=point:y($end)) then ( let $y := point:y($start) for $x in min((point:x($start), point:x($end))) to max((point:x($start), point:x($end))) return point:point($x, $y) ) else ( let $x0 := point:x($start) let $x1 := point:x($end) let $y0 := point:y($start) let $y1 := point:y($end) let $dx := abs($x1 - $x0) let $sx := util:sign($x1 - $x0) let $dy := -abs($y1 - $y0) let $sy := util:sign($y1 - $y0) let $error := $dx + $dy return ( util:until( function($x as xs:integer, $y as xs:integer, $error as xs:integer, $points as map(xs:string,item()*)*) as xs:boolean { $x = $x1 and $y = $y1 }, function($x as xs:integer, $y as xs:integer, $error as xs:integer, $points as map(xs:string,item()*)*) as item()* { if ($x = $x1 and $y = $y1) then ( $x, $y, $error, ($points, point:point($x, $y)) ) else ( let $e2 := 2 * $error let $next-x := if ($e2 >= $dy) then ( if ($x = $x1) then $x else $x + $dx ) else $x let $next-error := if ($e2 >= $dy) then ( if ($x = $x1) then $error else $error + $dy ) else $error let $next-y := if ($e2 <= $dx) then ( if ($y = $y1) then $y else $y + $sy ) else $y let $next-error := if ($e2 <= $dx) then ( if ($y = $y1) then $next-error + $dx else $error ) else $next-error return ( $next-x, $next-y, $next-error, ($points, point:point($x, $y)) ) ) }, $x0, $y0, $error, () ) )=>tail()=>tail()=>tail() ) )=>point:with-properties(map {"cix": $edge("cix")}) }
Function: to-array
declare function to-array($canvas as map(xs:string,item()*),
$regions as map(xs:string,item()*)*,
$filled as xs:boolean,
$base-grid as array(array(*))?,
$colouring as function(xs:integer*, xs:double) as xs:double) as array(array(*))
declare function to-array($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))?, $colouring as function(xs:integer*, xs:double) as xs:double) as array(array(*))
to-array()
Render regions into pixel array. By default index values are 1 for the region presence,
0 for absence. The colouring function can modify this for non-zero values.
Renders borders unless filled is true.
Params
- canvas as map(xs:string,item()*) space to turn into pixel grid
- regions as map(xs:string,item()*)* to render
- filled as xs:boolean whether to fill interior of regions
- base-grid as array(array(*))? starting grid, if any
- colouring as function(xs:integer*,xs:double)asxs:double function point and distance to index value
Returns
- array(array(*)): array of arrays of index values
declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))?, $colouring as function(xs:integer*, xs:double) as xs:double ) as array(array(*)) { let $singletons := ( point:snap($regions[util:kind(.)="point"]), this:edge-pixel-points($regions[util:kind(.)="edge"]) ) let $grid := array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return ( if (some $p in $singletons satisfies $j = point:y($p) and $i = point:x($p)) then $colouring(($i,$j), 1) else if (exists($base-grid)) then $base-grid($j)($i) else 0 ) } } let $others := $regions[ not(util:kind(.)=("point","edge")) ] return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and $fij <= 0) then $colouring(($i,$j), $fij) else if ($fij <= 1.42) then $colouring(($i,$j), $fij) else $grid($j)($i) ) } } ) ) ) }
Function: to-array
declare function to-array($canvas as map(xs:string,item()*),
$regions as map(xs:string,item()*)*,
$filled as xs:boolean,
$base-grid as array(array(*))?) as array(array(*))
declare function to-array($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))?) as array(array(*))
to-array()
Render regions into pixel array using default colouring (1 for the region presence,
0 for absence).
Renders borders unless filled is true.
Params
- canvas as map(xs:string,item()*) space to turn into pixel grid
- regions as map(xs:string,item()*)* to render
- filled as xs:boolean whether to fill interior of regions
- base-grid as array(array(*))?
Returns
- array(array(*)): array of arrays of index values
declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))? ) as array(array(*)) { let $singletons := ( point:snap($regions[util:kind(.)="point"]), this:edge-pixel-points($regions[util:kind(.)="edge"]) ) let $grid := array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return ( if (some $p in $singletons satisfies $j = point:y($p) and $i = point:x($p)) then 1 else if (exists($base-grid)) then $base-grid($j)($i) else 0 ) } } let $others := $regions[ not(util:kind(.)=("point","edge")) ] return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and head($fij) <= 0) then (tail($fij),1)[1] else if ($fij <= 1.42) then (tail($fij),1)[1] else $grid($j)($i) ) } } ) ) ) }
Function: to-array
declare function to-array($canvas as map(xs:string,item()*),
$regions as map(xs:string,item()*)*,
$filled as xs:boolean) as array(array(*))
declare function to-array($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean) as array(array(*))
Params
- canvas as map(xs:string,item()*)
- regions as map(xs:string,item()*)*
- filled as xs:boolean
Returns
- array(array(*))
declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean ) as array(array(*)) { this:to-array($canvas, $regions, $filled, ()) }
Function: to-signed-array
declare function to-signed-array($canvas as map(xs:string,item()*),
$regions as map(xs:string,item()*)*,
$filled as xs:boolean,
$base-grid as array(array(*))?) as array(array(*))
declare function to-signed-array($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))?) as array(array(*))
Params
- canvas as map(xs:string,item()*)
- regions as map(xs:string,item()*)*
- filled as xs:boolean
- base-grid as array(array(*))?
Returns
- array(array(*))
declare function this:to-signed-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))? ) as array(array(*)) { let $grid := ( if (exists($base-grid)) then $base-grid else ( array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return 0 } } ) ) let $others := $regions return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {"op": "sign-union", "colouring": true()})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and head($fij) <= 0) then (tail($fij),1)[1] else if ($fij = 0) then (tail($fij),1)[1] else $grid($j)($i) ) } } ) ) ) }
Function: to-colour-array
declare function to-colour-array($canvas as map(xs:string,item()*),
$regions as map(xs:string,item()*)*,
$filled as xs:boolean,
$colours as xs:string*) as array(array(*))
declare function to-colour-array($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $colours as xs:string*) as array(array(*))
to-colour-array()
Render regions into pixel array mapping distinct colours into a colour index.
Renders borders unless filled is true.
Params
- canvas as map(xs:string,item()*) space to turn into pixel grid
- regions as map(xs:string,item()*)* to render
- filled as xs:boolean whether to fill interior of regions
- colours as xs:string* colours to map to index values
Returns
- array(array(*)): array of arrays of index values
declare function this:to-colour-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $colours as xs:string* ) as array(array(*)) { let $regions := ( for $region in $regions let $cix := ( for $i in 1 to count($colours) where $region("colour")=$colours[$i] return $i ) return ( $region=>map:put("cix", $cix) ) ) return this:to-signed-array($canvas, $regions, $filled, ()) }
Original Source Code
xquery version "3.1"; (:~ : Module for drawing various things out onto pixel array. Not intended for : heavy rendering, more for creating seeds for diffusion-reaction or the like. : : Copyright© Mary Holstege 2020-2024 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since October 2023 : @custom:Status Bleeding edge :) module namespace this="http://mathling.com/geometric/pixels"; import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace f="http://mathling.com/core/callable" at "../core/callable.xqy"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace sdf="http://mathling.com/sdf" at "../sdf/sdf.xqy"; declare namespace art="http://mathling.com/art"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace array="http://www.w3.org/2005/xpath-functions/array"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; (:~ : max() : Compute the maximum of the values at each corresponding point in the array : and return an array with those maximums. : A way to union greyscale values, for example. : : @param $pixels pixel array : @param $other another pixel array : @return pixel array containing maximum value at every point :) declare function this:max( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { max(($this, $that)) } ) } ) }; (:~ : avg() : Compute the average of the values at each corresponding point in the array : and return an array with those maximums. : A way to union greyscale values, for example. : : @param $pixels pixel array : @param $other another pixel array : @return pixel array containing average value at every point :) declare function this:avg( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { avg(($this, $that)) } ) } ) }; (:~ : mask() : Mask the pixels from one grid by another. : : @param $pixels pixel array : @param $other another pixel array : @return pixel array containing the values of the first array minus the values of the second, clamped in [0,1] range :) declare function this:mask( $pixels as array(array(*)), $other as array(array(*)) ) as array(array(*)) { if (array:size($pixels)=array:size($other)) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), if (array:size($pixels(1))=array:size($other(1))) then () else errors:error("UTIL-MATRIXNOTCOMPAT", ("pixels:union", array:size($pixels), array:size($other), array:size($pixels(1)), array:size($other(1)))), array:for-each-pair($pixels, $other, function($row as array(*), $orow as array(*)) as array(*) { array:for-each-pair($row, $orow, function($this as xs:double, $that as xs:double) as xs:double { util:clamp($this - $that, 0.0, 1.0) } ) } ) }; (:~ : edge-pixel-points() : Determine which points we need to represent the straight edges. : Bresenham algorithm. : : @param $edges the edges (straight edges) : @return the pixel points touching those edges :) declare function this:edge-pixel-points( $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)* { for $edge in $edges let $start := edge:start($edge) let $end := edge:end($edge) return ( if (point:x($start)=point:x($end)) then ( let $x := point:x($start) for $y in min((point:y($start), point:y($end))) to max((point:y($start), point:y($end))) return point:point($x, $y) ) else if (point:y($start)=point:y($end)) then ( let $y := point:y($start) for $x in min((point:x($start), point:x($end))) to max((point:x($start), point:x($end))) return point:point($x, $y) ) else ( let $x0 := point:x($start) let $x1 := point:x($end) let $y0 := point:y($start) let $y1 := point:y($end) let $dx := abs($x1 - $x0) let $sx := util:sign($x1 - $x0) let $dy := -abs($y1 - $y0) let $sy := util:sign($y1 - $y0) let $error := $dx + $dy return ( util:until( function($x as xs:integer, $y as xs:integer, $error as xs:integer, $points as map(xs:string,item()*)*) as xs:boolean { $x = $x1 and $y = $y1 }, function($x as xs:integer, $y as xs:integer, $error as xs:integer, $points as map(xs:string,item()*)*) as item()* { if ($x = $x1 and $y = $y1) then ( $x, $y, $error, ($points, point:point($x, $y)) ) else ( let $e2 := 2 * $error let $next-x := if ($e2 >= $dy) then ( if ($x = $x1) then $x else $x + $dx ) else $x let $next-error := if ($e2 >= $dy) then ( if ($x = $x1) then $error else $error + $dy ) else $error let $next-y := if ($e2 <= $dx) then ( if ($y = $y1) then $y else $y + $sy ) else $y let $next-error := if ($e2 <= $dx) then ( if ($y = $y1) then $next-error + $dx else $error ) else $next-error return ( $next-x, $next-y, $next-error, ($points, point:point($x, $y)) ) ) }, $x0, $y0, $error, () ) )=>tail()=>tail()=>tail() ) )=>point:with-properties(map {"cix": $edge("cix")}) }; (:~ : to-array() : Render regions into pixel array. By default index values are 1 for the region presence, 0 for absence. The colouring function can modify this for non-zero values. : Renders borders unless filled is true. : : @param $canvas space to turn into pixel grid : @param $regions to render : @param $filled whether to fill interior of regions : @param $base-grid starting grid, if any : @param $colouring function point and distance to index value : @return array of arrays of index values :) declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))?, $colouring as function(xs:integer*, xs:double) as xs:double ) as array(array(*)) { let $singletons := ( point:snap($regions[util:kind(.)="point"]), this:edge-pixel-points($regions[util:kind(.)="edge"]) ) let $grid := array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return ( if (some $p in $singletons satisfies $j = point:y($p) and $i = point:x($p)) then $colouring(($i,$j), 1) else if (exists($base-grid)) then $base-grid($j)($i) else 0 ) } } let $others := $regions[ not(util:kind(.)=("point","edge")) ] return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and $fij <= 0) then $colouring(($i,$j), $fij) else if ($fij <= 1.42) then $colouring(($i,$j), $fij) else $grid($j)($i) ) } } ) ) ) }; (:~ : to-array() : Render regions into pixel array using default colouring (1 for the region presence, 0 for absence). : Renders borders unless filled is true. : : @param $canvas space to turn into pixel grid : @param $regions to render : @param $filled whether to fill interior of regions : @return array of arrays of index values :) declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))? ) as array(array(*)) { let $singletons := ( point:snap($regions[util:kind(.)="point"]), this:edge-pixel-points($regions[util:kind(.)="edge"]) ) let $grid := array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return ( if (some $p in $singletons satisfies $j = point:y($p) and $i = point:x($p)) then 1 else if (exists($base-grid)) then $base-grid($j)($i) else 0 ) } } let $others := $regions[ not(util:kind(.)=("point","edge")) ] return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and head($fij) <= 0) then (tail($fij),1)[1] else if ($fij <= 1.42) then (tail($fij),1)[1] else $grid($j)($i) ) } } ) ) ) }; declare function this:to-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean ) as array(array(*)) { this:to-array($canvas, $regions, $filled, ()) }; declare function this:to-signed-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $base-grid as array(array(*))? ) as array(array(*)) { let $grid := ( if (exists($base-grid)) then $base-grid else ( array { for $j in 1 to xs:integer(box:height($canvas)) return array { for $i in 1 to xs:integer(box:width($canvas)) return 0 } } ) ) let $others := $regions return ( if (empty($others)) then $grid else ( let $sdf := sdf:toSDF($others, map {"op": "sign-union", "colouring": true()})=>f:function() return ( array { for $j in 1 to array:size($grid) return array { for $i in 1 to array:size($grid(1)) let $fij := $sdf(($i, $j)) return ( if ($filled and head($fij) <= 0) then (tail($fij),1)[1] else if ($fij = 0) then (tail($fij),1)[1] else $grid($j)($i) ) } } ) ) ) }; (:~ : to-colour-array() : Render regions into pixel array mapping distinct colours into a colour index. : Renders borders unless filled is true. : : @param $canvas space to turn into pixel grid : @param $regions to render : @param $filled whether to fill interior of regions : @param $colours colours to map to index values : @return array of arrays of index values :) declare function this:to-colour-array( $canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean, $colours as xs:string* ) as array(array(*)) { let $regions := ( for $region in $regions let $cix := ( for $i in 1 to count($colours) where $region("colour")=$colours[$i] return $i ) return ( $region=>map:put("cix", $cix) ) ) return this:to-signed-array($canvas, $regions, $filled, ()) };