File Coverage

blib/lib/Image/BoxModel/Lowlevel.pm
Criterion Covered Total %
statement 12 138 8.7
branch 0 88 0.0
condition 0 64 0.0
subroutine 4 13 30.7
pod 8 9 88.8
total 24 312 7.6


line stmt bran cond sub pod time code
1             package Image::BoxModel::Lowlevel;
2              
3 1     1   13 use warnings;
  1         3  
  1         31  
4 1     1   5 use strict;
  1         2  
  1         29  
5              
6 1     1   1075 use POSIX; #for ceil() in ::Box
  1         12865  
  1         8  
7 1     1   3131 use Carp;
  1         1  
  1         2465  
8              
9             =head1 NAME
10              
11             Image::BoxModel::Lowlevel - Lowlevel functions for Image::BoxModel
12              
13             =head1 SYNOPSIS
14              
15             For an example and general information see Image::BoxModel.pm
16              
17             =head1 DESCRIPTION
18              
19             Image::BoxModel::Lowlevel implements some basic functionality.
20              
21             It does so by using the methods from Image::BoxModel::Backend::[LIBRARY]
22              
23             There are more backends planned and more functionality for each backend.
24             (backends, patches, wishes are very welcome - in this order ;-)
25              
26             Image::BoxModel::Lowlevel can be used directly, which is considered painful sometimes.
27             You need to specify the size of a box before you can put text on it, for example, while 'Annotate' (inherited from ::Text) easily inserts a box and puts text on it.
28             On the other hand, ::Lowlevel gives you full control.
29              
30             =head2 Methods:
31              
32             =cut
33              
34             #########################
35             #Get width & height of a Box
36             #########################
37              
38             =head3 GetBoxSize
39              
40             ($width, $height) = $image -> GetBoxSize (box => "name_of_your_box");
41              
42             =cut
43              
44             sub GetBoxSize{
45 0     0 1   my $image = shift;
46 0           my %p = @_;
47            
48 0 0 0       if ((exists $p{box} && defined $p{box}) && (exists $image->{$p{box}}{width})){
      0        
49 0           return $image->{$p{box}}{width}, $image->{$p{box}}{height};
50             }
51             else{
52 0           return "Box '$p{box}' is not (correctly, at least) defined";
53             }
54             }
55              
56              
57              
58             #########################
59             # Add a new box and resize another one (the "free"-box unless resize => box-to-resize is set)
60             #########################
61              
62             =head3 Box
63              
64             If you don't specify 'resize => $name_of_box_to_be_resized', the standard-box 'free' is chosen.
65              
66             $image -> Box (
67             position =>[left|right|top|bottom],
68             width => $x,
69             height => $y,
70             name => $name_of_new_box,
71            
72             # You can either specify a background color, then the box will be filled with that color
73             background => [color]
74            
75             # or you can define a border color and a background color, then you will get a nice rectangle with border.
76             # if you omit border_thickness it defaults to 1
77             background => [color],
78             border_color => [color],
79             border_thickness =>[color]
80             );
81              
82             =cut
83              
84             sub Box{
85 0     0 1   my $image = shift;
86 0           my %p = @_; #%p holds the _p_arameters
87 0   0       my $resize = $p{resize} || 'free';
88            
89             #~ print "Name: $resize, Wert: ", $image->{$resize},"\n";
90 0 0         croak __PACKAGE__,"::Box: You tried to put a box on '$resize' which does not exists. Die." unless exists $image ->{ $resize};
91            
92 0 0         croak __PACKAGE__,"::Box: Mandatory parameter name missing. Die." unless $p{name};
93 0 0         return "$p{name} already exists. No box added" if (exists $image->{$p{name}});
94 0 0         croak __PACKAGE__,"::Box: Mandatory parameter position missing. Die." unless $p{position};
95            
96             #return if width or height is not specified.
97             #(height wenn adding at top or bottom, width wen adding at left or right side.)
98 0 0 0       if ($p{position} eq "top" or $p{position} eq "bottom"){
    0 0        
99            
100 0 0 0       return "Box: Please specify height > 0. No box added\n"
101             unless (exists $p{height} and $p{height} > 0);
102            
103 0 0         return "Box: Not enough free space on $resize for $p{name}. No box added\n (requested space: $p{height}, available: $image->{$resize}{height})\n"
104             if ($p{height} > $image->{$resize}{height});
105             }
106             elsif ($p{position} eq "left" or $p{position} eq "right"){
107            
108 0 0 0       return "Box: Please specify width > 0. No box added\n"
      0        
109             unless (exists $p{width} and $p{width} and $p{width} > 0);
110            
111 0 0         return "Box: Not enough free space on $resize for $p{name}. No box added\n (requested space: $p{width}, available: $image->{$resize}{width})\n"
112             if ($p{width} > $image->{$resize}{width});
113             }
114            
115 0           $image -> print_message ("Add Box \"$p{name}\" with ", __PACKAGE__,"\n");
116            
117            
118 0           $image->{$p{name}}={ #First we make the new box as big as the field which will be resized..
119             top => $image->{$resize}{top},
120             bottom => $image->{$resize}{bottom},
121             left => $image->{$resize}{left} ,
122             right => $image->{$resize}{right},
123             };
124            
125             #.. then we overwrite as needed.
126            
127 0 0         $p{width} = ceil ($p{width}) if exists $p{width};
128 0 0         $p{height} = ceil ($p{height}) if exists $p{height};
129            
130 0 0         if ($p{position} eq "top"){
    0          
    0          
    0          
131 0           $image->{$p{name}}{bottom} = $image->{$resize}{top} + $p{height};
132            
133             #The top margin of the resized field is set to the bottom of the new box.
134 0           $image->{$resize}{top} = $image->{$p{name}}{bottom}+1;
135             }
136             elsif ($p{position} eq "bottom"){
137 0           $image->{$p{name}}{top} = $image->{$resize}{bottom} - $p{height};
138 0           $image->{$resize}{bottom} = $image->{$p{name}}{top}-1;
139             }
140             elsif ($p{position} eq "left"){
141 0           $image->{$p{name}}{right} = $image->{$resize}{left} + $p{width};
142 0           $image->{$resize}{left} = $image->{$p{name}}{right}+1;
143             }
144             elsif ($p{position} eq "right"){
145 0           $image->{$p{name}}{left} = $image->{$resize}{right} - $p{width};
146 0           $image->{$resize}{right} = $image->{$p{name}}{left}-1;
147             }
148             else {
149 0           return "Image::BoxModel::Lowlevel::Box: Position $p{position} unknown. No box added";
150            
151             }
152            
153             # if border_color and background are defined, draw a rectangle with border and fill it.
154 0 0 0       if (exists $p{border_color} and defined $p{border_color}
    0 0        
      0        
      0        
155             and
156             exists $p{background} and defined $p{background}
157             ){
158            
159 0 0 0       $p{border_thickness} = 1 unless (exists $p{border_thickness} and defined $p{border_thickness} and $p{border_thickness} > 1);
      0        
160            
161 0           $image -> DrawRectangle(
162             left => $image->{$p{name}}{left},
163             right => $image->{$p{name}}{right},
164             top => $image->{$p{name}}{top},
165             bottom => $image->{$p{name}}{bottom},
166             fill_color => $p{background},
167             border_color => $p{border_color},
168             border_thickness => $p{border_thickness}
169             );
170             }
171             # if there is only background, just fill the box with the color
172             elsif (exists $p{background} and defined $p{background}){
173 0           $image-> DrawRectangle(
174             left => $image->{$p{name}}{left},
175             right => $image->{$p{name}}{right},
176             top => $image->{$p{name}}{top},
177             bottom => $image->{$p{name}}{bottom},
178             color => $p{background}
179             );
180             }
181            
182 0           $image->{$p{name}}{width} = $image->{$p{name}}{right} - $image->{$p{name}}{left};
183 0           $image->{$p{name}}{height} = $image->{$p{name}}{bottom} - $image->{$p{name}}{top};
184            
185 0           $image->{$resize}{height} = $image->{$resize}{bottom} - $image->{$resize}{top}; #calculate these values for later use.. laziness
186 0           $image->{$resize}{width} = $image->{$resize}{right} - $image->{$resize}{left};
187            
188 0           return;
189             }
190              
191             #########################
192             # Add Floating Box. These boxes can reside anywhere and can overlap. Poor error-checking!
193             #########################
194              
195             =head3 FloatBox
196              
197             To position a free-floating box wherever you want. There is virtually no error-checking, so perhaps better keep your hands off. ;-)
198              
199             $image -> FloatBox(
200             top => $top,
201             bottom => $bottom,
202             right => $right,
203             left => $top,
204             name => "whatever_you_call_it",
205             background =>[color]
206             );
207              
208             =cut
209              
210             sub FloatBox{
211 0     0 1   my $image = shift;
212 0           my %p =@_;
213 0 0         return "$p{name} already exists. No FloatBox added" if (exists $image->{$p{name}});
214 0           foreach ("top", "bottom", "left", "right"){
215 0 0         return __PACKAGE__,"::FloatBox: argument $_ missing. No FloatBox added" unless (exists $p{$_});
216 0           $image->{$p{name}}{$_} = $p{$_};
217             }
218            
219 0           $image -> print_message ("Add FloatBox \"$p{name}\" with ", __PACKAGE__,"\n");
220            
221             #shift right <-> left if left is more right than right ;-)
222 0 0         ($image->{$p{name}}{right}, $image->{$p{name}}{left}) = ($image->{$p{name}}{left}, $image->{$p{name}}{right})
223             if ($image->{$p{name}}{left} > $image->{$p{name}}{right});
224             #same for bottom and top
225 0 0         ($image->{$p{name}}{top} , $image->{$p{name}}{bottom}) = ($image->{$p{name}}{bottom} , $image->{$p{name}}{top})
226             if ($image->{$p{name}}{bottom} < $image->{$p{name}}{top});
227            
228 0           $image->{$p{name}}{$_} = int ($image->{$p{name}}{$_}) foreach ('top', 'left'); #only allow integer values
229 0           $image->{$p{name}}{$_} = ceil ($image->{$p{name}}{$_}) foreach ('right', 'bottom');
230            
231 0           my $top = $image->{$p{name}}{top};
232 0           my $bottom = $image->{$p{name}}{bottom};
233 0           my $left = $image->{$p{name}}{left};
234 0           my $right = $image->{$p{name}}{right};
235 0 0 0       if ((exists $p{background}) && (defined $p{background})){
236 0           $image -> DrawRectangle(
237             left => $left,
238             right => $right,
239             top => $top,
240             bottom => $bottom,
241             color => $p{background}
242             );
243             }
244            
245 0           $image->{$p{name}}{width} = $image->{$p{name}}{right} - $image->{$p{name}}{left};
246 0           $image->{$p{name}}{height} = $image->{$p{name}}{bottom} - $image->{$p{name}}{top};
247            
248             return
249 0           }
250              
251             =head3 GetTextSize
252              
253             Get the boundig size of (rotated) text. Very useful to find out how big boxes need to be.
254             ($width, $height) = $image -> GetTextSize(
255             text => "Your Text",
256             textsize => [number],
257             rotate => [in degrees, may be negative as well]
258             );
259              
260             =cut
261              
262             sub GetTextSize{
263 0     0 1   my $image = shift;
264 0           my %p = (
265             rotate => 0,
266             @_
267             );
268            
269 0 0 0       $p{font} = default_font() unless (exists $p{font} and $p{font} and -f $p{font});
      0        
270            
271             #die if the mandatory parameters are missing
272 0           my $warning;
273 0           foreach ("text", "textsize"){
274 0 0         $warning .= "Mandatory parameter \"$_\" missing. " unless (exists $p{$_});
275             }
276 0 0         die __PACKAGE__,"::GetTextSize: ".$warning . "dying." if ($warning);
277            
278             #get x&y of all corners:
279             #@corner[0-3]{x|y}
280 0           my @corner = $image->TextSize(text => $p{text}, font => $p{font}, textsize => $p{textsize});
281            
282             #rotate all 4 corners
283 0 0         if ($p{rotate}){
284 0           for (my $i = 0; $i < scalar(@corner); $i++){
285 0           ($corner[$i]{x}, $corner[$i]{y}) = $image -> rotation ($corner[$i]{x}, $corner[$i]{y}, 0, 0, $p{rotate});
286             }
287             }
288            
289 0           my %most =(
290             left => 0,
291             right => 0,
292             top => 0,
293             bottom =>0
294             );
295            
296             #find the left-, right-, top- and bottommost values.
297 0           foreach (@corner){
298 0 0         $most{left} = $_->{x} if ($_->{x} < $most{left});
299 0 0         $most{right} = $_->{x} if ($_->{x} > $most{right});
300 0 0         $most{top} = $_->{y} if ($_->{y} < $most{top});
301 0 0         $most{bottom} = $_->{y} if ($_->{y} > $most{bottom});
302             }
303 0           return (ceil($most{right}- $most{left})), (ceil($most{bottom}-$most{top})); #return width and height
304             #ceil to ensure that the a the text will surely and safely fit.. There were strange errors in ::Backend::GD with values equaling while being inequal at the same time! I don't unterstand this.
305             }
306              
307             =head3 BoxSplit
308              
309             $image -> BoxSplit (
310             box => "name_of_parent",
311             orientation=> "[vertical|horizontal]",
312             number => $number_of_little_boxes),
313             );
314              
315             Splits a box into "number" small boxes. This can be useful if you want to have spreadsheet-style segmentation.
316              
317             Naming of little boxes: parent_[number, counting from 0]
318              
319             In bitmap-land we only have integer-size-boxes. Therefore some boxes may be 1 pixel taller than others..
320              
321             Example:
322              
323             If the parent is "myBox", then the babies are named myBox_0, myBox_1, ...myBox_2635 (if you are crazy enough to have 2635 babies)
324              
325             =cut
326              
327             sub BoxSplit{
328 0     0 1   my $image = shift;
329 0           my %p = @_;
330            
331 0           my $parent_size; #because ::Box ignores the not used given dimension, we just set this to with or height of parent and feed it twice..
332             my $position;
333 0 0         if ($p{orientation} eq "vertical"){
    0          
334 0           $parent_size = $image -> {$p{box}}{height};
335 0           $position = "top";
336             }
337             elsif ($p{orientation} eq "horizontal"){
338 0           $parent_size = $image -> {$p{box}}{width};
339 0           $position = "left";
340             }
341             else{
342 0           die __PACKAGE__,": Wrong value of mandatory parameter 'orientation': $p{orientation}, should be [vertical|horizontal]. Die.";
343             }
344            
345 0           foreach (0.. $p{number}-1){ #baby-box No. 1 holds number 0..
346 0           my $baby_size = sprintf("%.0f", ($parent_size / ($p{number} - $_)));
347             #~ print "baby-size: $baby_size\t baby-name: $p{box}_$_\n";
348            
349 0           $parent_size -= $baby_size;
350              
351 0           $image -> Box (
352             resize => $p{box},
353             position => $position,
354             width => $baby_size-1,
355             height => $baby_size-1,
356             name => "$p{box}_$_",
357             background => $p{background_colors}[$_],
358            
359             border_color => $p{border_color},
360             border_thickness => $p{border_thickness}
361             );
362             }
363 0           return; #nothing at the moment
364             }
365              
366             #########################
367             # Add text to a box
368             #########################
369              
370             =head3 Text
371              
372             For easy use: Better use 'Annotate' (inherited from ::Text) instead of 'Text'. Annotate reserves a box automatically while Text does not.
373              
374             But of course, if you need / want full control, use 'Text'.
375              
376             Put (rotated, antialized) text on a box. Takes a bunch of parameters, of which "text" and "textsize" are mandatory.
377              
378             $image -> Text(
379             text => $text,
380             textsize => [number],
381             color => "black",
382             font => [font-file]
383             rotate => [in degrees, may be negative as well],
384             box => "free",
385             align => [Left|Center|Right]", #align is how multiline-text is aligned
386             position => [Center #position is how text will be positioned inside its box
387             NorthWest|
388             North|
389             NorthEast|
390             West|
391             SoutEast|
392             South|
393             SouthWest|
394             West
395             ],
396             background => [color] #rather for debugging
397             );
398              
399             =cut
400              
401             sub Text{
402 0     0 1   my $image = shift;
403 0           my %p = (
404             color =>"black",
405             rotate =>0,
406             box => "free",
407             rotate => 0,
408             align => "Center",
409             position=> "Center",
410             @_
411             );
412            
413 0 0 0       $p{font} = default_font() unless (exists $p{font} and $p{font} and -f $p{font});
      0        
414            
415 0           my $warning;
416 0           foreach ("text", "textsize"){
417 0 0         $warning .= "Mandatory parameter \"$_\" missing. " unless (exists $p{$_});
418             }
419 0 0 0       $warning .= "align = $p{align} is invalid. Valid are Right / Left / Center. " unless ($p{align} =~ /left/i or $p{align} =~ /right/i or $p{align} =~ /center/i);
      0        
420            
421             #if the box does not exist (Box couldn't / didn't want to make it due to missing parameters), we can't add text.
422             #(It's better if we don't want to..)
423 0 0         $warning .= "Box '$p{box}' does not exist. " unless (exists $image->{$p{box}});
424            
425 0 0         return "Text: ".$warning . "No Text added.\n" if ($warning);
426            
427             #center of box = left + (right-left) /2
428             #later we will rotate the text around the center of the box.
429 0           $p{x_box_center} = $image->{$p{box}}{left} + ($image->{$p{box}}{right} - $image->{$p{box}}{left}) / 2;
430 0           $p{y_box_center} = $image->{$p{box}}{top} + ($image->{$p{box}}{bottom} - $image->{$p{box}}{top}) / 2;
431            
432             #DrawText lives in ::Backend::[your_library], because it has to do much library-specific calculations
433            
434 0           my $w = $image -> DrawText(%p);
435 0 0         $warning .= $w if $w;
436            
437 0           $image -> print_message ("Add Text to Box \"$p{box}\" with ",__PACKAGE__,"\n");
438 0   0       return $warning || return; #to avoid "uninitialized value in calling line when using -w"
439             }
440              
441             =head3 Save
442              
443             $image -> Save($filename);
444              
445             Save the image to file. There is no error-checking at the moment. You need to know yourself if your chosen library supports the desired file-type.
446              
447             =head3 DrawRectangle
448              
449             Rectangle without border:
450              
451             $image -> DrawRectangle (top => $top, bottom => $bottom, right => $right, left => $left, color => "color");
452              
453             Rectangle with border:
454              
455             $image -> DrawRectangle (top => $top, bottom => $bottom, right => $right, left => $left, fill_color => "color", border_color => "color");
456              
457             Draws a rectangle with the given sides. There are no rotated rectangles at the moment.
458            
459              
460             =cut
461              
462             #There is no Save, DrawRectangle.. here really, because they're in ::Backend::[library]
463              
464             =head2 Internal methods:
465              
466             (documentation for myself rather than the user)
467              
468             =head3 rotation
469              
470             To rotate a given point by any point. It takes the angle in degrees, which is very comfortable to me.
471             If you want to rotate something, feel free to use it. :-)
472              
473             ($x, $y) = $image -> rotation($x, $y, $x_center, $y_center, $angle);
474              
475             =cut
476              
477             sub rotation{
478 0     0 1   my $image = shift;
479 0           my ($x, $y, $x_center, $y_center, $angle) = @_;
480             #~ print "X: $x Y: $y x-center: $x_center y-center: $y_center angle: $angle\n";
481            
482 0 0         return ($x, $y) if ($angle == 0); # if angle == 0 then return immediately. 1st because there's nothing to do, 2nd to prevent from division by 0
483              
484 0           $angle = $image->{PI} / (360 / $angle) * 2;
485            
486 0           my $sin = sin ($angle);
487 0           my $cos = cos ($angle);
488            
489 0           my $x1=$x;
490 0           my $y1=$y;
491            
492 0           $x = ($x1 * $cos) - ($y1 * $sin) - ($x_center * $cos) + ($y_center * $sin) + $x_center;
493 0           $y = ($x1 * $sin) + ($y1 * $cos) - ($x_center * $sin) - ($y_center * $cos) + $y_center;
494            
495 0           return $x, $y;
496             }
497              
498             =head3 print_message
499              
500             Checks if verbose is on and then prints messages.
501             $image -> print_message("Text");
502              
503             =cut
504              
505             sub print_message{
506 0     0 1   my $image = shift;
507 0 0         print @_ if $image->{verbose};
508             }
509              
510             sub default_font{
511 0     0 0   my $package = __PACKAGE__; # Gives Image::BoxModel::Lowlevel
512 0           $package =~ s/::/\//g; # Image/BoxModel/Lowlevel
513             # Make default font: (path-to-lib)/Image/BoxModel/Backend/FreeSans.ttf
514 0           (my $default_font = $INC{"$package.pm"}) =~ s/Lowlevel\.pm/Backend\/FreeSans.ttf/;
515 0 0         if (-f $default_font){
516 0           return $default_font;
517             }
518             else{
519 0           die "Can't find default font. Please file bug report.";
520             }
521             }
522              
523              
524             1;
525             __END__