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-2025
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Bleeding edge
Function Index
avg($pixels as array(array(*)), $other as array(array(*))) as array(array(*))edge-pixel-points($edges as map(xs:string,item()*)*) as map(xs:string,item()*)*mask($pixels as array(array(*)), $other as array(array(*))) as array(array(*))max($pixels as array(array(*)), $other as array(array(*))) as array(array(*))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($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($canvas as map(xs:string,item()*), $regions as map(xs:string,item()*)*, $filled as xs:boolean) as array(array(*))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-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(*))
Imports
http://mathling.com/geometric/edgeimport module namespace edge="http://mathling.com/geometric/edge"
at "../geo/edge.xqy"http://mathling.com/core/utilitiesimport module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy"http://mathling.com/sdfimport module namespace sdf="http://mathling.com/sdf"
at "../sdf/sdf.xqy"http://mathling.com/mathimport module namespace mmath="http://mathling.com/math"
at "../math/math.xqy"http://mathling.com/core/callableimport module namespace f="http://mathling.com/core/callable"
at "../core/callable.xqy"http://mathling.com/geometric/rectangleimport module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy"http://mathling.com/geometricimport module namespace geom="http://mathling.com/geometric"
at "../geo/euclidean.xqy"http://mathling.com/core/configimport module namespace config="http://mathling.com/core/config"
at "../core/config.xqy"http://mathling.com/core/errorsimport module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy"http://mathling.com/geometric/pointimport 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 {
mmath: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 := mmath:sign($x1 - $x0)
let $dy := -abs($y1 - $y0)
let $sy := mmath: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")) ]
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
return (
if (empty($others)) then $grid else (
let $sdf := sdf:toSDF($others, map {})=>f:function()
return (
array {
for $j in 1 to $min-y - 1 return $grid($j),
for $j in $min-y to $max-y
return (
array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
}
),
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
}
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")) ]
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
return (
if (empty($others)) then $grid else (
let $sdf := sdf:toSDF($others, map {})=>f:function()
return (
array {
for $j in 1 to $min-y - 1 return $grid($j),
for $j in $min-y to $max-y return array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
},
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
}
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
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
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 $min-y - 1 return $grid($j),
for $j in $min-y to $max-y
return (
array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
}
),
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
}
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
)=>head()
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-2025
: 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 mmath="http://mathling.com/math"
at "../math/math.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 geom="http://mathling.com/geometric"
at "../geo/euclidean.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 {
mmath: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 := mmath:sign($x1 - $x0)
let $dy := -abs($y1 - $y0)
let $sy := mmath: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")) ]
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
return (
if (empty($others)) then $grid else (
let $sdf := sdf:toSDF($others, map {})=>f:function()
return (
array {
for $j in 1 to $min-y - 1 return $grid($j),
for $j in $min-y to $max-y
return (
array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
}
),
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
};
(:~
: 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")) ]
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
return (
if (empty($others)) then $grid else (
let $sdf := sdf:toSDF($others, map {})=>f:function()
return (
array {
for $j in 1 to $min-y - 1 return $grid($j),
for $j in $min-y to $max-y return array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
},
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
};
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
let $bbox := geom:bounding-box($others)
let $min-x := max((box:min-x($bbox), 1)) cast as xs:integer
let $max-x := min((box:max-x($bbox), array:size($grid(1)))) cast as xs:integer
let $min-y := max((box:min-y($bbox), 1)) cast as xs:integer
let $max-y := min((box:max-y($bbox), array:size($grid))) cast as xs:integer
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 $min-y - 1 return $grid($j),
for $j in $min-y to $max-y
return (
array {
for $i in 1 to $min-x - 1 return $grid($j)($i),
for $i in $min-x to $max-x
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)
),
for $i in $max-x + 1 to array:size($grid(1)) return $grid($j)($i)
}
),
for $j in $max-y + 1 to array:size($grid) return $grid($j)
}
)
)
)
};
(:~
: 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
)=>head()
return (
$region=>map:put("cix", $cix)
)
)
return this:to-signed-array($canvas, $regions, $filled, ())
};