File Coverage

blib/lib/Image/Base.pm
Criterion Covered Total %
statement 131 158 82.9
branch 49 56 87.5
condition 8 12 66.6
subroutine 13 23 56.5
pod 12 12 100.0
total 213 261 81.6


line stmt bran cond sub pod time code
1             package Image::Base ; # Documented at the __END__
2              
3 3     3   21700 use 5.004 ; # 5.004 for __PACKAGE__ special literal
  3         12  
  3         7345  
4 3     3   31 use strict ;
  3         7  
  3         138  
5              
6 3     3   18 use vars qw( $VERSION ) ;
  3         10  
  3         199  
7              
8             $VERSION = '1.17' ;
9              
10 3     3   18 use Carp qw( croak ) ;
  3         5  
  3         7655  
11              
12             # uncomment this to run the ### lines
13             #use Smart::Comments '###';
14              
15             # All the supplied methods are expected to be inherited by subclasses; some
16             # will be adequate, some will need to be overridden and some *must* be
17             # overridden.
18              
19             ### Private methods
20             #
21             # _get object
22             # _set object
23              
24             sub _get { # Object method
25 0     0   0 my $self = shift ;
26             # my $class = ref( $self ) || $self ;
27            
28 0         0 $self->{shift()} ;
29             }
30              
31              
32             sub _set { # Object method
33 0     0   0 my $self = shift ;
34             # my $class = ref( $self ) || $self ;
35            
36 0         0 my $field = shift ;
37              
38 0         0 $self->{$field} = shift ;
39             }
40              
41              
42 0     0   0 sub DESTROY {
43             ; # Save's time
44             }
45              
46              
47             ### Public methods
48              
49              
50 0     0 1 0 sub new { croak __PACKAGE__ . "::new() must be overridden" }
51 0     0 1 0 sub xy { croak __PACKAGE__ . "::xy() must be overridden" }
52 0     0 1 0 sub load { croak __PACKAGE__ . "::load() must be overridden" }
53 0     0 1 0 sub save { croak __PACKAGE__ . "::save() must be overridden" }
54 0     0 1 0 sub set { croak __PACKAGE__ . "::set() must be overridden" }
55              
56              
57             sub get { # Object method
58 0     0 1 0 my $self = shift ;
59             # my $class = ref( $self ) || $self ;
60            
61 0         0 my @result ;
62              
63 0         0 push @result, $self->_get( shift() ) while @_ ;
64              
65 0 0       0 wantarray ? @result : shift @result ;
66             }
67              
68              
69             sub new_from_image { # Object method
70 0     0 1 0 my $self = shift ; # Must be an image to copy
71 0   0     0 my $class = ref( $self ) || $self ;
72 0         0 my $newclass = shift ; # Class of target taken from class or object
73              
74 0 0       0 croak "new_from_image() cannot read $class" unless $self->can( 'xy' ) ;
75              
76 0         0 my( $width, $height ) = $self->get( -width, -height ) ;
77              
78             # If $newclass was an object reference we inherit its characteristics
79             # except for width/height and any arguments we've supplied.
80 0         0 my $obj = $newclass->new( @_, -width => $width, -height => $height ) ;
81              
82 0 0       0 croak "new_from_image() cannot convert to " . ref $obj unless $obj->can( 'xy' ) ;
83              
84 0         0 for( my $x = 0 ; $x < $width ; $x++ ) {
85 0         0 for( my $y = 0 ; $y < $height ; $y++ ) {
86 0         0 $obj->xy( $x, $y, $self->xy( $x, $y ) ) ;
87             }
88             }
89              
90 0         0 $obj ;
91             }
92              
93              
94             sub line { # Object method
95 141     141 1 3772 my( $self, $x0, $y0, $x1, $y1, $colour ) = @_ ;
96              
97             # basic Bressenham line drawing
98              
99 141         193 my $dy = abs ($y1 - $y0);
100 141         186 my $dx = abs ($x1 - $x0);
101             #### $dy
102             #### $dx
103              
104 141 100       243 if ($dx >= $dy) {
105             # shallow slope
106              
107 125 100       267 ( $x0, $y0, $x1, $y1 ) = ( $x1, $y1, $x0, $y0 ) if $x0 > $x1 ;
108              
109 125         132 my $y = $y0 ;
110 125 100       207 my $ystep = ($y1 > $y0 ? 1 : -1);
111 125         216 my $rem = int($dx/2) - $dx;
112 125         306 for( my $x = $x0 ; $x <= $x1 ; $x++ ) {
113             #### $rem
114 637         1420 $self->xy( $x, $y, $colour ) ;
115 637 100       6871 if (($rem += $dy) >= 0) {
116 61         71 $rem -= $dx;
117 61         207 $y += $ystep;
118             }
119             }
120             } else {
121             # steep slope
122              
123 16 100       41 ( $x0, $y0, $x1, $y1 ) = ( $x1, $y1, $x0, $y0 ) if $y0 > $y1 ;
124              
125 16         22 my $x = $x0 ;
126 16 100       29 my $xstep = ($x1 > $x0 ? 1 : -1);
127 16         31 my $rem = int($dy/2) - $dy;
128 16         42 for( my $y = $y0 ; $y <= $y1 ; $y++ ) {
129             #### $rem
130 68         161 $self->xy( $x, $y, $colour ) ;
131 68 100       771 if (($rem += $dx) >= 0) {
132 2         5 $rem -= $dy;
133 2         5 $x += $xstep;
134             }
135             }
136             }
137             }
138              
139              
140             # Midpoint ellipse algorithm from Computer Graphics Principles and Practice.
141             #
142             # The points of the ellipse are
143             # (x/a)^2 + (y/b)^2 == 1
144             # or expand out to
145             # x^2*b^2 + y^2*a^2 == a^2*b^2
146             #
147             # The x,y coordinates are taken relative to the centre $ox,$oy, with radials
148             # $a and $b half the width $x1-x0 and height $y1-$y0. If $x1-$x0 is odd,
149             # then $ox and $a are not integers but have 0.5 parts. Starting from $x=0.5
150             # and keeping that 0.5 means the final xy() pixels drawn in
151             # &$ellipse_point() are integers. Similarly for y.
152             #
153             # Only a few lucky pixels exactly satisfy the ellipse equation above. For
154             # the rest there's an error amount expressed as
155             #
156             # E(x,y) = x^2*b^2 + y^2*a^2 - a^2*b^2
157             #
158             # The first loop maintains a "discriminator" d1 in $d
159             #
160             # d1 = (x+1)^2*b^2 + (y-1/2)^2*a^2 - a^2*b^2
161             #
162             # which is E(x+1,y-1/2), being the error amount for the next x+1 position,
163             # taken at y-1/2 which is the midpoint between the possible next y or y-1
164             # pixels. When d1 > 0 it means that the y-1/2 position is outside the
165             # ellipse and the y-1 pixel is taken to be the better approximation to the
166             # ellipse than y.
167             #
168             # The first loop does the four octants near the Y axis, ie. the nearly
169             # horizontal parts. The second loop does the four octants near the X axis,
170             # ie. the nearly vertical parts. For the second loop the discriminator in
171             # $d is instead at the next y-1 position and between x and x+1,
172             #
173             # d2 = E(x+1/2,y-1) = (x+1/2)^2*b^2 + (y-1)^2*a^2 - a^2*b^2
174             #
175             # The difference between d1 and d2 for the changeover is as follows and is
176             # used to step across to the new position rather than a full recalculation.
177             # Not much difference in speed, but less code.
178             #
179             # E(x+1/2,y-1) - E(x+1,y-1/2)
180             # = -b^2 * (x + 3/4) + a^2 * (3/4 - y)
181             #
182             # since (x+1/2)^2 - (x+1)^2 = -x - 3/4
183             # (y-1)^2 - (y-1/2)^2 = -y + 3/4
184             #
185             #
186             # Other Possibilities:
187             #
188             # The calculations could be made all-integer by counting $x and $y from 0 at
189             # the bounding box edges and measuring inwards, rather than outwards from a
190             # fractional centre. E(x,y) could have a factor of 2 or 4 put through as
191             # necessary, the discriminating >0 or <0 staying the same. The d1 and d2
192             # steps are at most roughly 2*max(a*b^2,b*a^2), which for a circle means
193             # 2*r^3. This fits a 32-bit signed integer for up to about 1000 pixels or
194             # so, and then of course Perl switches to 53-bit floats automatically, which
195             # is still an exact integer up to about 160,000 pixels radius.
196             #
197             # It'd be possible to draw runs of horizontal pixels with line() instead of
198             # individual xy() calls. That might help subclasses doing a block-fill for
199             # a horizontal line segment. Except only big or flat ellipses have more
200             # than a few adjacent horizontal pixels. Perhaps just the initial topmost
201             # horizontal, using a sqrt to calculate where it crosses from the top y=b
202             # down to y=b-1.
203             #
204             # The end o the first loop could be pre-calculated (with a sqrt), if that
205             # seemed better than watching $aa*($y-0.5) vs $bb*($x+1). The loop change
206             # is where the tangent slope is steeper than -1. Drawing a little diagram
207             # shows that an x+0,y+1 downward step like in the second loop is not needed
208             # until that point.
209             #
210             # dx/dy = -x*b^2 / y*a^2 = -1 slope
211             # y = x*b^2/a^2
212             # b^2*x^2 + a^2*(b^4/a^4)*x^2 = a^2*b^2 into the ellipse equation
213             # x^2 * (1 + b^2/a^2) = a^2
214             # x = a * sqrt (a^2 / (a^2 + b^2))
215             # = a^2 / sqrt (a^2 + b^2)
216             #
217              
218             sub ellipse { # Object method
219 11     11 1 2689 my $self = shift ;
220             # my $class = ref( $self ) || $self ;
221              
222 11         21 my( $x0, $y0, $x1, $y1, $colour, $fill ) = @_ ;
223              
224             # per the docs, x0,y0 top left, x1,y1 bottom right
225             # could relax that fairly easily, if desired ...
226             ### assert: $x0 <= $x1
227             ### assert: $y0 <= $y1
228              
229 11         14 my ($a, $b);
230 11 100 66     74 if (($a = ( $x1 - $x0 ) / 2) <= .5
231             || ($b = ( $y1 - $y0 ) / 2) <= .5) {
232             # one or two pixels high or wide, treat as rectangle
233 1         4 $self->rectangle ($x0, $y0, $x1, $y1, $colour );
234 1         3 return;
235             }
236 10         32 my $aa = $a ** 2 ;
237 10         13 my $bb = $b ** 2 ;
238 10         13 my $ox = ($x0 + $x1) / 2;
239 10         13 my $oy = ($y0 + $y1) / 2;
240              
241 10         74 my $x = $a - int($a) ; # 0 or 0.5
242 10         14 my $y = $b ;
243             ### initial: "origin $ox,$oy start xy $x,$y"
244              
245             my $ellipse_point =
246             ($fill
247             ? sub {
248             ### ellipse_point fill: "$x,$y"
249 6     6   31 $self->line( $ox - $x, $oy + $y,
250             $ox + $x, $oy + $y, $colour ) ;
251 6         22 $self->line( $ox - $x, $oy - $y,
252             $ox + $x, $oy - $y, $colour ) ;
253             }
254             : sub {
255             ### ellipse_point xys: "$x,$y"
256 15     15   41 $self->xy( $ox + $x, $oy + $y, $colour ) ;
257 15         160 $self->xy( $ox - $x, $oy - $y, $colour ) ;
258 15         140 $self->xy( $ox + $x, $oy - $y, $colour ) ;
259 15         138 $self->xy( $ox - $x, $oy + $y, $colour ) ;
260 10 100       71 });
261              
262             # Initially,
263             # d1 = E(x+1,y-1/2)
264             # = (x+1)^2*b^2 + (y-1/2)^2*a^2 - a^2*b^2
265             # which for x=0,y=b is
266             # = b^2 - a^2*b + a^2/4
267             # or for x=0.5,y=b
268             # = 9/4*b^2 - ...
269             #
270 10 100       36 my $d = ($x ? 2.25*$bb : $bb) - ( $aa * $b ) + ( $aa / 4 ) ;
271              
272 10   100     55 while( $y >= 1
273             && ( $aa * ( $y - 0.5 ) ) > ( $bb * ( $x + 1 ) ) ) {
274              
275             ### assert: $d == ($x+1)**2 * $bb + ($y-.5)**2 * $aa - $aa * $bb
276 22 100       41 if( $d < 0 ) {
277 18 100       36 if (! $fill) {
278             # unfilled draws each pixel, but filled waits until stepping
279             # down "--$y" and then draws whole horizontal line
280 9         13 &$ellipse_point();
281             }
282 18         95 $d += ( $bb * ( ( 2 * $x ) + 3 ) ) ;
283 18         85 ++$x ;
284             }
285             else {
286 4         8 &$ellipse_point();
287 4         23 $d += ( ( $bb * ( ( 2 * $x ) + 3 ) ) +
288             ( $aa * ( ( -2 * $y ) + 2 ) ) ) ;
289 4         6 ++$x ;
290 4         11 --$y ;
291             }
292             }
293              
294             # switch to d2 = E(x+1/2,y-1) by adding E(x+1/2,y-1) - E(x+1,y-1/2)
295 10         23 $d += $aa*(.75-$y) - $bb*($x+.75);
296             ### assert: $d == $bb*($x+0.5)**2 + $aa*($y-1)**2 - $aa*$bb
297              
298             ### second loop at: "$x, $y"
299              
300 10         22 while( $y >= 1 ) {
301 8         16 &$ellipse_point();
302 8 100       49 if( $d < 0 ) {
303 6         14 $d += ( $bb * ( ( 2 * $x ) + 2 ) ) +
304             ( $aa * ( ( -2 * $y ) + 3 ) ) ;
305 6         7 ++$x ;
306 6         15 --$y ;
307             }
308             else {
309 2         3 $d += ( $aa * ( ( -2 * $y ) + 3 ) ) ;
310 2         5 --$y ;
311             }
312             ### assert: $d == $bb*($x+0.5)**2 + $aa*($y-1)**2 - $aa*$bb
313             }
314              
315             # loop ends with y=0 or y=0.5 according as the height is odd or even,
316             # leaving one or two middle rows to draw out to x0 and x1 edges
317             ### assert: $y == $b - int($b)
318              
319 10 100       21 if ($fill) {
320             ### middle fill: "y ".($oy-$y)." to ".($oy+$y)
321 5         16 $self->rectangle( $x0, $oy - $y,
322             $x1, $oy + $y,
323             $colour, 1 ) ;
324             } else {
325             # middle tails from $x out to the left/right edges
326             # $x can be several pixels less than $a if small height large width
327             ### tail: "y=$y, left $x0 to ".($ox-$x).", right ".($ox+$x)." to $x1"
328 5         18 $self->rectangle( $x0, $oy - $y, # left
329             $ox - $x, $oy + $y,
330             $colour, 1 ) ;
331 5         38 $self->rectangle( $ox + $x, $oy - $y, # right
332             $x1, $oy + $y,
333             $colour, 1 ) ;
334             }
335             }
336              
337             sub rectangle { # Object method
338 50     50 1 3253 my ($self, $x0, $y0, $x1, $y1, $colour, $fill) = @_;
339              
340 50 100       102 if ($x0 == $x1) {
341             # vertical line only
342 19         41 $self->line( $x0, $y0, $x1, $y1, $colour ) ;
343              
344             } else {
345 31 100       53 if ($fill) {
346 23         84 for( my $y = $y0 ; $y <= $y1 ; $y++ ) {
347 47         94 $self->line( $x0, $y, $x1, $y, $colour ) ;
348             }
349              
350             } else { # unfilled
351              
352 8         19 $self->line( $x0, $y0,
353             $x1, $y0, $colour ) ; # top
354 8 100       21 if (++$y0 <= $y1) {
355             # height >= 2
356 7 100       16 if ($y0 < $y1) {
357             # height >= 3, verticals
358 5         15 $self->line( $x0, $y0,
359             $x0, $y1-1, $colour ) ; # left
360 5         14 $self->line( $x1, $y0,
361             $x1, $y1-1, $colour ) ; # right
362             }
363 7         17 $self->line( $x1, $y1,
364             $x0, $y1, $colour ) ; # bottom
365             }
366             }
367             }
368             }
369              
370             sub diamond {
371 17     17 1 3911 my ($self, $x1,$y1, $x2,$y2, $colour, $fill) = @_;
372             ### diamond(): "$x1,$y1, $x2,$y2, $colour fill=".($fill||0)
373              
374             ### assert: $x2 >= $x1
375             ### assert: $y2 >= $y1
376              
377 17         27 my $w = $x2 - $x1;
378 17         23 my $h = $y2 - $y1;
379 17 100 100     79 if ($w < 2 || $h < 2) {
380 6         15 $self->rectangle ($x1,$y1, $x2,$y2, $colour, 1);
381 6         17 return;
382             }
383 11         20 $w = int ($w / 2);
384 11         15 $h = int ($h / 2);
385 11         12 my $x = $w; # middle
386 11         12 my $y = 0; # top
387              
388             ### $w
389             ### $h
390             ### x1+x: $x1+$w
391             ### x2-x: $x2-$w
392             ### y1+y: $y1+$h
393             ### y2-y: $y2-$h
394              
395 11         11 my $draw;
396 11 100       17 if ($fill) {
397             $draw = sub {
398             ### draw across: "$x,$y"
399 12     12   48 $self->line ($x1+$x,$y1+$y, $x2-$x,$y1+$y, $colour); # upper
400 12         39 $self->line ($x1+$x,$y2-$y, $x2-$x,$y2-$y, $colour); # lower
401 8         39 };
402             } else {
403             $draw = sub {
404             ### draw: "$x,$y"
405 4     4   14 $self->xy ($x1+$x,$y1+$y, $colour); # upper left
406 4         45 $self->xy ($x2-$x,$y1+$y, $colour); # upper right
407              
408 4         51 $self->xy ($x1+$x,$y2-$y, $colour); # lower left
409 4         38 $self->xy ($x2-$x,$y2-$y, $colour); # lower right
410 3         26 };
411             }
412              
413 11 100       23 if ($w > $h) {
414             ### shallow ...
415              
416 3         6 my $rem = int($w/2) - $w;
417             ### $rem
418              
419 3         8 while ($x > 0) {
420             ### at: "x=$x rem=$rem"
421              
422 8 100       17 if (($rem += $h) >= 0) {
423 4         9 &$draw();
424 4         6 $y++;
425 4         5 $rem -= $w;
426 4         10 $x--;
427             } else {
428 4 50       9 if (! $fill) { &$draw() }
  0         0  
429 4         13 $x--;
430             }
431             }
432              
433             } else {
434             ### steep ...
435              
436             # when $h is odd bias towards pointier at the narrower top/bottom ends
437 8         14 my $rem = int(($h-1)/2) - $h;
438             ### $rem
439              
440 8         20 while ($y < $h) {
441             ### $rem
442 12         16 &$draw();
443              
444 12 100       59 if (($rem += $w) >= 0) {
445 10         12 $rem -= $h;
446 10         12 $x--;
447             ### x inc to: "x=$x rem $rem"
448             }
449 12         26 $y++;
450             }
451             }
452              
453             ### final: "$x,$y"
454              
455             # middle row if $h odd, or middle two rows if $h even
456             # done explicitly rather than with &$draw() so as not to draw the middle
457             # row twice when $h odd
458 11 100       21 if ($fill) {
459 8         22 $self->rectangle ($x1,$y1+$h, $x2,$y2-$h, $colour, 1);
460             } else {
461 3         10 $self->rectangle ($x1,$y1+$h, $x1+$x,$y2-$h, $colour, 1); # left
462 3         9 $self->rectangle ($x2-$x,$y1+$h, $x2,$y2-$h, $colour, 1); # right
463             }
464             }
465              
466 1     1 1 305 sub add_colours {
467             # my ($self, $colour, $colour, ...) = @_;
468             }
469              
470             1 ;
471              
472              
473             __END__