line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CAD::Drawing::Manipulate::Graphics; |
2
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
3
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
96488
|
use CAD::Drawing; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
use CAD::Drawing::Defined; |
6
|
|
|
|
|
|
|
use Image::Magick; |
7
|
|
|
|
|
|
|
push(@CAD::Drawing::ISA, __PACKAGE__); |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use warnings; |
10
|
|
|
|
|
|
|
use strict; |
11
|
|
|
|
|
|
|
use Carp; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=pod |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 Name |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
CAD::Drawing::Manipulate::Graphics - Gimp meets CAD? |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 AUTHOR |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Eric L. Wilhelm |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
http://scratchcomputing.com |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 COPYRIGHT |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
This module is copyright (C) 2004-2006 by Eric L. Wilhelm. Portions |
28
|
|
|
|
|
|
|
copyright (C) 2003 by Eric L. Wilhelm and A. Zahner Co. |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=head1 LICENSE |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
This module is distributed under the same terms as Perl. See the Perl |
33
|
|
|
|
|
|
|
source package for details. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
You may use this software under one of the following licenses: |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
(1) GNU General Public License |
38
|
|
|
|
|
|
|
(found at http://www.gnu.org/copyleft/gpl.html) |
39
|
|
|
|
|
|
|
(2) Artistic License |
40
|
|
|
|
|
|
|
(found at http://www.perl.com/pub/language/misc/Artistic.html) |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 NO WARRANTY |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
This software is distributed with ABSOLUTELY NO WARRANTY. The author, |
45
|
|
|
|
|
|
|
his former employer, and any other contributors will in no way be held |
46
|
|
|
|
|
|
|
liable for any loss or damages resulting from its use. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=head1 Modifications |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
The source code of this module is made freely available and |
51
|
|
|
|
|
|
|
distributable under the GPL or Artistic License. Modifications to and |
52
|
|
|
|
|
|
|
use of this software must adhere to one of these licenses. Changes to |
53
|
|
|
|
|
|
|
the code should be noted as such and this notification (as well as the |
54
|
|
|
|
|
|
|
above copyright information) must remain intact on all copies of the |
55
|
|
|
|
|
|
|
code. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Additionally, while the author is actively developing this code, |
58
|
|
|
|
|
|
|
notification of any intended changes or extensions would be most helpful |
59
|
|
|
|
|
|
|
in avoiding repeated work for all parties involved. Please contact the |
60
|
|
|
|
|
|
|
author with any such development plans. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=cut |
63
|
|
|
|
|
|
|
######################################################################## |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head1 Methods |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
All of these are CAD::Drawing methods (I force my own inheritance:) |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=cut |
70
|
|
|
|
|
|
|
######################################################################## |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head2 image_init |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Initialize the image at $addr based on the value at the fullpath key. |
75
|
|
|
|
|
|
|
This establishes the contained Image::Magick object and loads the image |
76
|
|
|
|
|
|
|
into memory in the image_handle key. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
$drw->image_init($addr); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=cut |
81
|
|
|
|
|
|
|
sub image_init { |
82
|
|
|
|
|
|
|
my $self = shift; |
83
|
|
|
|
|
|
|
my ($addr) = @_; |
84
|
|
|
|
|
|
|
($addr->{type} eq "images") or croak("item is not an image\n"); |
85
|
|
|
|
|
|
|
my $obj = $self->getobj($addr); |
86
|
|
|
|
|
|
|
my $name = $obj->{fullpath}; |
87
|
|
|
|
|
|
|
(-e $name) or croak("$name does not exist\n"); |
88
|
|
|
|
|
|
|
# print "loading $name ...\n"; |
89
|
|
|
|
|
|
|
my $im = Image::Magick->new(); |
90
|
|
|
|
|
|
|
my $err = $im->Read($name); |
91
|
|
|
|
|
|
|
$err && carp("read $name gave $err\n"); |
92
|
|
|
|
|
|
|
$obj->{image_handle} = $im; |
93
|
|
|
|
|
|
|
} # end subroutine image_init definition |
94
|
|
|
|
|
|
|
######################################################################## |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=head2 image_crop |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Crops an image and its definition (actually, changes its insert point) |
99
|
|
|
|
|
|
|
according to the points given by @crop_points (which maybe had better be |
100
|
|
|
|
|
|
|
within the object (but I don't really sweat that.)) |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
@crop_points should be in world coordinates as follows: |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
@crop_points = ( |
105
|
|
|
|
|
|
|
[$lower_left_x , $lower_left_y ], |
106
|
|
|
|
|
|
|
[$upper_right_x, $upper_right_y], |
107
|
|
|
|
|
|
|
); |
108
|
|
|
|
|
|
|
# note that you can get these as |
109
|
|
|
|
|
|
|
# ($drw->getExtentsRec($something))[0,2] |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
$drw->image_crop($addr, \@crop_points); |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
sub image_crop { |
115
|
|
|
|
|
|
|
my $dbg = 0; |
116
|
|
|
|
|
|
|
my $self = shift; |
117
|
|
|
|
|
|
|
my ($addr, $crp_pts) = @_; |
118
|
|
|
|
|
|
|
($addr->{type} eq "images") or croak("not an image\n"); |
119
|
|
|
|
|
|
|
my $obj = $self->getobj($addr); |
120
|
|
|
|
|
|
|
(ref($crp_pts) eq "ARRAY") or croak("$crp_pts is not array\n"); |
121
|
|
|
|
|
|
|
(@$crp_pts == 2) or croak("crop points should be 2\n"); |
122
|
|
|
|
|
|
|
# need upper left first |
123
|
|
|
|
|
|
|
my @crop_start = map({sprintf("%0.0f", $_)} |
124
|
|
|
|
|
|
|
$self->drw_to_img( |
125
|
|
|
|
|
|
|
[ |
126
|
|
|
|
|
|
|
$crp_pts->[0][0], # leftmost x |
127
|
|
|
|
|
|
|
$crp_pts->[1][1] # uppermost y |
128
|
|
|
|
|
|
|
], |
129
|
|
|
|
|
|
|
$addr) |
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
my @crop_stop = map({sprintf("%0.0f", $_)} |
132
|
|
|
|
|
|
|
$self->drw_to_img( |
133
|
|
|
|
|
|
|
[ |
134
|
|
|
|
|
|
|
$crp_pts->[1][0], # rightmost x |
135
|
|
|
|
|
|
|
$crp_pts->[0][1] # lowermost y |
136
|
|
|
|
|
|
|
], |
137
|
|
|
|
|
|
|
$addr) |
138
|
|
|
|
|
|
|
); |
139
|
|
|
|
|
|
|
my @ext = map({$crop_stop[$_] - $crop_start[$_]} 0,1); |
140
|
|
|
|
|
|
|
my $im = $obj->{image_handle}; |
141
|
|
|
|
|
|
|
my @old_ext = $self->get_world_image_rectangle($addr); |
142
|
|
|
|
|
|
|
$dbg && print "old extents @{$obj->{size}}\n"; |
143
|
|
|
|
|
|
|
$dbg && print "new extents: @ext\n"; |
144
|
|
|
|
|
|
|
$dbg && print "start crop: @crop_start\n"; |
145
|
|
|
|
|
|
|
$dbg && print "stop crop: @crop_stop\n"; |
146
|
|
|
|
|
|
|
$im->Crop( |
147
|
|
|
|
|
|
|
width => $ext[0], height => $ext[1], |
148
|
|
|
|
|
|
|
x => $crop_start[0], y => $crop_start[1], |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
my @sz = $im->Get("width", "height"); |
151
|
|
|
|
|
|
|
$dbg && print "check: @sz\n"; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# image processing does strange things, so we use the size reported |
154
|
|
|
|
|
|
|
# by Image::Magick to reset the insert point and size of the image |
155
|
|
|
|
|
|
|
my @new_base = ( |
156
|
|
|
|
|
|
|
$crop_start[0], |
157
|
|
|
|
|
|
|
$crop_start[1] + $sz[1], |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
my @new_pt = $self->img_to_drw(\@new_base, $addr); |
160
|
|
|
|
|
|
|
$dbg && print "old insert: @{$obj->{pt}}\n"; |
161
|
|
|
|
|
|
|
$dbg && print "new basepoint: @new_base at @new_pt\n"; |
162
|
|
|
|
|
|
|
$obj->{pt} = [@new_pt]; |
163
|
|
|
|
|
|
|
$obj->{size} = [@sz]; |
164
|
|
|
|
|
|
|
if(0) { |
165
|
|
|
|
|
|
|
my $check = CAD::Drawing->new(); |
166
|
|
|
|
|
|
|
$check->addpolygon(\@old_ext); |
167
|
|
|
|
|
|
|
$check->addrec($crp_pts, {color => "blue"}); |
168
|
|
|
|
|
|
|
$check->addpolygon( |
169
|
|
|
|
|
|
|
[$self->get_world_image_rectangle($addr)], {color => "red"} |
170
|
|
|
|
|
|
|
); |
171
|
|
|
|
|
|
|
$check->show(hang=>1); |
172
|
|
|
|
|
|
|
exit; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
} # end subroutine image_crop definition |
175
|
|
|
|
|
|
|
######################################################################## |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=head2 image_scale |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
Scales both the image and the definition by $scale, starting at |
180
|
|
|
|
|
|
|
@base_point. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
$drw->image_scale($addr, $scale, \@base_point); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=cut |
185
|
|
|
|
|
|
|
sub image_scale { |
186
|
|
|
|
|
|
|
my $self = shift; |
187
|
|
|
|
|
|
|
my ($addr, $scale, $point) = @_; |
188
|
|
|
|
|
|
|
($addr->{type} eq "images") or croak("not an image\n"); |
189
|
|
|
|
|
|
|
# this sets only the insert: |
190
|
|
|
|
|
|
|
$self->Scale($addr, $scale, $point); |
191
|
|
|
|
|
|
|
# maybe not scale image here (punt like autoheck) |
192
|
|
|
|
|
|
|
my $obj = $self->getobj($addr); |
193
|
|
|
|
|
|
|
# really should put this in the manipulate code? |
194
|
|
|
|
|
|
|
$obj->{vector}[0][0] *=$scale; |
195
|
|
|
|
|
|
|
$obj->{vector}[1][1] *=$scale; |
196
|
|
|
|
|
|
|
print "vectors now $obj->{vector}[0][0], $obj->{vector}[1][1]\n"; |
197
|
|
|
|
|
|
|
} # end subroutine image_scale definition |
198
|
|
|
|
|
|
|
######################################################################## |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head2 image_rotate |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
This leaves the definition orthoganal, expands the underlying image |
203
|
|
|
|
|
|
|
object, and resets the insert point and size properties accordingly. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
$drw->image_rotate($addr, $angle, \@point); |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
The current implementation does not handle the change to the image |
208
|
|
|
|
|
|
|
clipping boundary. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
211
|
|
|
|
|
|
|
sub image_rotate { |
212
|
|
|
|
|
|
|
my $dbg = 0; |
213
|
|
|
|
|
|
|
my $check = 0; |
214
|
|
|
|
|
|
|
# FIXME: must be a better way to do this: |
215
|
|
|
|
|
|
|
my $bgcolor = "gold"; |
216
|
|
|
|
|
|
|
my $self = shift; |
217
|
|
|
|
|
|
|
my ($addr, $ang, $pt) = @_; |
218
|
|
|
|
|
|
|
($addr->{type} eq "images") or croak("not an image\n"); |
219
|
|
|
|
|
|
|
my $obj = $self->getobj($addr); |
220
|
|
|
|
|
|
|
my $im = $obj->{image_handle}; |
221
|
|
|
|
|
|
|
# Ben Franklin was retarded |
222
|
|
|
|
|
|
|
my $cw_deg_ang = $ang * -180 / $pi; |
223
|
|
|
|
|
|
|
# image rotates inside the box: |
224
|
|
|
|
|
|
|
$im->Rotate(degrees => $cw_deg_ang); |
225
|
|
|
|
|
|
|
# but now we have to change the box |
226
|
|
|
|
|
|
|
my ($w, $h) = $im->Get("width", "height"); |
227
|
|
|
|
|
|
|
$dbg && print "size now $w x $h\n"; |
228
|
|
|
|
|
|
|
# so we make a fake version of the image: |
229
|
|
|
|
|
|
|
my @pts = $self->get_world_image_rectangle($addr); |
230
|
|
|
|
|
|
|
print "points: \n\t", join("\n\t", map({join(",", @$_[0,1])} @pts)), "\n"; |
231
|
|
|
|
|
|
|
my $scrpad = CAD::Drawing->new(); |
232
|
|
|
|
|
|
|
my $box = $scrpad->addpolygon([map({[@$_]} @pts)]); |
233
|
|
|
|
|
|
|
# and rotate that |
234
|
|
|
|
|
|
|
$dbg && print "rotating about @$pt\n"; |
235
|
|
|
|
|
|
|
$scrpad->Rotate($box, $ang, $pt); |
236
|
|
|
|
|
|
|
print "points: \n\t", join("\n\t", map({join(",", @$_[0,1])} @pts)), "\n"; |
237
|
|
|
|
|
|
|
my @ext = $scrpad->getExtentsRec([$box]); |
238
|
|
|
|
|
|
|
$check && $scrpad->addcircle($pt, 10, {color => "red"}); |
239
|
|
|
|
|
|
|
$check && $scrpad->addpolygon(\@pts, {color => "green"}); |
240
|
|
|
|
|
|
|
$check && $scrpad->addpolygon(\@ext, {color => "red"}); |
241
|
|
|
|
|
|
|
$check && $scrpad->addcircle($ext[0], 5, {color => "blue"}); |
242
|
|
|
|
|
|
|
# so the lower-left of the extents is our new insert: |
243
|
|
|
|
|
|
|
my @insert = @{$ext[0]}; |
244
|
|
|
|
|
|
|
$obj->{pt} = [@insert]; |
245
|
|
|
|
|
|
|
$dbg && print "new insert: @insert\n"; |
246
|
|
|
|
|
|
|
$check && $scrpad->show(hang=>1); |
247
|
|
|
|
|
|
|
$check && exit; |
248
|
|
|
|
|
|
|
# set the size and we're done |
249
|
|
|
|
|
|
|
$obj->{size} = [$w, $h]; |
250
|
|
|
|
|
|
|
} # end subroutine image_rotate definition |
251
|
|
|
|
|
|
|
######################################################################## |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 image_swap_context |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
This involves a scaling of the image (the contexts should be aligned |
256
|
|
|
|
|
|
|
over each other at this point or everything will go to hell.) Do your |
257
|
|
|
|
|
|
|
own move / rotate / crop before calling this, because all this does is |
258
|
|
|
|
|
|
|
to scale the underlying image object such that the vec property of the |
259
|
|
|
|
|
|
|
image definition at $dest_addr can be used correctly. |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Note that this does not "swap" the image to $dest_addr, rather it uses |
262
|
|
|
|
|
|
|
the image definition of $dest_addr to change the image object and |
263
|
|
|
|
|
|
|
definition at $source_addr. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
Also note that the image must fit completely inside (I think) of the |
266
|
|
|
|
|
|
|
destination in order for the composite to work correctly. |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
$drw->image_swap_context($source_addr, $dest_addr); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=cut |
271
|
|
|
|
|
|
|
sub image_swap_context { |
272
|
|
|
|
|
|
|
my $dbg = 0; |
273
|
|
|
|
|
|
|
my $self = shift; |
274
|
|
|
|
|
|
|
my ($s_addr, $d_addr) = @_; |
275
|
|
|
|
|
|
|
my $bgcolor = "gold"; |
276
|
|
|
|
|
|
|
($s_addr->{type} eq "images") or croak("not an image\n"); |
277
|
|
|
|
|
|
|
($d_addr->{type} eq "images") or croak("not an image\n"); |
278
|
|
|
|
|
|
|
my $obj = $self->getobj($s_addr); |
279
|
|
|
|
|
|
|
# note: we will kill this one: |
280
|
|
|
|
|
|
|
my $im_in = $obj->{image_handle}; |
281
|
|
|
|
|
|
|
# determine the scale difference between the two definitions |
282
|
|
|
|
|
|
|
my $dvecs = $self->Get("vector", $d_addr); |
283
|
|
|
|
|
|
|
my $svecs = $self->Get("vector", $s_addr); |
284
|
|
|
|
|
|
|
my @scale = ( |
285
|
|
|
|
|
|
|
$dvecs->[0][0] / $svecs->[0][0], |
286
|
|
|
|
|
|
|
$dvecs->[1][1] / $svecs->[1][1], |
287
|
|
|
|
|
|
|
); |
288
|
|
|
|
|
|
|
$dbg && print "vecs scale at @scale\n"; |
289
|
|
|
|
|
|
|
my ($w, $h) = map({sprintf("%0.0f", $_ * $scale[0])} |
290
|
|
|
|
|
|
|
$im_in->Get("width", "height") |
291
|
|
|
|
|
|
|
); |
292
|
|
|
|
|
|
|
$im_in->Scale("width" => $w, "height" => $h); |
293
|
|
|
|
|
|
|
$dbg && print "size now $w x $h (hopefully)\n"; |
294
|
|
|
|
|
|
|
$dbg && print "checking: ", |
295
|
|
|
|
|
|
|
join(" x ", $im_in->Get("width", "height")), "\n"; |
296
|
|
|
|
|
|
|
# and set the vecs |
297
|
|
|
|
|
|
|
$obj->{vector} = [map({[@$_]} @$dvecs)]; |
298
|
|
|
|
|
|
|
# and the size |
299
|
|
|
|
|
|
|
$obj->{size} = [$w, $h]; |
300
|
|
|
|
|
|
|
# need to create a new image object which represents the destination |
301
|
|
|
|
|
|
|
# size and find the points where this one fits into that. |
302
|
|
|
|
|
|
|
my $d_size = $self->Get("size", $d_addr); |
303
|
|
|
|
|
|
|
my $im_out = Image::Magick->new(); |
304
|
|
|
|
|
|
|
$im_out->Set(size => sprintf("%0.0fx%0.0f", @$d_size)); |
305
|
|
|
|
|
|
|
$dbg && print "filling new image at @$d_size\n"; |
306
|
|
|
|
|
|
|
$im_out->Read("xc:$bgcolor"); |
307
|
|
|
|
|
|
|
$im_out->Transparent("color" => $bgcolor); |
308
|
|
|
|
|
|
|
# dot each corner for justification into other images |
309
|
|
|
|
|
|
|
my $color = $aci2hex[$self->Get("color", $s_addr)]; |
310
|
|
|
|
|
|
|
$dbg && print "output dot color: $color\n"; |
311
|
|
|
|
|
|
|
my $x = $d_size->[0] - 1; |
312
|
|
|
|
|
|
|
my $y = $d_size->[1] - 1; |
313
|
|
|
|
|
|
|
$im_out->Set("pixel[0,0]" => $color); |
314
|
|
|
|
|
|
|
$im_out->Set("pixel[$x,0]" => $color); |
315
|
|
|
|
|
|
|
$im_out->Set("pixel[0,$y]" => $color); |
316
|
|
|
|
|
|
|
$im_out->Set("pixel[$x,$y]" => $color); |
317
|
|
|
|
|
|
|
# determine placement from 0,0 of source mapped onto dest: |
318
|
|
|
|
|
|
|
my @placement = map({sprintf("%0.0f", $_)} |
319
|
|
|
|
|
|
|
$self->drw_to_img([$self->img_to_drw([0,0], $s_addr)], $d_addr) |
320
|
|
|
|
|
|
|
); |
321
|
|
|
|
|
|
|
$dbg && print "compositing...\n"; |
322
|
|
|
|
|
|
|
$im_out->Composite( |
323
|
|
|
|
|
|
|
compose => "Over", image => $im_in, |
324
|
|
|
|
|
|
|
x => $placement[0], y => $placement[1] |
325
|
|
|
|
|
|
|
); |
326
|
|
|
|
|
|
|
$dbg && print "done\n"; |
327
|
|
|
|
|
|
|
$obj->{image_handle} = $im_out; |
328
|
|
|
|
|
|
|
undef($im_in); |
329
|
|
|
|
|
|
|
# set the size, so it will be proper |
330
|
|
|
|
|
|
|
} # end subroutine image_swap_context definition |
331
|
|
|
|
|
|
|
######################################################################## |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
1; |