comp.lang.perl.tk-canvas_as_scrollable_image_map

This is part of The Pile, a partial archive of some open source mailing lists and newsgroups.



From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 20 Feb 2004 08:52:12 GMT

I have a scrollable canvas widget that displays an image (actually it's
the tubemap of London). What I now want to achieve is that this widget
has specially marked regions (namely the stations) that react to
mouse-clicks (like an image-map in HTML really). What I was able to do
was creating Rectangle items. However, it seems that they need to have a
'-fill' otherwise I can only bind callbacks that are triggered when I
click on the borderline of these rectangles. But when I fill them, this
covers the part of the image that is below the rectangle.

What I have is roughly this:


my $photo = $main->Photo( -file   => $file,
			  -format => "gif", );
			  
$canvas->createImage(0, 0, -image  => $photo, 
			   -anchor => 'nw', 
			   -tags   => 'map');
			   
$canvas->bind( qw/map <ButtonPress-1>/ => \&register_point);
$canvas->bind( qw/map <ButtonPress-3>/ => \&delete_rec);

{
    my (@start, @end);
    my %recs;
    sub register_point {
	my $c = shift;
	my $e = $c->XEvent;
	if (!@start) {
	    @start = ($c->canvasx($e->x), $c->canvasy($e->y));
	} else {
	    @end = ($c->canvasx($e->x), $c->canvasy($e->y));
	    make_rec();
	}
    }
    
    sub make_rec {
	my $flag = join ",", @start, @end;
	$canvas->createRectangle(@start, @end, 
				    -outline => 'black',
				    -tags    => $flag);
	$canvas->pack;
	@start = ();
	$recs{ $flag }++;
    }

    sub delete_rec {
	my $c = shift;
	my $e = $c->XEvent;
	my ($x, $y) = ($c->canvasx($e->x), $c->canvasy($e->y));
	for (keys %recs) {
	    my ($x1, $y1, $x2, $y2) = split /,/, $_;
	    # check whether the click was inside a rectangle
	    if ($x >= $x1 && $x <= $x2 &&
		$y >= $y1 && $y <= $y2) {
		my @rec = $canvas->find('withtag', $_); 
		$canvas->delete($rec[0]);
		$canvas->pack;
		delete $recs{ $_ };
		return;
	    }
	}
    }
}

That means a rectangle is created with two left mouseclicks and with a
right click into the rectangle it is deleted. When I use a fill for the
rectangles, the delete_rec() function can be simplified quite a bit to

    sub delete_rec { 
	my $c = shift; 
	my $i = $c->find( qw/withtag current/ );
	$canvas->delete($i);
	$canvas->pack 
    }

But a fill ruins my image so it appears that I can't use that.

Summing that up, the above shall be a sort of map-editor. After creating
all the rectangles, it should write a file of roughly this format:

    <station-name1>  x1 y1 x2 y2
    <station-name2>  ...

so that later I can load the image along with this file and create an
image-map according to the coordinates attached to each station name.

If any, what would be a good way to attach invisible region information
to a canvas?

===

From: zentara <zentara@highstream.net>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 09:52:44 -0500

On 20 Feb 2004 08:52:12 GMT, "Tassilo v. Parseval"
<tassilo.parseval@rwth-aachen.de> wrote:

>If any, what would be a good way to attach invisible region information
>to a canvas?

I tried to figure out what you are trying to do from your description,
and I'm not entirely clear, although I'm sure you are.

I think if I was starting such a project from scratch, I would break the
large map into smaller sections, then tile them into the canvas.
In the loop to add them to the canvas, I would name each tile and make
it a key in a hash. Then put all your hidden info for each tile into the
hash.  Then whenever the tile is selected for something, you can refer
to it's hash entry for the hidden info.

You might also be to use the "enter" and "leave"  bindings to make
callbacks when the mouse goes over a tile or leaves it, based on 'tags'.


===







			       

     
                          
          
      









From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 20 Feb 2004 16:54:09 GMT

Also sprach zentara:
> On 20 Feb 2004 08:52:12 GMT, "Tassilo v. Parseval"
><tassilo.parseval@rwth-aachen.de> wrote:
> 
>>If any, what would be a good way to attach invisible region information
>>to a canvas?
> 
> I tried to figure out what you are trying to do from your description,
> and I'm not entirely clear, although I'm sure you are.
> 
> I think if I was starting such a project from scratch, I would break the
> large map into smaller sections, then tile them into the canvas.
> In the loop to add them to the canvas, I would name each tile and make
> it a key in a hash. Then put all your hidden info for each tile into the
> hash.  Then whenever the tile is selected for something, you can refer
> to it's hash entry for the hidden info.

Reassembling the map out of smaller images is probably what I will be
doing now. It's a little more tricky for me as I can't just slice out
regular tiles. I need to cut out all the stations.

> You might also be to use the "enter" and "leave"  bindings to make
> callbacks when the mouse goes over a tile or leaves it, based on 'tags'.
> 
> From what I can determine from your code snippet, you are working with
> the whole image, and creating rectangles overlayed on it. I don't see
> any advantage to that, since all the regions are sort of anonymous.
> Using named tiles would give you alot more control.

In the complete program a dialogbox pops up when I've drawn a rectangle
and asks me for the name of the station which I add as another tag to
the rectangle (besides the coordinates "x1,y1,x2,y2").

> Here is a script I wrote to "slice and dice" an image. I've reassembled
> them to give html output, but you could rearrange the loop to
> tile them onto a canvas, keeping their names and tile locations as hash
> entries, and give them tags for "Enter" and "Leave" bindings. 
> (There is also the "npuzzle" game in the Tk widget demo).

[...]

> I hope it gives you some ideas.

Yes, it does, thank you. I will take the core of it and put it in my
program. I'll still be drawing all the rectangles I need and give tags
to them. For each drawn rectangle however I'll use your script as a
template to generate the slices accordingly. Each rectangle becomes a
slice and the filename will be something like "$x1-$y1-$x2-$y2.gif" or
so so that I can later insert them again easily.

===

From: "Dean Arnold" <darnold@presicient.com>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 18:32:25 GMT

IIRC, Mssr. Lidie did a perl.com article on using transparent
regions within a canvas for click thru purposes. If that doesn't
do it, then you might look at his article/demo app about D&D
from one canvas to another,
http://www.perl.com/pub/a/2001/12/11/perltk.html
 there should be some code
you can borrow from there.

===

From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 20 Feb 2004 20:37:05 GMT

Also sprach Dean Arnold:

> IIRC, Mssr. Lidie did a perl.com article on using transparent
> regions within a canvas for click thru purposes. If that doesn't
> do it, then you might look at his article/demo app about D&D
> from one canvas to another,
> http://www.perl.com/pub/a/2001/12/11/perltk.html
>  there should be some code
> you can borrow from there.

Very nice. I can do with some canvas-related articles now. D&D is
another thing that I will almost certainly need.

===

From: "Ala Qumsieh" <xxala_qumsiehxx@xxyahooxx.com>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 18:35:20 GMT

"Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> wrote in message
news:c14hrs$hmk$1@nets3.rz.RWTH-Aachen.DE...
> 
>
> I have a scrollable canvas widget that displays an image (actually it's
> the tubemap of London). What I now want to achieve is that this widget

*chuckle*
I always find it funny when I hear the word 'tube'. I'm just used to subway
:)

> has specially marked regions (namely the stations) that react to
> mouse-clicks (like an image-map in HTML really). What I was able to do
> was creating Rectangle items. However, it seems that they need to have a
> '-fill' otherwise I can only bind callbacks that are triggered when I
> click on the borderline of these rectangles. But when I fill them, this
> covers the part of the image that is below the rectangle.

Correct. To fix this, you can use a transparent fill. Just add:

    -fill    => 'black', # or any color .. doesn't matter
    -stipple => 'transparent',

to your rectangles.

===
From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 20 Feb 2004 20:37:04 GMT

Also sprach Ala Qumsieh:

> "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> wrote in message
> news:c14hrs$hmk$1@nets3.rz.RWTH-Aachen.DE...
>> 
>>
>> I have a scrollable canvas widget that displays an image (actually it's
>> the tubemap of London). What I now want to achieve is that this widget
> 
> *chuckle*
> I always find it funny when I hear the word 'tube'. I'm just used to subway
>:)

It isn't less comical when your native language is German. It always
reminds me of the little container that holds the toothpaste.

>> has specially marked regions (namely the stations) that react to
>> mouse-clicks (like an image-map in HTML really). What I was able to do
>> was creating Rectangle items. However, it seems that they need to have a
>> '-fill' otherwise I can only bind callbacks that are triggered when I
>> click on the borderline of these rectangles. But when I fill them, this
>> covers the part of the image that is below the rectangle.
> 
> Correct. To fix this, you can use a transparent fill. Just add:
> 
>     -fill    => 'black', # or any color .. doesn't matter
>     -stipple => 'transparent',
> 
> to your rectangles.

So that is what -stipple is about! I also did a quick grep for
transparency in various spots but didn't find it as a valid bitmap
pattern.

Needless to say, the above does exactly what I need. Thank you!

===
From: zentara <zentara@highstream.net>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 19:07:33 -0500

On 20 Feb 2004 08:52:12 GMT, "Tassilo v. Parseval"
<tassilo.parseval@rwth-aachen.de> wrote:

>
>
>I have a scrollable canvas widget that displays an image (actually it's
>the tubemap of London). What I now want to achieve is that this widget
>has specially marked regions (namely the stations) that react to
>mouse-clicks (like an image-map in HTML really). What I was able to do
>was creating Rectangle items. However, it seems that they need to have a
>'-fill' otherwise I can only bind callbacks that are triggered when I
>click on the borderline of these rectangles. But when I fill them, this
>covers the part of the image that is below the rectangle.

 building on my previous idea, here is an image map system for
a canvas. Just run the script on a jpg image.

It dosn't make any temporary files, it keeps all it tile images in a
hash.  Now this causes some problems the way I have it setup,
because the tiles are stored as binary in the hash, that is why I
have 3 hashes, one for the Imager tiles, one for the canvas tiles,
and one for the rectangles. They probably could be combined but I was 
getting errors when I tried to combine them, probably because of
the binary data.

Anyways, it works pretty well, except for a slight overlap shading of
the highlighted rectangles.
#####################################################
#!/usr/bin/perl
use warnings;
use strict;
use Imager;
use Tk;
use Tk::JPEG;
use MIME::Base64;

my %tiles;
my %rects;
my $file = shift || die "need filename\n";
my $tempname = $file;
$tempname =~ s/^(.+)(\.\w+)$/$1/;
print "$tempname\n";

#set tile size adjustment
my $x = 100;
my $y = 100;

my $image = Imager->new(); 
$image->open(file=>$file, type=>'jpeg') or die $image->errstr();
my $width = $image->getwidth()+40;
my $height = $image->getheight()+40;
print "width->$width   height->$height\n";

my $mw = MainWindow->new;
$mw->geometry($width.'x'.$height.'+100+100');
my $canvas = $mw->Scrolled('Canvas',-bg=>'grey', 
            -width => $width, -height => $height)->pack;

my $rows = int($height/$y +1) - 1; #make it 0 based
my $cols = int($width/$x + 1) - 1;
print "rows->$rows  cols->$cols\n";

my %data;
foreach my $row(0..$rows){
    foreach my $col(0..$cols){ 
      my $imageout = Imager->new(xsize=>$x, ysize=>$y, type=>'direct'); 
                          
      $imageout = $image->crop(left=>$col*$y, right=>$col*$y+$y, 
                                top=>$row*$x, bottom=>$row*$x+$x);
          
      $imageout->write(type=>'jpeg', data=>\$data{$row}{$col});
           #or warn $imageout->errstr;
  
 
      my $image = $mw->Photo(-data =>encode_base64($data{$row}{$col}));	
      $tiles{$row}{$col} = $canvas->createImage($col*100,$row*100,
	                               -image => $image,-anchor => 'nw',
		        	        );
					
      $rects{$row}{$col} =
$canvas->createRectangle($col*100,$row*100,$col*100+100,$row*100+100,
                                  -width=> 0,
				  -outline=>'grey',
				  	    );
				    
$canvas->bind($tiles{$row}{$col}, '<Enter>',
                                sub {
$canvas->itemconfigure($rects{$row}{$col}, 
	                         -outline =>"yellow",
	                        -width => 5); 
	print "Entered $row $col\n";				 
			 } );


$canvas->bind($tiles{$row}{$col}, '<Leave>',
                                sub {
$canvas->itemconfigure($rects{$row}{$col}, 
                                       -outline => "grey",
		 -state => 'disabled',
		 -width => 0,
 				 ); } );
$canvas->update;
   }
}
MainLoop;
__END__

===

From: zentara <zentara@highstream.net>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 19:37:57 -0500

zentara <zentara@highstream.net> wrote:

>"Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> wrote:

>Hi, building on my previous idea, here is an image map system for
>a canvas. Just run the script on a jpg image.

Well here is an improved version of my previous script. I've combined
all 3 hashes , so the is now just one %tiles hash, with all the data.

I've also modified the rectangles setup to adjust to whatever $x and $y
value you want.

#!/usr/bin/perl
use warnings;
use strict;
use Imager;
use Tk;
use Tk::JPEG;
use MIME::Base64;

my %tiles;
my $file = shift || die "need jpg filename\n";
my $tempname = $file;
$tempname =~ s/^(.+)(\.\w+)$/$1/;
print "$tempname\n";

#set tile size adjustment
my $x = 50;
my $y = 50;

my $image = Imager->new();
$image->open( file => $file, type => 'jpeg' ) or die $image->errstr();
my $width  = $image->getwidth() + 40;
my $height = $image->getheight() + 40;
print "width->$width   height->$height\n";

my $mw = MainWindow->new;
$mw->geometry( $width . 'x' . $height . '+100+100' );
my $canvas = $mw->Scrolled(
    'Canvas',
    -bg     => 'grey',
    -width  => $width,
    -height => $height
)->pack;

my $rows = int( $height / $y + 1 ) - 1;    #make it 0 based
my $cols = int( $width / $x + 1 ) - 1;
print "rows->$rows  cols->$cols\n";

foreach my $row ( 0 .. $rows ) {
    foreach my $col ( 0 .. $cols ) {
        my $imageout =
          Imager->new( xsize => $x, ysize => $y, type => 'direct' );

        $imageout = $image->crop(
            left   => $col * $y,
            right  => $col * $y + $y,
            top    => $row * $x,
            bottom => $row * $x + $x
        );

        $imageout->write( type => 'jpeg', data => \$tiles{$row}{$col} );

        #or warn $imageout->errstr;

        $tiles{$row}{$col} = encode_base64( $tiles{$row}{$col} );

        my $image = $mw->Photo( -data => $tiles{$row}{$col} );
        $tiles{$row}{$col} = $canvas->createImage(
            $col * $x, $row * $y,
            -image  => $image,
            -anchor => 'nw',
        );

        $tiles{'r'}{$row}{$col} = $canvas->createRectangle(
            $col * $x, $row * $y, $col * $x + $x, $row * $y + $y,
            -width   => 0,
            -outline => 'grey',
        );

        $canvas->bind(
            $tiles{$row}{$col},
            '<Enter>',
            sub {
                $canvas->itemconfigure(
                    $tiles{'r'}{$row}{$col},
                    -outline => "yellow",
                    -width   => 5
                );
                print "Entered $row $col\n";
            }
        );

        $canvas->bind(
            $tiles{$row}{$col},
            '<Leave>',
            sub {
                $canvas->itemconfigure(
                    $tiles{'r'}{$row}{$col},
                    -outline => "grey",
                    -state   => 'disabled',
                    -width   => 0,
                );
            }
        );
        $canvas->update;
    }
}
MainLoop;
__END__

===

From: $_@_.%_
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: Fri, 20 Feb 2004 10:14:46 GMT
                    
>I have a scrollable canvas widget that displays an image (actually it's
>the tubemap of London). What I now want to achieve is that this widget
>has specially marked regions (namely the stations) that react to
>mouse-clicks (like an image-map in HTML really). What I was able to do
>was creating Rectangle items. However, it seems that they need to have a
>'-fill' otherwise I can only bind callbacks that are triggered when I
>click on the borderline of these rectangles. But when I fill them, this
>covers the part of the image that is below the rectangle.
>
I've never used canvas, but scanning the docs, im wondering
if perhaps you could use the createWindow method and place
a button in that window, then put an image on the button.
the image could be the section of the tubemap which would be
covered by the button.


===
From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 20 Feb 2004 10:37:15 GMT

Also sprach $_@_.%_:

>>I have a scrollable canvas widget that displays an image (actually it's
>>the tubemap of London). What I now want to achieve is that this widget
>>has specially marked regions (namely the stations) that react to
>>mouse-clicks (like an image-map in HTML really). What I was able to do
>>was creating Rectangle items. However, it seems that they need to have a
>>'-fill' otherwise I can only bind callbacks that are triggered when I
>>click on the borderline of these rectangles. But when I fill them, this
>>covers the part of the image that is below the rectangle.
>>
> I've never used canvas, but scanning the docs, im wondering
> if perhaps you could use the createWindow method and place
> a button in that window, then put an image on the button.
> the image could be the section of the tubemap which would be
> covered by the button.

Hmmh, that sounds scary. I think it would require partitioning the image
into hundres of small images (one for each station). If I did that, it'd
probably be more feasible to place these partitions as image items onto
the main map and bind the mouse to these images (sort of like a jigsaw). 

I'd have to see how well Tk can deal with many small images. It could be
quite a challenge memory- and CPU-wise.

===

From: Slaven Rezic <slaven@rezic.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 22 Feb 2004 12:41:27 +0100

"Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> writes:

> Also sprach $_@_.%_:
> 
> >>I have a scrollable canvas widget that displays an image (actually it's
> >>the tubemap of London). What I now want to achieve is that this widget
> >>has specially marked regions (namely the stations) that react to
> >>mouse-clicks (like an image-map in HTML really). What I was able to do
> >>was creating Rectangle items. However, it seems that they need to have a
> >>'-fill' otherwise I can only bind callbacks that are triggered when I
> >>click on the borderline of these rectangles. But when I fill them, this
> >>covers the part of the image that is below the rectangle.
> >>
> > I've never used canvas, but scanning the docs, im wondering
> > if perhaps you could use the createWindow method and place
> > a button in that window, then put an image on the button.
> > the image could be the section of the tubemap which would be
> > covered by the button.
> 
> Hmmh, that sounds scary. I think it would require partitioning the image
> into hundres of small images (one for each station). If I did that, it'd
> probably be more feasible to place these partitions as image items onto
> the main map and bind the mouse to these images (sort of like a jigsaw). 
> 
> I'd have to see how well Tk can deal with many small images. It could be
> quite a challenge memory- and CPU-wise.

If "many" is a few thousand, then it should be no problem. Also there
be no large difference memory-wise between one large image and many
smaller images --- Tk always holds images uncompressed with four
channels (32bits) in memory. If you're happy with just 256 colors,
then you could use Tk::Pixmap instead of Tk::Photo to get a smaller
memory footprint.

Is there any reason why you're not using the vector-oriented features
of Tk::Canvas, that is createLine and others?

===
From: "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 22 Feb 2004 12:04:06 GMT

Also sprach Slaven Rezic:

> "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> writes:

>> Hmmh, that sounds scary. I think it would require partitioning the image
>> into hundres of small images (one for each station). If I did that, it'd
>> probably be more feasible to place these partitions as image items onto
>> the main map and bind the mouse to these images (sort of like a jigsaw). 
>> 
>> I'd have to see how well Tk can deal with many small images. It could be
>> quite a challenge memory- and CPU-wise.
> 
> If "many" is a few thousand, then it should be no problem. Also there
> be no large difference memory-wise between one large image and many
> smaller images --- Tk always holds images uncompressed with four
> channels (32bits) in memory. If you're happy with just 256 colors,
> then you could use Tk::Pixmap instead of Tk::Photo to get a smaller
> memory footprint.

Alright, good to know. And yes, then I will be using Tk::Pixmap instead
of ::Photo. For a tubemap, 256 colors should be more than enough.

> Is there any reason why you're not using the vector-oriented features
> of Tk::Canvas, that is createLine and others?

The main reason is that I don't want to draw the tubemap myself. I am
not talented with such things and the result will look dreadful. I'd
rather want to use the nice gif image I already have.


===

From: Slaven Rezic <slaven@rezic.de>
Subject: Re: Turning a canvas into an image-map
Newsgroups: comp.lang.perl.tk
Date: 22 Feb 2004 15:32:30 +0100

"Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> writes:

> Also sprach Slaven Rezic:
> 
> > "Tassilo v. Parseval" <tassilo.parseval@rwth-aachen.de> writes:
> 
> >> Hmmh, that sounds scary. I think it would require partitioning the image
> >> into hundres of small images (one for each station). If I did that, it'd
> >> probably be more feasible to place these partitions as image items onto
> >> the main map and bind the mouse to these images (sort of like a jigsaw). 
> >> 
> >> I'd have to see how well Tk can deal with many small images. It could be
> >> quite a challenge memory- and CPU-wise.
> > 
> > If "many" is a few thousand, then it should be no problem. Also there
> > be no large difference memory-wise between one large image and many
> > smaller images --- Tk always holds images uncompressed with four
> > channels (32bits) in memory. If you're happy with just 256 colors,
> > then you could use Tk::Pixmap instead of Tk::Photo to get a smaller
> > memory footprint.
> 
> Alright, good to know. And yes, then I will be using Tk::Pixmap instead
> of ::Photo. For a tubemap, 256 colors should be more than enough.
> 
> > Is there any reason why you're not using the vector-oriented features
> > of Tk::Canvas, that is createLine and others?
> 
> The main reason is that I don't want to draw the tubemap myself. I am
> not talented with such things and the result will look dreadful. I'd
> rather want to use the nice gif image I already have.
> 

Vectorizing an existing image *manually* shouldn't be too hard. Just
load the gif into a vector drawing program (e.g. xfig) as a background
image and then create lines on top of the image. Unfortunately there
is no Perl/Tk export for xfig (yet), but it should be possible to turn
the Tcl/Tk output into a Perl script with a couple of clever regular
expressions.

===

the rest of The Pile (a partial mailing list archive)

doom@kzsu.stanford.edu