G'day All,

I'm continuing my project to create interactive maps for RPGs with Tk generally and, for the main part, Tk::Canvas. I wrote a bit about that in "Tk::Canvas createGroup() Undocumented"; this CUFP post has nothing to do with the issue in that SoPW post; there is some backgound information and (very early, now substantially matured) code which may be of interest. The test code there to create creeks is related to the current work creating paths.

I had created the basic paths, put nicely curved bends in them, and so on. All of this looked good except for where the paths terminated upon entering a glade, meadow, or whatever: all I had at the ends was solid lines; what I wanted was for these to gradually peter out. The sections for this needed to be solid (opaque) where the main path ended and gradually fade to nothingness (transparent) as the terrain moved away from the path. In addition, this gradient needed to have direction to match the direction of the path where it terminated.

I made one futile attempt to do this manually in Gimp: the result looked horrible. I decided to let Perl do it for me. Here's the result which I knocked up this afternoon.

#!/usr/bin/env perl use 5.032; use warnings; use autodie ':all'; use List::Util 'shuffle'; use Path::Tiny; die "Usage: $0 name\n" unless @ARGV == 1; my $name = $ARGV[0]; my ($base, $size, $orient) = split /_/, $name; my %bits_for = (s => 16, m => 32, l => 48, b => 96); die "Unknown size '$size'" unless exists $bits_for{$size}; my $bits = $bits_for{$size}; my $matrix = create_matrix($bits); my $oriented = orient_matrix($orient, $matrix); my $hexes = gen_hex_values($oriented); write_xbm($name, $hexes, $bits); sub create_matrix { my ($bits) = @_; my $matrix = []; for (0 .. $bits - 1) { push $matrix->@*, [shuffle((0)x$_, (1)x($bits-$_))]; } return $matrix; } sub orient_matrix { my ($orient, $matrix) = @_; my $oriented = []; for ($orient) { /^n$/ && do { push $oriented->@*, $matrix->[$_] for reverse 0 .. $matrix +->$#*; last; }; /^s$/ && do { push $oriented->@*, $matrix->[$_] for 0 .. $matrix->$#*; last; }; /^e$/ && do { for my $x (0 .. $matrix->$#*) { my $col = []; for my $y (0 .. $matrix->[$x]->$#*) { push $col->@*, $matrix->[$y][$x]; } push $oriented->@*, $col; } last; }; /^w$/ && do { for my $x (0 .. $matrix->$#*) { my $col = []; for my $y (reverse 0 .. $matrix->[$x]->$#*) { push $col->@*, $matrix->[$y][$x]; } push $oriented->@*, $col; } last; }; die "Unknown orientation '$orient'"; } return $oriented; } sub gen_hex_values { my ($matrix) = @_; my $vector = []; push $vector->@*, $matrix->[$_]->@* for 0 .. $matrix->$#*; my $hexes = []; for (my $i = 0; $i <= $vector->$#*; $i += 8) { push $hexes->@*, map $_ eq '00' ? '0x00' : $_, sprintf '%#0.2x', eval '0b' . join '', $vector->@[$i .. $i ++7]; } return $hexes; } sub write_xbm { my ($name, $hexes, $size) = @_; my $index = '0000'; my $dir = '.'; my @files; { opendir(my $dh, $dir); @files = grep /^\Q$name\E_\d{4}/, readdir $dh; closedir $dh; } if (@files) { $index = (sort { $b cmp $a } map /^\Q$name\E_(\d{4})/, @files) +[0]; ++$index; } my $indexed_name = $name . '_' . $index; my $xbm_file = $indexed_name . '.xbm'; my $xbm_path = path($dir, $xbm_file); { open my $fh, '>', $xbm_path; $fh->say("#define ${indexed_name}_width $size"); $fh->say("#define ${indexed_name}_height $size"); $fh->say("static unsigned char ${indexed_name}_bits[] = { " . +join(', ', $hexes->@*) . ' };'); } return; }

This produced the result I wanted. It allows multiple random instances so all the path endings don't look the same. I wouldn't claim this to be production-grade code; however, it does the required task without any sort of problems. This was only intended to be a quickly developed tool for a specific task; it does the job and does it quickly — I probably won't be spending any time making improvements but acknowledge that improvements could be made.

When I was doing some research into this, before I started coding, I did note that the hex elements (0xhh) seemed to be in groups of twelve. I was unable to find any reason for this, so I didn't implement it — I'm certainly not going to try to discern bit patterns from such a large collection of hex values; although, there is a preponderance of 0xff values on the opaque side, and 0x00 on the transparent side.

Here's an example of output. It is a 32x32 square. The orientation is 'w' (west): opaque on the right; transparent on the left.

#define fade_m_w_0000_width 32 #define fade_m_w_0000_height 32 static unsigned char fade_m_w_0000_bits[] = { 0x01, 0xc8, 0x27, 0xef, +0x20, 0x21, 0xfe, 0xdf, 0x22, 0x8f, 0x7a, 0xff, 0x00, 0x51, 0xdf, 0x7 +f, 0x02, 0xc7, 0xaf, 0xff, 0x10, 0x89, 0xaf, 0xff, 0x11, 0x22, 0x8a, +0xff, 0x01, 0x4f, 0xe7, 0xdf, 0x02, 0x1b, 0x5f, 0xbf, 0x90, 0x8d, 0x2 +9, 0xbf, 0x04, 0x02, 0xff, 0x7f, 0x06, 0x2a, 0x7f, 0xff, 0x04, 0x45, +0x33, 0xbf, 0x00, 0x04, 0xf1, 0x77, 0x00, 0x04, 0xf8, 0xff, 0x08, 0x7 +2, 0x97, 0xff, 0x01, 0x2c, 0xb5, 0xfb, 0x42, 0x11, 0x57, 0xb7, 0x03, +0x24, 0x53, 0xbf, 0x05, 0x1a, 0xce, 0xcf, 0x04, 0x33, 0xdc, 0x3f, 0x0 +8, 0x02, 0x03, 0xff, 0x10, 0xac, 0xdb, 0xef, 0x48, 0xb2, 0x0f, 0x7f, +0x29, 0x59, 0x2f, 0xf7, 0x02, 0x53, 0x7f, 0xef, 0x00, 0x5d, 0xf4, 0x7 +f, 0x00, 0x24, 0x7d, 0xdb, 0x00, 0x13, 0x2f, 0xdf, 0x09, 0x54, 0x35, +0xfd, 0x04, 0xa9, 0xcb, 0x7f, 0x00, 0x86, 0x9c, 0xff };

— Ken

Replies are listed 'Best First'.
Re: Creating X BitMap (XBM) images with directional gradients
by etj (Priest) on Aug 08, 2024 at 02:35 UTC
    It will surprise few, if any, of you, that I had a go at recreating this with PDL. Some moderately neat things that came out of this:
    • the format Ken made of the bit-patterns resemble a lower-triangular matrix of 1s, but shuffled along each line
    • that suggested to me that creating such a lower-triangular matrix using tritosquare, then using GSL to shuffle each line, would be super-easy - indeed, barely even an inconvenience
    • then I'd be able to write the transposed (or whatever) matrix as an XBM
    • it turned out PDL::GSL::RNG didn't support shuffling along just one dimension, and calling ran_shuffle on slices didn't work at all
    • it turned out PDL::IO::Pic didn't support XBM at all
    The latter two problems have been fixed on PDL git master and will be released soon(ish), once I've finished updating PDL::Graphics::Simple to support the needs of various main-PDL demos (and adding/fixing any PDL deficiencies that result from that), after which I will be generating web-output versions of all the PDL demos and put them on the website. In the meantime, if you have the lust for life to use PDL on git master, you can run the script below to make various sizes and orientations of XBMs fading about the place!
    #!/usr/bin/env perl use 5.032; use warnings; use autodie ':all'; use Path::Tiny; use PDL; use PDL::GSL::RNG; die "Usage: $0 name\n" unless @ARGV == 1; my $name = $ARGV[0]; my ($base, $size, $orient) = split /_/, $name; my %bits_for = (s => 16, m => 32, l => 48, b => 96); die "Unknown size '$size'" unless exists $bits_for{$size}; my $bits = $bits_for{$size}; my $matrix = create_matrix($bits); my $oriented = orient_matrix($orient, $matrix); my $filename = get_filename($name, $bits); write_xbm($filename, $oriented); sub create_matrix { my ($bits) = @_; my $ones = ones(byte, ($bits+1)*$bits/2)->tritosquare; my $rng = PDL::GSL::RNG->new('mt19937'); $rng->ran_shuffle_1d($ones); $ones; } my %DISPATCH_TABLE; BEGIN { %DISPATCH_TABLE = ( n => sub { $_[0]->copy }, s => sub { $_[0]->slice(',-1:0')->sever }, e => sub { $_[0]->transpose->sever }, w => sub { $_[0]->slice(',-1:0')->transpose->sever }, ); } sub orient_matrix { my ($orient, $matrix) = @_; my $code = $DISPATCH_TABLE{$orient} or die "Unknown orientation '$orient'"; return $code->($matrix); } sub write_xbm { my ($filename, $matrix) = @_; $matrix->wpic($filename); } sub get_filename { my ($name, $size) = @_; my $dir = '.'; my @files = do { opendir(my $dh, $dir) or die "$dir: $!"; grep /^\Q$name\E_\d{4}/, readdir $dh; }; my $index = '0000'; if (@files) { $index = (sort { $b cmp $a } map /^\Q$name\E_(\d{4})/, @files) +[0]; ++$index; } my $indexed_name = $name . '_' . $index; my $xbm_file = $indexed_name . '.xbm'; return path($dir, $xbm_file); }
Re: Creating X BitMap (XBM) images with directional gradients
by etj (Priest) on Aug 07, 2024 at 18:19 UTC
    The XBM this program makes is obviously valid in some sense, but ImageMagick doesn't like it (nor does NetPBM). To fix that, write_xbm needs tweaking to replace this:
    $fh->say("static unsigned char ${indexed_name}_bits[] = { " . +join(', ', $hexes->@*) . ' };');
    with:
    $fh->say("static unsigned char ${indexed_name}_bits[] = {"); $fh->say(join(', ', $hexes->@*)); $fh->say('};');
      wouldn't the only difference of the two be the space after the opening squiggle, and the space before the closing one?
        Yes. The two image libraries referred to don't seem to be excellent at parsing C code (which isn't entirely unreasonable), so the above change gives them a little extra help.
Re: Creating X BitMap (XBM) images with directional gradients
by NERDVANA (Priest) on Aug 08, 2024 at 04:30 UTC
      Yowzer, nearly 200 lines!

      That in turn reminds me that the last Big Push™ I did for PDL::Graphics::Simple was the simple (ha!) task of making it handle contours. That featured moving the contour_segments operation out of TriD into ImageND, then rewriting it from 100 lines down to 19 so I could understand it (it's pretty much Marching Squares), then (because the line-segments are generated in a single pass) saving the amount of data passed to Gnuplot (etc) by joining them up into polylines with a path_join operation. Then writing my own contour_polylines algorithm because all the published ones were for computer vision stuff, not making polylines.

      I can only imagine how much harder that would have been without the facilities from array programming. Would your spatial thingummy benefit from being a PDL thingummy?

        I think it would benefit the most from some kind of CAD package that can clip polygons for me, so I don't have to do all the math manually. I was rendering semi-transparent, so I needed to prevent polygon overlap. (or use a different technique like rendering to a texture then rendering the texture semi-transparent, but that gets into zooming artifacts and awkwardness) There was also the problem where roads can turn at sharp bends and the default polygons for that look horrible without some sort of smoothing around the corner. And UGH, intersections...

        Geo::SpatialDB itself is just a database of things described in lat/lon, designed to be loaded tile-by-tile at varying levels of detail for the zoom level. I don't think that's really a PDL problem. The polygons it exports could be, if that's what a rendering pipeline wants to consume. But, PDL would need the polygon objects to be available without GLUT installed, because one of the use cases is server-side rendering.

Re: Creating X BitMap (XBM) images with directional gradients
by kcott (Archbishop) on Aug 09, 2024 at 08:27 UTC

    A blast from the past! :-)

    This is a general reply to etj, soonix and NERDVANA who commented on, and discussed, my post from well over three years ago. Thankyou very much for the continued interest.

    I've tried many times over the years to install PDL (on my Cygwin platform) without success. I've just tried it again, with Perl v5.40.0, and it worked. It did have some problems with OpenGL modules — probably due to missing libraries — but I do have PDL and PDL::GSL::RNG.

    So, a future exercise is to go through PDL::Course. That looks like a lot of work and not something I'll be doing this weekend. However, thanks for prompting me to try the PDL installation again. :-)

    — Ken

      I'm glad it worked! If you don't have the latest OpenGL and OpenGL::GLUT, it won't build the TriD stuff. Sadly, to get it to do so, you'd have to rebuild/install the whole thing after installing those two. I'd be grateful for feedback on whether that worked, if you do try it.

      I remember being rather put off by the concept of a "PDL course", and never actually did it explicitly. I'd say though that these days I'm somewhat familiar with the whole thing. But a great starting point is just to run the demos; list them with the demo command in perldl. They tell you what they're doing as they go along. Again, I'd be very grateful for feedback on things you think are bad, unclear, or confusing, or could be better.

        G'day etj,

        Firstly, my apologies for the very late response. This is first time I've logged in for over six months. I've been dealing with a huge amount of real-life issues. I'm hoping to get back to regular PM attendance later this year.

        Thanks for your reply re OpenGL. That's not something I'm likely to get to any time soon but it's not off the cards. I'll try and remember to provide you with some feedback when it does happen.

        — Ken

Re: Creating X BitMap (XBM) images with directional gradients
by cavac (Prior) on May 20, 2021 at 13:39 UTC

    Interesting variant of a switch statement in orient_matrix(). I don't believe i've seen that construct before.

    Thanks!

    perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'

      G'day cavac,

      Sorry for the slow response: I've been unwell recently and haven't logged in for a few days.

      "I don't believe i've seen that construct before."

      I'm fairly certain I first saw that type of construct in the original Camel Book back in the '90s; I use it occasionally when it seems appropriate. There's currently similar code in "perlsyn: Basic BLOCKs".

      The code in "perlsyn: Switch Statements" does much the same thing; however, I don't use the experimental given/when (switch) feature.

      Anyway, thanks for looking and leaving a comment (++).

      — Ken

        Another Way To Do It™ (tested and works):
        my %DISPATCH_TABLE; BEGIN { %DISPATCH_TABLE = ( n => sub { my ($oriented, $matrix) = @_; push $oriented->@*, $matrix->[$_] for reverse 0 .. $matrix->$#*; }, s => sub { my ($oriented, $matrix) = @_; push $oriented->@*, $matrix->[$_] for 0 .. $matrix->$#*; }, e => sub { my ($oriented, $matrix) = @_; for my $x (0 .. $matrix->$#*) { push $oriented->@*, my $col = []; for my $y (0 .. $matrix->[$x]->$#*) { push $col->@*, $matrix->[$y][$x]; } } }, w => sub { my ($oriented, $matrix) = @_; for my $x (0 .. $matrix->$#*) { push $oriented->@*, my $col = []; for my $y (reverse 0 .. $matrix->[$x]->$#*) { push $col->@*, $matrix->[$y][$x]; } } }, ); } sub orient_matrix { my ($orient, $matrix) = @_; my $code = $DISPATCH_TABLE{$orient} or die "Unknown orientation '$orient'"; $code->(my $oriented = [], $matrix); return $oriented; }

        Uhm, seems like i must have skipped (or since forgotten) a few chapters.

        That's the really nice thing in Perl. "No matter what it is, you can use it in a boolean expression"

        perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'