From eb96ab962545cf9b403f18422f1de698d9619499 Mon Sep 17 00:00:00 2001 From: Andinus Date: Thu, 18 Nov 2021 16:56:58 +0530 Subject: Move frame generation to a module, update progress reporting --- lib/Fornax/CLI.rakumod | 123 ++++++++------------------------------- lib/Fornax/GenerateFrame.rakumod | 100 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 100 deletions(-) create mode 100644 lib/Fornax/GenerateFrame.rakumod diff --git a/lib/Fornax/CLI.rakumod b/lib/Fornax/CLI.rakumod index 8b18215..d066ebf 100644 --- a/lib/Fornax/CLI.rakumod +++ b/lib/Fornax/CLI.rakumod @@ -1,5 +1,4 @@ -use Cairo; -use Fornax::Hex2RGB; +use Fornax::GenerateFrame; subset File of Str where *.IO.f; @@ -19,7 +18,7 @@ multi sub MAIN( Int() :$batch = 4, #= number of iterations to process at once (default: 4) Int() :fps($frame-rate) = 1, #= frame rate for video solution (default: 1) Bool :$skip-video, #= skip video solution - Bool :$verbose = True, #= verbosity (default: True) + Bool :$debug, #= debug logs ) is export { my IO() $output = "%s/fornax-%s".sprintf( '/tmp', ('a'...'z', 'A'...'Z', 0...9).roll(8).join @@ -27,40 +26,14 @@ multi sub MAIN( mkdir $output; die "Output directory doesn't exist" unless $output.d; - put "[fornax] Output: '$output'" if $verbose; + put "[fornax] Output: '$output'"; my Str @lines = $input.IO.lines; my Int() %meta{Str} = Metadata.parse(@lines.first).Hash - or die "Cannot parse metadata"; - - # Cells as defined by fornax format. - constant $PATH = '.'; - constant $BLOK = '#'; - constant $DEST = '$'; - constant $STRT = '^'; - constant $VIS = '-'; - constant $CUR = '@'; - constant $CURPATH = '~'; + or die "Cannot parse metadata"; constant %CANVAS = :1920width, :1080height; - # Colors. - constant %C = ( - bg-main => "#ffffff", - - red-subtle-bg => "#f2b0a2", - blue-subtle-bg => "#b5d0ff", - cyan-subtle-bg => "#c0efff", - green-subtle-bg => "#aecf90", - - fg-main => "#000000", - - fg-special-cold => "#093060", - fg-special-warm => "#5d3026", - fg-special-mild => "#184034", - fg-special-calm => "#61284f", - ).map: {.key => hex2rgb(.value)}; - # Every cell must be square. Get the maximum width, height and use # that to decide which is to be used. my Int %cell = width => %CANVAS div %meta, @@ -79,7 +52,8 @@ multi sub MAIN( $side = %cell; } - enum IterStatus ; + my $render-start = now; + my Int $total-frames = @lines.elems - 1; my Promise @p; for @lines.skip.kv -> $idx, $iter is rw { @@ -88,89 +62,38 @@ multi sub MAIN( if @p.elems == $batch { await @p; @p = []; + + print "\r"; + print "%s Remaining: %.2fs Elapsed: %.2fs %s".sprintf( + "[fornax $idx/$total-frames]", + ((now - $render-start) / $idx) * ($total-frames - $idx), + now - $render-start, " ", + ); } push @p, start { - my IterStatus $status; - given $iter.substr(0, 1) { - when '|' { $status = Completed } - when '!' { $status = Blocked } - default { $status = Walking } - }; - - # Remove marker. - $iter .= substr(1) if $status == Completed|Blocked; - - put "[fornax] $idx $iter $status" if $verbose; - - my @grid = $iter.comb.rotor: %meta; - warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta; - - given Cairo::Image.create( - Cairo::FORMAT_ARGB32, %CANVAS, %CANVAS - ) { - given Cairo::Context.new($_) { - # Paint the entire canvas white. - .rgb: |%C; - .rectangle(0, 0, %CANVAS, %CANVAS); - .fill; - - for ^%meta -> $r { - for ^%meta -> $c { - my Int @target = %excess div 2 + $c * $side, - %excess div 2 + $r * $side, - $side, $side; - - .rectangle: |@target; - - given @grid[$r][$c] -> $cell { - # According to the format, current - # position may be prioritized over - # Destination symbol so we colorize it - # according to $status. - when $cell eq $CUR { - .rgba: |%C, 0.56; - .rgba: |%C, 0.72 if $status == Completed; - .rgba: |%C, 0.72 if $status == Blocked; - } - when $cell eq $CURPATH { - .rgba: |%C, 0.84; - .rgba: |%C, 0.96 if $status == Completed; - .rgba: |%C, 0.96 if $status == Blocked; - } - when $cell eq $VIS { - .rgba: |%C, 0.72; - } - when $cell eq $BLOK { .rgba: |%C, 0.56 } - when $cell eq $STRT|$DEST { .rgba: |%C, 0.72 } - default { .rgba: |%C, 0.08 } - } - .fill :preserve; - - .rgb: |%C; - .stroke; - } - } - } - .write_png("%s/%08d.png".sprintf: $output, $idx); - .finish; - } + generate-frame( + :%CANVAS, :%excess, :$side, :%meta, :$iter, :$idx, :$debug, + :out("%s/%08d.png".sprintf: $output, $idx), + ); } } # Wait for remaining jobs to finish. await @p; - put "[fornax] Generated images." if $verbose; + print "\r"; + put "[fornax] Generated $total-frames frames in %.2fs. %s".sprintf( + now - $render-start, " " x 16, + ); unless $skip-video { - put "[fornax] Creating a slideshow." if $verbose; + put "[fornax] Creating a slideshow."; - my Str $log-level = $verbose ?? "info" !! "error"; + my Str $log-level = $debug ?? "info" !! "error"; run «ffmpeg -loglevel "$log-level" -r "$frame-rate" -i "$output/\%08d.png" -vf 'tpad=stop_mode=clone:stop_duration=4' -vcodec libx264 -crf 28 -pix_fmt yuv420p "$output/solution.mp4"»; } - put "[fornax] Output: '$output'"; } multi sub MAIN( diff --git a/lib/Fornax/GenerateFrame.rakumod b/lib/Fornax/GenerateFrame.rakumod new file mode 100644 index 0000000..e19d342 --- /dev/null +++ b/lib/Fornax/GenerateFrame.rakumod @@ -0,0 +1,100 @@ +use Cairo; +use Fornax::Hex2RGB; + +# Cells as defined by fornax format. +constant $PATH = '.'; +constant $BLOK = '#'; +constant $DEST = '$'; +constant $STRT = '^'; +constant $VIS = '-'; +constant $CUR = '@'; +constant $CURPATH = '~'; + +# Colors. +constant %C = ( + bg-main => "#ffffff", + + red-subtle-bg => "#f2b0a2", + blue-subtle-bg => "#b5d0ff", + cyan-subtle-bg => "#c0efff", + green-subtle-bg => "#aecf90", + + fg-main => "#000000", + + fg-special-cold => "#093060", + fg-special-warm => "#5d3026", + fg-special-mild => "#184034", + fg-special-calm => "#61284f", +).map: {.key => hex2rgb(.value)}; + +enum IterStatus ; + +sub generate-frame( + :%CANVAS, :$out, :%excess, :$side, :%meta, :$iter is copy + , :$idx, :$debug, +) is export { + my IterStatus $status; + given $iter.substr(0, 1) { + when '|' { $status = Completed } + when '!' { $status = Blocked } + default { $status = Walking } + }; + + # Remove marker. + $iter .= substr(1) if $status == Completed|Blocked; + + put "\n[fornax] $idx $iter $status" if $debug; + + my @grid = $iter.comb.rotor: %meta; + warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta; + + given Cairo::Image.create( + Cairo::FORMAT_ARGB32, %CANVAS, %CANVAS + ) { + given Cairo::Context.new($_) { + # Paint the entire canvas white. + .rgb: |%C; + .rectangle(0, 0, %CANVAS, %CANVAS); + .fill; + + # This seems to be slower than creating an intermediate + # variable and assigning from that. Difference is not much + # so we'll ignore it. + for ^%meta X ^%meta -> ($r, $c) { + my Int @target = %excess div 2 + $c * $side, + %excess div 2 + $r * $side, + $side, $side; + + .rectangle: |@target; + + given @grid[$r][$c] -> $cell { + # According to the format, current position may be + # prioritized over Destination symbol so we + # colorize it according to $status. + when $cell eq $CUR { + .rgba: |%C, 0.56; + .rgba: |%C, 0.72 if $status == Completed; + .rgba: |%C, 0.72 if $status == Blocked; + } + when $cell eq $CURPATH { + .rgba: |%C, 0.84; + .rgba: |%C, 0.96 if $status == Completed; + .rgba: |%C, 0.96 if $status == Blocked; + } + when $cell eq $VIS { + .rgba: |%C, 0.72; + } + when $cell eq $BLOK { .rgba: |%C, 0.56 } + when $cell eq $STRT|$DEST { .rgba: |%C, 0.72 } + default { .rgba: |%C, 0.08 } + } + .fill :preserve; + + .rgb: |%C; + .stroke; + } + } + .write_png($out); + .finish; + } +} -- cgit 1.4.1-2-gfad0