| 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; |