File Coverage

blib/lib/CAD/Drawing/Manipulate/Graphics.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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;