http://mathling.com/wfc/image-tiled-model library module
http://mathling.com/wfc/image-tiled-model
Wave Function Collapse
This is a port and rework of WFC
See https://github.com/mxgmn/WaveFunctionCollapse
Requires png/ppm modules, which require Saxon Java extension, EXPath file
respectively.
inferred-model() infers a simple tiling model from a source image.
Larger tile sizes on inferred model => more constraints => prettier result,
but slower.
Copyright© Mary Holstege 2022-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Stable
Imports
http://mathling.com/core/arrayimport module namespace arr="http://mathling.com/core/array" at "../core/array.xqy"http://mathling.com/wfc/modeldef
import module namespace modeldef="http://mathling.com/wfc/modeldef" at "modeldef.xqy"http://mathling.com/image/png
import module namespace png="http://mathling.com/image/png" at "../image/png.xqy"http://mathling.com/wfc/simple-tiled-model
import module namespace simple="http://mathling.com/wfc/simple-tiled-model" at "simple-tiled-model.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/image/matrix
import module namespace matrix="http://mathling.com/image/matrix" at "../image/matrix.xqy"http://mathling.com/image/ppm
import module namespace ppm="http://mathling.com/image/ppm" at "../image/ppm.xqy"http://mathling.com/colour/rgb
import module namespace rgb="http://mathling.com/colour/rgb" at "../colourspace/rgb.xqy"http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"http://mathling.com/art/core
import module namespace core="http://mathling.com/art/core" at "../art/core.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/wfc/model
import module namespace model="http://mathling.com/wfc/model" at "model.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"
Functions
Function: model
declare function model($model-def as map(xs:string,item()*),
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function model($model-def as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- model-def as map(xs:string,item()*)
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:model( $model-def as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { if (not($model-def=>modeldef:image-type() = ("png","ppm"))) then errors:error("WFC-BADFORMAT", $model-def=>modeldef:image-type()) else let $image-type as xs:string := $model-def=>modeldef:image-type() (: Define these as no-ops because all rotations/reflections done already :) let $rotate as function(map(*), xs:integer) as map(*) := function($pattern as map(*), $tilesize as xs:integer) as map(*) {$pattern} let $reflect as function(map(*), xs:integer) as map(*) := function($pattern as map(*), $tilesize as xs:integer) as map(*) {$pattern} let $make-tile as function(xs:string, xs:integer) as map(*) := function ($tilename as xs:string, $tilesize as xs:integer) as map(*) { this:read-tile($options("source-dir"), $tilename, $image-type="png") } return ( simple:base-model( $model-def, $width, $height, $make-tile, $rotate, $reflect, $options ) ) }
Function: model
declare function model($source-dir as xs:string,
$subset-name as xs:string?,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function model($source-dir as xs:string, $subset-name as xs:string?, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- subset-name as xs:string?
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:model( $source-dir as xs:string, $subset-name as xs:string?, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $model-node as element() := doc($source-dir||"/data.xml")/set let $model-def as map(xs:string,item()*) := modeldef:parse($model-node, $subset-name) return ( this:model( $model-def, $width, $height, $options=>map:put("source-dir", $source-dir) ) ) }
Function: edge-aligns
declare function edge-aligns($m1 as map(*),
$m2 as map(*)) as xs:boolean
declare function edge-aligns($m1 as map(*), $m2 as map(*)) as xs:boolean
edge-aligns()
Tiles are same along given row or column, e.g.
o x o o x x
x o o aligns o x x with direction=4 (right)
x x o o x x
Params
- m1 as map(*)
- m2 as map(*)
Returns
- xs:boolean
declare function this:edge-aligns( $m1 as map(*), $m2 as map(*) ) as xs:boolean { let $N as xs:integer := $m1=>arr:rows() let $N as xs:integer := ( util:assert($m1=>arr:rows() = $m1=>arr:columns(), "m1.rows!=m1.columns"), util:assert($m2=>arr:rows() = $m2=>arr:columns(), "m2.rows!=m2.columns"), util:assert($m1=>arr:rows() = $m2=>arr:rows(), "m1.rows!=m2.rows"), $N ) let $left-column-m1 as xs:double* := $m1=>arr:column($N) let $right-column-m2 as xs:double* := $m2=>arr:column($N) return ( every $i in 1 to $N satisfies $left-column-m1[$i] = $right-column-m2[$i] ) }
Function: infer-neighbours
declare function infer-neighbours($patterns as map(*)*,
$keep as xs:integer) as map(xs:string,item()*)*
declare function infer-neighbours($patterns as map(*)*, $keep as xs:integer) as map(xs:string,item()*)*
Params
- patterns as map(*)*
- keep as xs:integer
Returns
- map(xs:string,item()*)*
declare function this:infer-neighbours( $patterns as map(*)*, $keep as xs:integer ) as map(xs:string,item()*)* { for $p1 at $i in $patterns for $p2 at $j in $patterns where this:edge-aligns($p1, $p2) and rand:flip($keep) return modeldef:neighbour(string($i), string($j)) }
Function: base-inferred-model
declare function base-inferred-model($model-def as map(xs:string,item()*),
$patterns as map(*)*,
$colours as xs:integer*,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function base-inferred-model($model-def as map(xs:string,item()*), $patterns as map(*)*, $colours as xs:integer*, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- model-def as map(xs:string,item()*)
- patterns as map(*)*
- colours as xs:integer*
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:base-inferred-model( $model-def as map(xs:string,item()*), $patterns as map(*)*, $colours as xs:integer*, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { if (not($model-def=>modeldef:image-type() = ("png","ppm"))) then errors:error("WFC-BADFORMAT", $model-def=>modeldef:image-type()) else let $rotate as function(map(*), xs:integer) as map(*) := this:rotate-pattern#2 let $reflect as function(map(*), xs:integer) as map(*) := this:reflect-pattern#2 let $make-tile as function(xs:string, xs:integer) as map(*) := function ($tilename as xs:string, $tilesize as xs:integer) as map(*) { let $ix as xs:integer := xs:integer(substring-before($tilename," ")) let $pattern as map(*) := $patterns[$ix] let $data := ( for $v in $pattern=>arr:data() return rgb:from-int($colours[xs:integer($v)]) ) return matrix:array($tilesize, $tilesize, $data) } return ( simple:base-model( $model-def, $width, $height, $make-tile, $rotate, $reflect, $options ) ) }
Function: sample-tiles
declare function sample-tiles($sample as map(*),
$N as xs:integer,
$periodic-input as xs:boolean,
$sample-percent as xs:integer) as map(*)*
declare function sample-tiles($sample as map(*), $N as xs:integer, $periodic-input as xs:boolean, $sample-percent as xs:integer) as map(*)*
Params
- sample as map(*)
- N as xs:integer
- periodic-input as xs:boolean
- sample-percent as xs:integer
Returns
- map(*)*
declare function this:sample-tiles( $sample as map(*), $N as xs:integer, $periodic-input as xs:boolean, $sample-percent as xs:integer ) as map(*)* { let $SX := $sample=>arr:columns() let $SY := $sample=>arr:rows() for $y in 1 to (if ($periodic-input) then $SY else $SY - $N + 1) for $x in 1 to (if ($periodic-input) then $SX else $SX - $N + 1) return ( if (rand:flip($sample-percent)) then $sample=>this:pattern-from-sample($x, $y, $N) else () ) }
Function: inferred-model
declare function inferred-model($sampled-modeldef as map(xs:string,item()*),
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($sampled-modeldef as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- sampled-modeldef as map(xs:string,item()*)
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $sampled-modeldef as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := $sampled-modeldef=>modeldef:source-image() let $image-type as xs:string := $sampled-modeldef=>modeldef:image-type() let $symmetry as xs:integer := $sampled-modeldef=>modeldef:symmetry() let $periodic-input as xs:boolean := $sampled-modeldef=>modeldef:periodic-input() let $image as map(*) := $sampled-modeldef=>modeldef:source-image() let $N as xs:integer := $sampled-modeldef=>modeldef:tilesize() let $keep as xs:integer := $sampled-modeldef=>modeldef:keep-percent() let $colours as xs:integer* := distinct-values(($image=>matrix:data())!rgb:to-int(.)) let $C as xs:integer := count($colours) let $W as xs:integer := math:pow($C, $N*$N) cast as xs:integer let $SX as xs:integer := $image=>matrix:columns() let $SY as xs:integer := $image=>matrix:rows() let $sample as map(*) := let $data := for $y in 1 to $SY for $x in 1 to $SX let $colour as xs:integer := $image=>matrix:get($y, $x)=>rgb:to-int() let $cix as xs:integer := for $col at $i in $colours where $colour = $col return $i (: : -1 because we are going to represent sample arrays as integers : base C, so we need 0s :) return ( util:assert( rgb:from-int($colour)=>rgb:to-string()=$image=>matrix:get($y, $x)=>rgb:to-string(), $colour||" "||(rgb:from-int($colour)=>rgb:to-string())||($image=>matrix:get($y, $x)=>rgb:to-string()) ), ($cix - 1) cast as xs:double ) return ( arr:array($SY (:rows:), $SX(:columns:), $data) ) let $pattern-from-index as function(xs:integer) as map(*) := function($index as xs:integer) as map(*) { let $data as xs:integer* := ( ($index=>util:as-base($C))!(. + 1) ) let $data as xs:integer* := ( for $i in 1 to $N*$N - count($data) return 1, $data ) return ( arr:array($N, $N, $data) ) } let $weights as map(*) := ( fold-left( for $y in 1 to (if ($periodic-input) then $SY else $SY - $N + 1) for $x in 1 to (if ($periodic-input) then $SX else $SX - $N + 1) return [$x, $y], map {}, function($weights as map(*), $pair as array(xs:integer)) as map(*) { let $x as xs:integer := $pair(1) let $y as xs:integer := $pair(2) let $ps as map(*)* := let $p0 as map(*) := $sample=>this:pattern-from-sample($x, $y, $N) let $p1 as map(*) := this:reflect-pattern($p0, $N) let $p2 as map(*) := this:rotate-pattern($p0, $N) let $p3 as map(*) := this:reflect-pattern($p2, $N) let $p4 as map(*) := this:rotate-pattern($p2, $N) let $p5 as map(*) := this:reflect-pattern($p4, $N) let $p6 as map(*) := this:rotate-pattern($p4, $N) let $p7 as map(*) := this:reflect-pattern($p6, $N) return ($p0, $p1, $p2, $p3, $p4, $p5, $p6, $p7) return ( fold-left(1 to $symmetry, $weights, function($weights as map(*), $k as xs:integer) as map(*) { let $index as xs:integer := this:index($ps[$k], $C) return ( if ($weights=>map:contains($index)) then $weights=>util:map-increment($index) else ( $weights=> map:put($index, 1)=> util:map-append("ordering", $index) ) ) } ) ) } ) ) let $ordering as xs:integer* := $weights("ordering") let $weights as xs:double* := for $w at $i in $ordering return xs:double($weights($w)) let $patterns as map(*)* := for $w in $ordering return $pattern-from-index($w) let $tiles as map(xs:string,item()*)* := for $w at $i in $ordering return ( modeldef:tile(string($i), "X", $weights[$i]) ) let $neighbours as map(xs:string,item()*)* := this:infer-neighbours($patterns, $keep) return ( (: util:log(count($patterns)||" "||util:quote($patterns)), util:log(util:quote($neighbours)), :) this:base-inferred-model( modeldef:modeldef( $N, $tiles, $neighbours, $image-type, true() (: every tile unique :) ), $patterns, $colours, $width, $height, $options ) ) }
Function: inferred-model
declare function inferred-model($source-dir as xs:string,
$tilename as xs:string,
$tilesize as xs:integer,
$image-type as xs:string,
$symmetry as xs:integer,
$periodic-input as xs:boolean,
$keep-percent as xs:integer,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $keep-percent as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- tilename as xs:string
- tilesize as xs:integer
- image-type as xs:string
- symmetry as xs:integer
- periodic-input as xs:boolean
- keep-percent as xs:integer
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $keep-percent as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry, $periodic-input, $keep-percent) return ( this:inferred-model($model-def, $width, $height, $options) ) }
Function: inferred-model
declare function inferred-model($source-dir as xs:string,
$tilename as xs:string,
$tilesize as xs:integer,
$image-type as xs:string,
$symmetry as xs:integer,
$periodic-input as xs:boolean,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- tilename as xs:string
- tilesize as xs:integer
- image-type as xs:string
- symmetry as xs:integer
- periodic-input as xs:boolean
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry, $periodic-input) return ( this:inferred-model($model-def, $width, $height, $options) ) }
Function: inferred-model
declare function inferred-model($source-dir as xs:string,
$tilename as xs:string,
$tilesize as xs:integer,
$image-type as xs:string,
$symmetry as xs:integer,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- tilename as xs:string
- tilesize as xs:integer
- image-type as xs:string
- symmetry as xs:integer
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry) return ( this:inferred-model($model-def, $width, $height, $options) ) }
Function: inferred-model
declare function inferred-model($source-dir as xs:string,
$tilename as xs:string,
$tilesize as xs:integer,
$image-type as xs:string,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- tilename as xs:string
- tilesize as xs:integer
- image-type as xs:string
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type) return ( this:inferred-model($model-def, $width, $height, $options) ) }
Function: inferred-model
declare function inferred-model($source-dir as xs:string,
$tilename as xs:string,
$tilesize as xs:integer,
$width as xs:integer,
$height as xs:integer,
$options as map(xs:string,item()*)) as map(xs:string,item()*)
declare function inferred-model($source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- source-dir as xs:string
- tilename as xs:string
- tilesize as xs:integer
- width as xs:integer
- height as xs:integer
- options as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, true()) let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize) return ( this:inferred-model($model-def, $width, $height, $options) ) }
Function: options
declare function options($n as xs:integer,
$periodic-output as xs:boolean,
$background as xs:string,
$ground as xs:integer,
$heuristic as xs:string) as map(xs:string,item()*)
declare function options($n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer, $heuristic as xs:string) as map(xs:string,item()*)
Params
- n as xs:integer
- periodic-output as xs:boolean
- background as xs:string
- ground as xs:integer
- heuristic as xs:string
Returns
- map(xs:string,item()*)
declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer, $heuristic as xs:string ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background, $ground, $heuristic) }
Function: options
declare function options($n as xs:integer,
$periodic-output as xs:boolean,
$background as xs:string,
$ground as xs:integer) as map(xs:string,item()*)
declare function options($n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer) as map(xs:string,item()*)
Params
- n as xs:integer
- periodic-output as xs:boolean
- background as xs:string
- ground as xs:integer
Returns
- map(xs:string,item()*)
declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background, $ground) }
Function: options
declare function options($n as xs:integer,
$periodic-output as xs:boolean,
$background as xs:string) as map(xs:string,item()*)
declare function options($n as xs:integer, $periodic-output as xs:boolean, $background as xs:string) as map(xs:string,item()*)
Params
- n as xs:integer
- periodic-output as xs:boolean
- background as xs:string
Returns
- map(xs:string,item()*)
declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background) }
Function: options
declare function options($n as xs:integer,
$periodic-output as xs:boolean) as map(xs:string,item()*)
declare function options($n as xs:integer, $periodic-output as xs:boolean) as map(xs:string,item()*)
Params
- n as xs:integer
- periodic-output as xs:boolean
Returns
- map(xs:string,item()*)
declare function this:options( $n as xs:integer, $periodic-output as xs:boolean ) as map(xs:string,item()*) { model:options($n, $periodic-output) }
Function: options
declare function options($n as xs:integer) as map(xs:string,item()*)
declare function options($n as xs:integer) as map(xs:string,item()*)
Params
- n as xs:integer
Returns
- map(xs:string,item()*)
declare function this:options( $n as xs:integer ) as map(xs:string,item()*) { model:options($n) }
Function: options
declare function options() as map(xs:string,item()*)
declare function options() as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:options( ) as map(xs:string,item()*) { model:options() }
Function: run
declare function run($model as map(xs:string,item()*),
$limit as xs:integer) as map(xs:string,item()*)
declare function run($model as map(xs:string,item()*), $limit as xs:integer) as map(xs:string,item()*)
Params
- model as map(xs:string,item()*)
- limit as xs:integer
Returns
- map(xs:string,item()*)
declare function this:run( $model as map(xs:string,item()*), $limit as xs:integer ) as map(xs:string,item()*) { $model=>model:run($limit, false()) }
Function: run
declare function run($model as map(xs:string,item()*),
$limit as xs:integer,
$forced as xs:boolean) as map(xs:string,item()*)
declare function run($model as map(xs:string,item()*), $limit as xs:integer, $forced as xs:boolean) as map(xs:string,item()*)
Params
- model as map(xs:string,item()*)
- limit as xs:integer
- forced as xs:boolean
Returns
- map(xs:string,item()*)
declare function this:run( $model as map(xs:string,item()*), $limit as xs:integer, $forced as xs:boolean ) as map(xs:string,item()*) { $model=>model:run($limit, $forced) }
Function: continue
declare function continue($model as map(xs:string,item()*),
$run as map(xs:string,item()*),
$limit as xs:integer) as map(xs:string,item()*)
declare function continue($model as map(xs:string,item()*), $run as map(xs:string,item()*), $limit as xs:integer) as map(xs:string,item()*)
Params
- model as map(xs:string,item()*)
- run as map(xs:string,item()*)
- limit as xs:integer
Returns
- map(xs:string,item()*)
declare function this:continue( $model as map(xs:string,item()*), $run as map(xs:string,item()*), $limit as xs:integer ) as map(xs:string,item()*) { $model=>model:continue($run, $limit) }
Function: graphics
declare function graphics($model as map(*),
$run as map(*),
$canvas as map(xs:string,item()*)) as map(*)
declare function graphics($model as map(*), $run as map(*), $canvas as map(xs:string,item()*)) as map(*)
Params
- model as map(*)
- run as map(*)
- canvas as map(xs:string,item()*)
Returns
- map(*)
declare function this:graphics( $model as map(*), $run as map(*), $canvas as map(xs:string,item()*) ) as map(*) (: colour map :) { simple:graphics($model, $run, $canvas) }
Function: draw
declare function draw($model as map(*),
$run as map(*),
$canvas as map(xs:string,item()*)) as item()*
declare function draw($model as map(*), $run as map(*), $canvas as map(xs:string,item()*)) as item()*
Params
- model as map(*)
- run as map(*)
- canvas as map(xs:string,item()*)
Returns
- item()*
declare function this:draw( $model as map(*), $run as map(*), $canvas as map(xs:string,item()*) ) as item()* { simple:draw($model, $run, $canvas) }
Function: draw
declare function draw($model as map(*),
$run as map(*),
$mutation as function(map(xs:string,item()*)) as map(xs:string,item()*),
$edge-width as function(map(xs:string,item()*)) as xs:double?,
$canvas as map(xs:string,item()*)) as item()*
declare function draw($model as map(*), $run as map(*), $mutation as function(map(xs:string,item()*)) as map(xs:string,item()*), $edge-width as function(map(xs:string,item()*)) as xs:double?, $canvas as map(xs:string,item()*)) as item()*
Params
- model as map(*)
- run as map(*)
- mutation as function(map(xs:string,item()*))asmap(xs:string,item()*)
- edge-width as function(map(xs:string,item()*))asxs:double?
- canvas as map(xs:string,item()*)
Returns
- item()*
declare function this:draw( $model as map(*), $run as map(*), $mutation as function(map(xs:string,item()*)) as map(xs:string,item()*), $edge-width as function(map(xs:string,item()*)) as xs:double?, $canvas as map(xs:string,item()*) ) as item()* { simple:draw($model, $run, $mutation, $edge-width, $canvas) }
Original Source Code
xquery version "3.1"; (:~ : Wave Function Collapse : This is a port and rework of WFC : See https://github.com/mxgmn/WaveFunctionCollapse : : Requires png/ppm modules, which require Saxon Java extension, EXPath file : respectively. : inferred-model() infers a simple tiling model from a source image. : Larger tile sizes on inferred model => more constraints => prettier result, : but slower. : : Copyright© Mary Holstege 2022-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since July 2022 : @custom:Status Stable :) module namespace this="http://mathling.com/wfc/image-tiled-model"; import module namespace core="http://mathling.com/art/core" at "../art/core.xqy"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace arr="http://mathling.com/core/array" at "../core/array.xqy"; import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"; import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"; import module namespace matrix="http://mathling.com/image/matrix" at "../image/matrix.xqy"; import module namespace ppm="http://mathling.com/image/ppm" at "../image/ppm.xqy"; import module namespace png="http://mathling.com/image/png" at "../image/png.xqy"; import module namespace rgb="http://mathling.com/colour/rgb" at "../colourspace/rgb.xqy"; import module namespace model="http://mathling.com/wfc/model" at "model.xqy"; import module namespace simple="http://mathling.com/wfc/simple-tiled-model" at "simple-tiled-model.xqy"; import module namespace modeldef="http://mathling.com/wfc/modeldef" at "modeldef.xqy"; 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"; declare %private function this:rotate( $matrix as map(*), $tilesize as xs:integer ) as map(*) { let $colours := $matrix=>matrix:data() return $matrix=>matrix:data( for $y in 1 to $tilesize for $x in 1 to $tilesize return $colours[$tilesize - $y + 1 + ($x - 1) * $tilesize] ) }; declare %private function this:reflect( $matrix as map(*), $tilesize as xs:integer ) as map(*) { let $colours := $matrix=>matrix:data() return $matrix=>matrix:data( for $y in 1 to $tilesize for $x in 1 to $tilesize return $colours[$tilesize - $x + 1 + ($y - 1) * $tilesize] ) }; declare %private function this:rotate-pattern( $matrix as map(*), $tilesize as xs:integer ) as map(*) { let $colours := $matrix=>arr:data() return $matrix=>arr:data( for $y in 1 to $tilesize for $x in 1 to $tilesize return $colours[$tilesize - $y + 1 + ($x - 1) * $tilesize] ) }; declare %private function this:reflect-pattern( $matrix as map(*), $tilesize as xs:integer ) as map(*) { let $colours := $matrix=>arr:data() return $matrix=>arr:data( for $y in 1 to $tilesize for $x in 1 to $tilesize return $colours[$tilesize - $x + 1 + ($y - 1) * $tilesize] ) }; declare %private function this:read-tile( $source-dir as xs:string, $tilename as xs:string, $png as xs:boolean ) as map(*) { if ($png) then ( png:png-array($source-dir||"/"||$tilename||".png") ) else ( ppm:p3-array($source-dir||"/"||$tilename||".ppm") ) }; declare function this:model( $model-def as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { if (not($model-def=>modeldef:image-type() = ("png","ppm"))) then errors:error("WFC-BADFORMAT", $model-def=>modeldef:image-type()) else let $image-type as xs:string := $model-def=>modeldef:image-type() (: Define these as no-ops because all rotations/reflections done already :) let $rotate as function(map(*), xs:integer) as map(*) := function($pattern as map(*), $tilesize as xs:integer) as map(*) {$pattern} let $reflect as function(map(*), xs:integer) as map(*) := function($pattern as map(*), $tilesize as xs:integer) as map(*) {$pattern} let $make-tile as function(xs:string, xs:integer) as map(*) := function ($tilename as xs:string, $tilesize as xs:integer) as map(*) { this:read-tile($options("source-dir"), $tilename, $image-type="png") } return ( simple:base-model( $model-def, $width, $height, $make-tile, $rotate, $reflect, $options ) ) }; declare function this:model( $source-dir as xs:string, $subset-name as xs:string?, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $model-node as element() := doc($source-dir||"/data.xml")/set let $model-def as map(xs:string,item()*) := modeldef:parse($model-node, $subset-name) return ( this:model( $model-def, $width, $height, $options=>map:put("source-dir", $source-dir) ) ) }; (:~ : edge-aligns() : Tiles are same along given row or column, e.g. : o x o o x x : x o o aligns o x x with direction=4 (right) : x x o o x x :) declare function this:edge-aligns( $m1 as map(*), $m2 as map(*) ) as xs:boolean { let $N as xs:integer := $m1=>arr:rows() let $N as xs:integer := ( util:assert($m1=>arr:rows() = $m1=>arr:columns(), "m1.rows!=m1.columns"), util:assert($m2=>arr:rows() = $m2=>arr:columns(), "m2.rows!=m2.columns"), util:assert($m1=>arr:rows() = $m2=>arr:rows(), "m1.rows!=m2.rows"), $N ) let $left-column-m1 as xs:double* := $m1=>arr:column($N) let $right-column-m2 as xs:double* := $m2=>arr:column($N) return ( every $i in 1 to $N satisfies $left-column-m1[$i] = $right-column-m2[$i] ) }; declare function this:infer-neighbours( $patterns as map(*)*, $keep as xs:integer ) as map(xs:string,item()*)* { for $p1 at $i in $patterns for $p2 at $j in $patterns where this:edge-aligns($p1, $p2) and rand:flip($keep) return modeldef:neighbour(string($i), string($j)) }; declare %private function this:pattern-from-sample( $matrix as map(*), $x as xs:integer, $y as xs:integer, $tilesize as xs:integer ) as map(*) { let $data := let $SX := $matrix=>arr:columns() let $SY := $matrix=>arr:rows() for $dy in 0 to $tilesize - 1 for $dx in 0 to $tilesize - 1 return $matrix=>arr:get(util:modix($y + $dy, $SY), util:modix($x + $dx, $SX)) return arr:array($tilesize, $tilesize, $data) }; declare %private function this:index( $matrix as map(*), $C as xs:integer ) as xs:integer { (: Turn the array into a number base C, essentially :) let $data := reverse($matrix=>arr:data()) let $l as xs:integer := count($data) let $powers as xs:integer* := fold-left(1 to count($data), 1, function($powers as xs:integer*, $i as xs:integer) as xs:integer* { $powers, $powers[last()] * $C } ) return ( fold-left(1 to $l, 0, function($result as xs:integer, $i as xs:integer) as xs:integer { ($result + $powers[$i] * xs:integer($data[$i])) } ) ) }; declare function this:base-inferred-model( $model-def as map(xs:string,item()*), $patterns as map(*)*, $colours as xs:integer*, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { if (not($model-def=>modeldef:image-type() = ("png","ppm"))) then errors:error("WFC-BADFORMAT", $model-def=>modeldef:image-type()) else let $rotate as function(map(*), xs:integer) as map(*) := this:rotate-pattern#2 let $reflect as function(map(*), xs:integer) as map(*) := this:reflect-pattern#2 let $make-tile as function(xs:string, xs:integer) as map(*) := function ($tilename as xs:string, $tilesize as xs:integer) as map(*) { let $ix as xs:integer := xs:integer(substring-before($tilename," ")) let $pattern as map(*) := $patterns[$ix] let $data := ( for $v in $pattern=>arr:data() return rgb:from-int($colours[xs:integer($v)]) ) return matrix:array($tilesize, $tilesize, $data) } return ( simple:base-model( $model-def, $width, $height, $make-tile, $rotate, $reflect, $options ) ) }; declare function this:sample-tiles( $sample as map(*), $N as xs:integer, $periodic-input as xs:boolean, $sample-percent as xs:integer ) as map(*)* { let $SX := $sample=>arr:columns() let $SY := $sample=>arr:rows() for $y in 1 to (if ($periodic-input) then $SY else $SY - $N + 1) for $x in 1 to (if ($periodic-input) then $SX else $SX - $N + 1) return ( if (rand:flip($sample-percent)) then $sample=>this:pattern-from-sample($x, $y, $N) else () ) }; declare function this:inferred-model( $sampled-modeldef as map(xs:string,item()*), $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := $sampled-modeldef=>modeldef:source-image() let $image-type as xs:string := $sampled-modeldef=>modeldef:image-type() let $symmetry as xs:integer := $sampled-modeldef=>modeldef:symmetry() let $periodic-input as xs:boolean := $sampled-modeldef=>modeldef:periodic-input() let $image as map(*) := $sampled-modeldef=>modeldef:source-image() let $N as xs:integer := $sampled-modeldef=>modeldef:tilesize() let $keep as xs:integer := $sampled-modeldef=>modeldef:keep-percent() let $colours as xs:integer* := distinct-values(($image=>matrix:data())!rgb:to-int(.)) let $C as xs:integer := count($colours) let $W as xs:integer := math:pow($C, $N*$N) cast as xs:integer let $SX as xs:integer := $image=>matrix:columns() let $SY as xs:integer := $image=>matrix:rows() let $sample as map(*) := let $data := for $y in 1 to $SY for $x in 1 to $SX let $colour as xs:integer := $image=>matrix:get($y, $x)=>rgb:to-int() let $cix as xs:integer := for $col at $i in $colours where $colour = $col return $i (: : -1 because we are going to represent sample arrays as integers : base C, so we need 0s :) return ( util:assert( rgb:from-int($colour)=>rgb:to-string()=$image=>matrix:get($y, $x)=>rgb:to-string(), $colour||" "||(rgb:from-int($colour)=>rgb:to-string())||($image=>matrix:get($y, $x)=>rgb:to-string()) ), ($cix - 1) cast as xs:double ) return ( arr:array($SY (:rows:), $SX(:columns:), $data) ) let $pattern-from-index as function(xs:integer) as map(*) := function($index as xs:integer) as map(*) { let $data as xs:integer* := ( ($index=>util:as-base($C))!(. + 1) ) let $data as xs:integer* := ( for $i in 1 to $N*$N - count($data) return 1, $data ) return ( arr:array($N, $N, $data) ) } let $weights as map(*) := ( fold-left( for $y in 1 to (if ($periodic-input) then $SY else $SY - $N + 1) for $x in 1 to (if ($periodic-input) then $SX else $SX - $N + 1) return [$x, $y], map {}, function($weights as map(*), $pair as array(xs:integer)) as map(*) { let $x as xs:integer := $pair(1) let $y as xs:integer := $pair(2) let $ps as map(*)* := let $p0 as map(*) := $sample=>this:pattern-from-sample($x, $y, $N) let $p1 as map(*) := this:reflect-pattern($p0, $N) let $p2 as map(*) := this:rotate-pattern($p0, $N) let $p3 as map(*) := this:reflect-pattern($p2, $N) let $p4 as map(*) := this:rotate-pattern($p2, $N) let $p5 as map(*) := this:reflect-pattern($p4, $N) let $p6 as map(*) := this:rotate-pattern($p4, $N) let $p7 as map(*) := this:reflect-pattern($p6, $N) return ($p0, $p1, $p2, $p3, $p4, $p5, $p6, $p7) return ( fold-left(1 to $symmetry, $weights, function($weights as map(*), $k as xs:integer) as map(*) { let $index as xs:integer := this:index($ps[$k], $C) return ( if ($weights=>map:contains($index)) then $weights=>util:map-increment($index) else ( $weights=> map:put($index, 1)=> util:map-append("ordering", $index) ) ) } ) ) } ) ) let $ordering as xs:integer* := $weights("ordering") let $weights as xs:double* := for $w at $i in $ordering return xs:double($weights($w)) let $patterns as map(*)* := for $w in $ordering return $pattern-from-index($w) let $tiles as map(xs:string,item()*)* := for $w at $i in $ordering return ( modeldef:tile(string($i), "X", $weights[$i]) ) let $neighbours as map(xs:string,item()*)* := this:infer-neighbours($patterns, $keep) return ( (: util:log(count($patterns)||" "||util:quote($patterns)), util:log(util:quote($neighbours)), :) this:base-inferred-model( modeldef:modeldef( $N, $tiles, $neighbours, $image-type, true() (: every tile unique :) ), $patterns, $colours, $width, $height, $options ) ) }; declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $keep-percent as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry, $periodic-input, $keep-percent) return ( this:inferred-model($model-def, $width, $height, $options) ) }; declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $periodic-input as xs:boolean, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry, $periodic-input) return ( this:inferred-model($model-def, $width, $height, $options) ) }; declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $symmetry as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type, $symmetry) return ( this:inferred-model($model-def, $width, $height, $options) ) }; declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $image-type as xs:string, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, $image-type="png") let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize, $image-type) return ( this:inferred-model($model-def, $width, $height, $options) ) }; declare function this:inferred-model( $source-dir as xs:string, $tilename as xs:string, $tilesize as xs:integer, $width as xs:integer, $height as xs:integer, $options as map(xs:string,item()*) ) as map(xs:string,item()*) { let $image as map(*) := this:read-tile($source-dir, $tilename, true()) let $model-def as map(xs:string,item()*) := modeldef:sampled-modeldef($image, $tilesize) return ( this:inferred-model($model-def, $width, $height, $options) ) }; declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer, $heuristic as xs:string ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background, $ground, $heuristic) }; declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string, $ground as xs:integer ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background, $ground) }; declare function this:options( $n as xs:integer, $periodic-output as xs:boolean, $background as xs:string ) as map(xs:string,item()*) { model:options($n, $periodic-output, $background) }; declare function this:options( $n as xs:integer, $periodic-output as xs:boolean ) as map(xs:string,item()*) { model:options($n, $periodic-output) }; declare function this:options( $n as xs:integer ) as map(xs:string,item()*) { model:options($n) }; declare function this:options( ) as map(xs:string,item()*) { model:options() }; declare function this:run( $model as map(xs:string,item()*), $limit as xs:integer ) as map(xs:string,item()*) { $model=>model:run($limit, false()) }; declare function this:run( $model as map(xs:string,item()*), $limit as xs:integer, $forced as xs:boolean ) as map(xs:string,item()*) { $model=>model:run($limit, $forced) }; declare function this:continue( $model as map(xs:string,item()*), $run as map(xs:string,item()*), $limit as xs:integer ) as map(xs:string,item()*) { $model=>model:continue($run, $limit) }; declare function this:graphics( $model as map(*), $run as map(*), $canvas as map(xs:string,item()*) ) as map(*) (: colour map :) { simple:graphics($model, $run, $canvas) }; declare function this:draw( $model as map(*), $run as map(*), $canvas as map(xs:string,item()*) ) as item()* { simple:draw($model, $run, $canvas) }; declare function this:draw( $model as map(*), $run as map(*), $mutation as function(map(xs:string,item()*)) as map(xs:string,item()*), $edge-width as function(map(xs:string,item()*)) as xs:double?, $canvas as map(xs:string,item()*) ) as item()* { simple:draw($model, $run, $mutation, $edge-width, $canvas) };