#! /usr/bin/perl # # Copyright (c) 2004 Matthias Bauer # # Permission is hereby granted, free of charge, to any person obtaining a cop # # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # # Nodes in our tree package node; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; $self->{0} = undef; # left child $self->{1} = undef; # right child $self->{pos} = []; # position in img $self->{colors} = []; # colors of links to children $self->{level} = undef; # depth $self->{tag} = undef; # path to the node $self->{label} = undef; # optional text to print next to $self->{caption} = undef; # dto. to print below return bless $self, $class; } 1; use GD; use strict; use warnings "all"; use Math::Trig; # GD is sooo sick. Colors are allocated _per image_. my @dim = ( 800, 640 ); my $b = new GD::Image(@dim); my $white = $b->colorAllocate( 255, 255, 255 ); my $black = $b->colorAllocate( 0, 0, 0 ); # For text in the image my $font = "/usr/local/lib/X11/fonts/mscorefonts/arial.ttf"; # offset of rotated text below a node my $offset = 3; # Creates a tree of $depth in an image of ($width, $height) sub mktree { my ( $width, $height, $depth ) = @_; my $root = new node; $root->{pos} = [ $width / 2, 0 ]; $root->{level} = 0; $root->{tag} = ""; # it's divine ... sub btr { my ( $n, $l ) = @_; return if ( $l >= $depth ); $n->{0} = new node; $n->{1} = new node; $n->{colors} = [ $black, $black ]; $n->{0}->{level} = $n->{1}->{level} = $l; $n->{0}->{tag} = $n->{tag} . "0"; $n->{1}->{tag} = $n->{tag} . "1"; $n->{0}->{pos} = [ $n->{pos}->[0] - ( $width * ( 0.5**( $l + 1 ) ) ), $n->{pos}->[1] + ( ( $height / $depth ) / $l ) ]; $n->{1}->{pos} = [ $n->{pos}->[0] + ( $width * ( 0.5**( $l + 1 ) ) ), $n->{pos}->[1] + ( ( $height / $depth ) / $l ) ]; btr( $n->{0}, $l + 1 ); btr( $n->{1}, $l + 1 ); return; } btr( $root, 1 ); return $root; } # Draw it sub rekdraw { my ( $img, $n ) = @_; my @box; if ( defined $n->{label} ) { print STDERR "$n->{label}\n"; @box = GD::Image->stringFT( $black, $font, 10, 0, @{ $n->{pos} }, $n->{label} ); unless (@box) { print STDERR "Damn: $@\n"; exit 1; } $img->stringFT( $black, $font, 10, 0, @{ $n->{pos} }, $n->{label} ); } if ( defined $n->{caption} ) { print STDERR "$n->{caption}\n"; @box = GD::Image->stringFT( $black, $font, 10, 1.5 * pi, @{ $n->{pos} }, $n->{caption} ); unless (@box) { print STDERR "Damn: $@\n"; exit 1; } my ( $x, $y ) = @{ $n->{pos} }; $y += $offset; die "empty box" if ( $box[4] - $box[0] == 0 ); # Center the caption $x -= ( $box[4] - $box[0] ) / 2; $img->stringFT( $black, $font, 10, 1.5 * pi, $x, $y, $n->{caption} ); } return unless $n->{0}; $img->line( $n->{pos}->[0], $n->{pos}->[1], $n->{0}->{pos}->[0], $n->{0}->{pos}->[1], $n->{colors}->[0] ); $img->line( $n->{pos}->[0], $n->{pos}->[1], $n->{1}->{pos}->[0], $n->{1}->{pos}->[1], $n->{colors}->[1] ); rekdraw( $img, $n->{0} ); rekdraw( $img, $n->{1} ); return; } # draws the edges along a given path of zeros and ones in # the given color. sub markpath { my ( $path, $root, $col ) = @_; my $n; $n = $root; foreach $b ( split //, $path ) { $n->{colors}->[$b] = $col; $n = $n->{$b}; } return $root; } # puts the given labels (@names) to the nodes along the # given path. sub labelpath { my ( $path, $root, @names ) = @_; my $n; $n = $root; foreach $b ( split //, $path ) { $n->{label} = shift @names; $n = $n->{$b}; } $n->{label} = shift @names; return $root; } # puts a caption to a node at the end of the given path. # the caption is printed vertically below the node. sub leafcaption { my ( $path, $root, $cap ) = @_; my $n; $n = $root; foreach $b ( split //, $path ) { $n = $n->{$b} if exists $n->{$b}; } $n->{caption} = $cap; } # draws the edges to the children of a give node (by path) # in the given color. sub pathpairs { my ( $path, $root, $col ) = @_; my $n; $n = $root; foreach $b ( split //, $path ) { $n->{colors}->[0] = $col; $n->{colors}->[1] = $col; $n = $n->{$b}; } return $root; } # Almost Haskelly. It's possible to rewrite almost any # function here by supplying the right $q to forall. # iterator function over all nodes of a tree rooted in root sub forall { my ( $root, $q ) = @_; sub ftrk { my $n = shift; &$q($n); unless ( defined $n->{0} ) { return; } ftrk( $n->{0}, $q ); ftrk( $n->{1}, $q ); return; } ftrk( $root, $q ); } # draws all edges between nodes of layer $l and $l+1 in # the given color. sub colorlayer { my ( $l, $root, $col ) = @_; return if $l < 0; sub rk { my ( $n, $d ) = @_; if ( $d == $l ) { $n->{colors}->[0] = $col; $n->{colors}->[1] = $col; return; } else { rk( $n->{0}, $d + 1 ); rk( $n->{1}, $d + 1 ); } return; } rk( $root, 0 ); return $root; } # XXX No idea why this recurses endlessly sub copytree { my $src = shift; my $dst = new node; foreach my $att ( "label", "caption", "pos", "tag", "level" ) { $dst->{$att} = $src->{$att}; } my @colors = @{ $src->{colors} }; $dst->{colors} = \@colors; if ( defined $src->{0} ) { $dst->{0} = copytree $src->{0}; } if ( defined $src->{1} ) { $dst->{1} = copytree $src->{1}; } return $dst; } # returns an array of leaves sub leaves { my $root = shift; my @l; sub lrk { my ( $n, $rl ) = @_; unless ( $n->{0} ) { push @$rl, $n; return; } else { lrk( $n->{0}, $rl ); lrk( $n->{1}, $rl ); } return; } lrk( $root, \@l ); return @l; } # puts captions on leaves, consisting of $labelbase and # a number. sub doclabeltree { my ( $root, $labelbase ) = @_; my @leaves = leaves($root); my $c = 0; foreach my $l (@leaves) { $l->{caption} = "$labelbase$c"; $c++; } return $root; } # XXX Does not work because of GD's aforementioned sickness sub outtree { my ( $root, $fname ) = @_; my $img = $b->clone; rekdraw( $img, $root ); open F, ">$fname.png" or die "Could not write to $fname"; print F $img->png; close F; } # Tests my $red = $b->colorAllocate( 255, 0, 0 ); my $foo = mktree( @dim, 7 ); my $outfile = $ARGV[0] or die "please give a file to write to"; pathpairs( "0110110", $foo, $red ); labelpath( "0110110", $foo, ( "", "0", "1", "1", "0", "1", "1", "0" ) ); leafcaption( "011011", $foo, "final" ); colorlayer( 2, $foo, $red ); rekdraw( $b, $foo ); open F, ">$outfile" or die "Could not write to $outfile"; print F $b->png; close F;