1: package CGI::Imagemap;
2:
3: use Carp;
4: use strict;
5: use vars qw( $VERSION );
6:
7: $VERSION = '0.01';
8:
9:
10: sub new {
11: my $class = shift;
12: my $self = bless { SHAPES => [], POINTS => [], }, $class;
13: croak "odd number of parameters to CGI::Imagemap::new" if @_ % 2;
14: while (@_) {
15: my $type = shift;
16: my $config = shift;
17: croak "configuration not an array reference in CGI::Imagemap::new"
18: if ref($config) ne "ARRAY";
19: my $shape = CGI::Imagemap::Shape->new($type,$config);
20: $self->{DEFAULT} = $shape, next if $type eq 'DEFAULT';
21: push @{ $self->{SHAPES} }, $shape unless @{ $self->{POINTS} };
22: push @{ $self->{POINTS} }, $shape if $type eq 'POINT';
23: }
24: return $self;
25: }
26:
27:
28: sub query_eval {
29: my ($self, $query, $field) = @_;
30: croak "query must be CGI object in CGI::Imagemap::query_eval"
31: unless UNIVERSAL::isa($query, 'CGI');
32: my ($x,$y) = ($query->param("$field.x"), $query->param("$field.y"));
33: $self->manual_eval($x,$y);
34: }
35:
36:
37: sub manual_eval {
38: my ($self, $x, $y) = @_;
39: for (@{ $self->{SHAPES} }) {
40: if ($_->type('POINT')) {
41: my ($min,$shape) = ($_->proximity($x,$y),$_);
42: for (@{ $self->{POINTS} }) {
43: my $dst = $_->proximity($x,$y);
44: $dst < $min and ($min,$shape) = ($dst,$_);
45: }
46: $shape->execute() and return 1;
47: }
48: $_->proximity($x,$y) and $_->execute() and return 1;
49: }
50: $self->{DEFAULT} and $self->{DEFAULT}->execute();
51: return 0;
52: }
53:
54:
55:
56: package CGI::Imagemap::Shape;
57:
58: use Carp;
59:
60: my %types;
61: @types{qw( CIRCLE OVAL RECT POLY POINT DEFAULT )} = ();
62:
63: sub new {
64: my ($class, $type, $config) = @_;
65: croak "invalid shape (must be @{[ keys %types ]}) '$type'"
66: unless exists $types{uc $type};
67: croak "handler must be code ref" if ref($config->[0]) ne 'CODE';
68: my $self = bless {}, $class . "::" . uc($type);
69: $self->{HANDLER} = shift @$config;
70: $self->{COORDS} = $config;
71: return $self;
72: }
73:
74:
75: sub coords { return @{ $_[0]{COORDS} } }
76:
77:
78: sub execute { $_[0]{HANDLER}->() }
79:
80:
81:
82: package CGI::Imagemap::Shape::CIRCLE;
83:
84: use vars '@ISA';
85: @ISA = qw( CGI::Imagemap::Shape );
86:
87: sub proximity {
88: my ($shape, $x, $y) = @_;
89: my ($Cx, $Cy, $r) = $shape->coords;
90: return ($x - $Cx)**2 + ($y - $Cy)**2 <= $r**2;
91: }
92:
93:
94:
95: package CGI::Imagemap::Shape::OVAL;
96:
97: use vars '@ISA';
98: @ISA = qw( CGI::Imagemap::Shape );
99:
100: sub proximity {
101: my ($shape, $x, $y) = @_;
102: my ($Cx, $Cy, $a, $b) = $shape->coords;
103: return (($x - $Cx)/$a)**2 + (($y - $Cy)/$b)**2 <= 1;
104: }
105:
106:
107:
108: package CGI::Imagemap::Shape::RECT;
109:
110: use vars '@ISA';
111: @ISA = qw( CGI::Imagemap::Shape );
112:
113: sub proximity {
114: my ($shape, $x, $y) = @_;
115: my ($ULx, $ULy, $LRx, $LRy) = $shape->coords;
116: return ($ULx <= $x) && ($x <= $LRx) && ($ULy <= $y) && ($y <= $LRy);
117: }
118:
119:
120:
121: package CGI::Imagemap::Shape::POLY;
122:
123: use vars '@ISA';
124: @ISA = qw( CGI::Imagemap::Shape );
125:
126: sub coords {
127: my ($shape) = @_;
128: my @x = map $shape->{COORDS}[$_ * 2], 0 .. $#{ $shape->{COORDS} }/2;
129: my @y = map $shape->{COORDS}[$_ * 2 + 1], 0 .. $#{ $shape->{COORDS} }/2;
130: return (\@x, \@y);
131: }
132:
133:
134: sub proximity {
135: my ($shape, $x, $y) = @_;
136: my ($Xc, $Yc) = $shape->coords;
137: my @X = @$Xc;
138: my @Y = @$Yc;
139: my $n = @X;
140: my ($i,$j);
141: my $inside = 0;
142: # thanks to "Mastering Algorithms in Perl" (pg. 444-5)
143: # point_in_ploygon derived from Wm. Randolph Franklin
144:
145: for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
146: if (
147: (
148: (($Y[$i] <= $y) && ($y < $Y[$j])) ||
149: (($Y[$j] <= $y) && ($y < $Y[$i]))
150: )
151: and
152: ($x <
153: ($X[$j] - $X[$i]) *
154: ($y - $Y[$i]) /
155: ($Y[$j] - $Y[$i]) +
156: $X[$i]
157: )
158: ) { $inside = !$inside }
159: }
160:
161: return $inside;
162: }
163:
164:
165:
166: package CGI::Imagemap::Shape::POINT;
167:
168: use vars '@ISA';
169: @ISA = qw( CGI::Imagemap::Shape );
170:
171: sub proximity {
172: my ($shape, $x, $y) = @_;
173: my ($Px, $Py) = $shape->coords;
174: return sqrt(($Px - $x)**2 + ($Py - $y)**2);
175: }
176:
177:
178: 1;
179:
180:
181: __END__
182:
183: =head1 NAME
184:
185: CGI::Imagemap - program-handling of X,Y coordinates in an
186: image
187:
188: =head1 SYNOPSIS
189:
190: use CGI::Imagemap;
191: $request = CGI::Imagemap->new(
192: CIRCLE => [\&handler, $center_x, $center_y, $radius],
193: OVAL => [\&handler, $axis_x, $axis_y, $den_x, $den_y],
194: RECT => [\&handler, $upleft_x, $upleft_y, $lowright_x, $lowright_y],
195: POLY => [\&handler, $x0, $y0, $x1, $y1, ..., $xN, $yN],
196: POINT => [\&handler, $x, $y],
197: DEFAULT => [\&handler],
198: );
199: $found = $request->query_eval($CGIobj, $fieldname);
200: $found = $request->manual_eval($px, $py);
201: if (!$found) {
202: # resorted to DEFAULT handler
203: }
204:
205: =head1 DESCRIPTION
206:
207: Basically, you make a CGI::Imagemap object, and list (in
208: order of precedence) the zones that the imagemap should map
209: to. The shape names are CIRCLE, OVAL, RECT, POLY, POINT,
210: and DEFAULT. All POINT shapes will be compared at the same
211: time, and they should be left for last.
212:
213: The query_eval method takes a CGI query object and a
214: fieldname, and gets the fieldname.x and fieldname.y values
215: from the query object. Then it calls manual_eval. This
216: just takes the (x,y) pair of coordinates.
217:
218: The shapes take a handler (code reference) as their first
219: argument, and this handler will be executed if the point is
220: within the bounds of the shape. If the default handler is
221: resorted to, the query_eval and manual_eval methods will
222: return 0. Otherwise, they'll return 1.
223:
224: That's about it.
225:
226: =head1 AUTHOR
227:
228: Jeff "japhy" Pinyan
229: CPAN ID: PINYAN
230: japhy@pobox.com
231:
232: =cut