File Coverage

blib/lib/GD/Icons.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package GD::Icons;
2              
3             our $VERSION = '0.04';
4              
5             # $Id: Icons.pm,v 1.8 2007/08/06 15:36:54 canaran Exp $
6              
7 1     1   20793 use warnings;
  1         3  
  1         37  
8 1     1   5 use strict;
  1         1  
  1         31  
9              
10 1     1   658 use GD::Icons::Config;
  1         3  
  1         39  
11              
12 1     1   10 use Carp;
  1         2  
  1         67  
13 1     1   439 use GD;
  0            
  0            
14              
15             ###############
16             # CONSTRUCTOR #
17             ###############
18              
19             sub new {
20             my ($class, %params) = @_;
21              
22             my $self = bless {}, $class;
23              
24             my $config_file = $params{config_file};
25             my $gd_icons_config = GD::Icons::Config->new($config_file);
26             $self->config($gd_icons_config->config);
27              
28             # Assign alpha
29             my $alpha = $params{alpha} ? $params{alpha} : 0;
30             if ($alpha =~ /[^0-9]/ or $alpha > 127) {
31             croak("alpha must be a value between 0 and 127!");
32             }
33             $self->alpha($alpha);
34            
35             # Parse shapes keys
36             my $shape_keys =
37             $params{shape_keys}
38             ? $params{shape_keys}
39             : [":default"];
40             $self->shape_keys($shape_keys);
41              
42             my $shape_values =
43             $params{shape_values}
44             ? $params{shape_values}
45             : $self->all_shapes;
46             $self->shape_values($shape_values);
47              
48             # Parse color keys
49             my $color_keys =
50             $params{color_keys}
51             ? $params{color_keys}
52             : [":default"];
53             $self->color_keys($color_keys);
54              
55             my $color_values =
56             $params{color_values}
57             ? $params{color_values}
58             : $self->all_colors;
59             $self->color_values($color_values);
60              
61             # Parse sval keys
62             my $sval_keys =
63             $params{sval_keys}
64             ? $params{sval_keys}
65             : [":default"];
66             $self->sval_keys($sval_keys);
67              
68             my $sval_values =
69             $params{sval_values}
70             ? $params{sval_values}
71             : [map { $_ / (@$sval_keys) * 100 } (1 .. @$sval_keys)]; # ratio only
72             $self->sval_values($sval_values);
73              
74             my $icon_dir = $params{icon_dir}
75             or croak("A icon_dir is required!");
76             $self->icon_dir($icon_dir);
77              
78             my $icon_prefix = $params{icon_prefix};
79             if (!defined $icon_prefix) {
80             croak("A icon_prefix is required!");
81             }
82             $self->icon_prefix($icon_prefix);
83              
84             return $self;
85             }
86              
87             ##################
88             # PUBLIC METHODS #
89             ##################
90              
91             # Function : Get method that ignores _* names
92             # Arguments : None
93             # Returns : \@all
94             # Notes : None provided.
95              
96             sub all_colors {
97             my ($self) = @_;
98              
99             my $config = $self->config;
100              
101             my @all;
102             foreach (sort keys %{$config->{color}}) {
103             push @all, $_ unless /^_/;
104             }
105              
106             return \@all;
107             }
108              
109             # Function : Get method that ignores _* names
110             # Arguments : None
111             # Returns : \@all
112             # Notes : None provided.
113              
114             sub all_shapes {
115             my ($self) = @_;
116              
117             my $config = $self->config;
118              
119             my @all;
120             foreach (sort keys %{$config->{shape}}) {
121             push @all, $_ unless /^_/;
122             }
123              
124             return \@all;
125             }
126              
127             # Function : Get/set method for icon location
128             # Arguments : $shape_key, $color_key, $sval_key, $value
129             # Returns : $value
130             # Notes : None provided.
131              
132             sub icon {
133             my ($self, $shape_key, $color_key, $sval_key, $value) = @_;
134              
135             $self->{icons}->{$shape_key}->{$color_key}->{$sval_key} = $value
136             if @_ > 4;
137              
138             return $self->{icons}->{$shape_key}->{$color_key}->{$sval_key};
139             }
140              
141             # Function : Make ...
142             # Arguments :
143             # Returns :
144             # Notes : None provided.
145              
146             sub generate_icons {
147             my ($self) = @_;
148              
149             my $config = $self->config;
150              
151             my $alpha = $self->alpha;
152            
153             my @shape_keys = @{$self->shape_keys};
154             my @shape_values = @{$self->shape_values};
155              
156             my @color_keys = @{$self->color_keys};
157             my @color_values = @{$self->color_values};
158              
159             my @sval_keys = @{$self->sval_keys};
160             my @sval_values = @{$self->sval_values};
161              
162             my $icon_dir = $self->icon_dir;
163             my $icon_prefix = $self->icon_prefix;
164              
165             foreach my $shape_i (0 .. $#shape_keys) {
166             foreach my $color_i (0 .. $#color_keys) {
167             foreach my $sval_i (0 .. $#sval_keys) {
168             my $shape =
169             $config->{shape}->{$shape_values[$shape_i % @shape_values]
170             }; # @shape_values here, not $#shape_values
171              
172             my $color =
173             $self->_resolve_color(
174             $color_values[$color_i % @color_values])
175             ; # @color_values here, not $#color_values
176              
177             my $sval = $sval_values[$sval_i];
178             if (@sval_values > 1) {
179             $color = $self->_blend_in_sval($color, $sval);
180             }
181              
182             my ($line_color) = $shape =~ /lc\[([^\[\]]+)\]/;
183             if ($line_color eq ":fill") {
184             $line_color = [@$color];
185             }
186             $line_color = $self->_resolve_color($line_color);
187              
188             my ($line_thickness) = $shape =~ /lt\[(\d+)\]/;
189             my ($side_length) = $shape =~ /sl\[(\d+)\]/;
190              
191             my $image = new GD::Image($side_length, $side_length);
192              
193             my $white_color = $image->colorAllocateAlpha(255, 255, 255, $alpha);
194             my $fill_color = $image->colorAllocateAlpha(@$color, $alpha);
195             my $draw_color = $image->colorAllocateAlpha(@$line_color, $alpha);
196              
197             $image->transparent($white_color);
198              
199             $image->setThickness($line_thickness);
200              
201             while ($shape =~ /\s*(py|fl|nm)\[([0-9, ]+|:auto)\]\s*/g) {
202             my ($action, $values) = ($1, $2);
203              
204             if ($action eq "py") {
205             my @points = split(" ", $values);
206              
207             foreach my $i (0 .. $#points - 1) {
208             my ($x1, $y1) = split(",", $points[$i]);
209             my ($x2, $y2) = split(",", $points[$i + 1]);
210              
211             $image->line($x1, $y1, $x2, $y2, $draw_color);
212             }
213             }
214              
215             elsif ($action eq "fl") {
216             my ($x1, $y1) = split(",", $values);
217              
218             $image->fill($x1, $y1, $fill_color);
219             }
220              
221             elsif ($action eq "nm" && $values eq ":auto") {
222             my ($number) = $shape_keys[$shape_i] =~ /:(\d+)/;
223             if ($number && $number > 99) {
224             $number = 'M';
225             }
226             if (!$number) {
227             $number = 'U';
228             }
229            
230             my $x = int(($side_length - 5 * length($number)) / 2) + 1;
231             my $y = ($side_length - 8) / 2;
232            
233             $image->string(gdTinyFont, $x, $y, $number, $draw_color);
234             }
235            
236             else {
237             croak(
238             "Unrecognized action ($action) in shapes file!");
239             }
240             }
241              
242             my $icon_file =
243             qq[$icon_dir/${icon_prefix}-${shape_i}-${color_i}-${sval_i}.png];
244             my $icon_file_name =
245             qq[${icon_prefix}-${shape_i}-${color_i}-${sval_i}.png];
246              
247             $self->icon(
248             $shape_keys[$shape_i], $color_keys[$color_i],
249             $sval_keys[$sval_i], $icon_file_name
250             );
251              
252             open(PNG, ">$icon_file")
253             or croak("Cannot write file ($icon_file): $!");
254             binmode PNG;
255             print PNG $image->png;
256             close PNG;
257             }
258             }
259              
260             }
261              
262             return 1;
263             }
264              
265             ###################
266             # GET/SET METHODS #
267             ###################
268              
269             sub alpha {
270             my ($self, $value) = @_;
271             $self->{alpha} = $value if @_ > 1;
272             return $self->{alpha};
273             }
274              
275             sub color_keys {
276             my ($self, $value) = @_;
277             $self->{color_keys} = $value if @_ > 1;
278             return $self->{color_keys};
279             }
280              
281             sub color_values {
282             my ($self, $value) = @_;
283             $self->{color_values} = $value if @_ > 1;
284             return $self->{color_values};
285             }
286              
287             sub config {
288             my ($self, $value) = @_;
289             $self->{config} = $value if @_ > 1;
290             return $self->{config};
291             }
292              
293             sub icon_dir {
294             my ($self, $value) = @_;
295             $self->{icon_dir} = $value if @_ > 1;
296             return $self->{icon_dir};
297             }
298              
299             sub icon_prefix {
300             my ($self, $value) = @_;
301             $self->{icon_prefix} = $value if @_ > 1;
302             return $self->{icon_prefix};
303             }
304              
305             sub shape_keys {
306             my ($self, $value) = @_;
307             $self->{shape_keys} = $value if @_ > 1;
308             return $self->{shape_keys};
309             }
310              
311             sub shape_values {
312             my ($self, $value) = @_;
313             $self->{shape_values} = $value if @_ > 1;
314             return $self->{shape_values};
315             }
316              
317             sub sval_keys {
318             my ($self, $value) = @_;
319             $self->{sval_keys} = $value if @_ > 1;
320             return $self->{sval_keys};
321             }
322              
323             sub sval_values {
324             my ($self, $value) = @_;
325             $self->{sval_values} = $value if @_ > 1;
326             return $self->{sval_values};
327             }
328              
329             ###########################
330             # PRIVATE/UTILITY METHODS #
331             ###########################
332              
333             # Function : Apply an S Value to color in RGB
334             # Arguments : (\@color, $sval_percent)
335             # Returns : \@new_color
336             # Notes : This is a private method.
337              
338             sub _blend_in_sval {
339             my ($self, $color, $sval_percent) = @_;
340              
341             my ($rval, $gval, $bval) = @$color;
342              
343             my ($hval, $sval, $vval) = $self->_RGBtoHSV($rval, $gval, $bval);
344              
345             $sval = int($sval * $sval_percent / 100);
346              
347             my @new_color = $self->_HSVtoRGB($hval, $sval, $vval);
348              
349             return \@new_color;
350             }
351              
352             # Function : Determine the type of color designation
353             # Arguments : $color
354             # Returns : $rgb
355             # Notes : This is a private method.
356              
357             sub _resolve_color {
358             my ($self, $color) = @_;
359              
360             my $rgb;
361              
362             if (ref $color) {
363             $rgb = $color;
364             }
365              
366             elsif ($color =~ /^#/) {
367             $rgb = $self->_hex2rgb($color);
368             }
369              
370             else {
371             my $hex = $self->config->{color}->{$color};
372              
373             $rgb = $self->_hex2rgb($hex);
374             }
375              
376             croak("Cannot resolve color ($color)!") unless $rgb;
377              
378             return $rgb;
379             }
380              
381             # Function : Convert hex to rgb
382             # Arguments : $hex
383             # Returns : \@rgb
384             # Notes : This is a private method.
385              
386             sub _hex2rgb {
387             my ($self, $hex) = @_;
388              
389             $hex =~ s/^#//;
390              
391             my @rgb = map { hex(substr($hex, $_, 2)); } qw[0 2 4];
392              
393             return \@rgb;
394             }
395              
396             ##################################################
397             # _HSVtoRGB and _RGBtoHSV subs incorporated from #
398             # GD::Simple module written by Lincoln Stein #
399             ##################################################
400              
401             # ($red,$green,$blue) = GD::Simple->HSVtoRGB($hue,$saturation,$value)
402             #
403             # Convert a Hue/Saturation/Value (HSV) color into an RGB triple. The
404             # hue, saturation and value are integers from 0 to 255.
405              
406             sub _HSVtoRGB {
407             my $self = shift;
408             @_ == 3
409             or croak "Usage: _HSVtoRGB(\$hue,\$saturation,\$value)";
410              
411             my ($h, $s, $v) = @_;
412             my ($r, $g, $b, $i, $f, $p, $q, $t);
413              
414             if ($s == 0) {
415             ## achromatic (grey)
416             return ($v, $v, $v);
417             }
418             $h %= 255;
419             $s /= 255; ## scale saturation from 0.0-1.0
420             $h /= 255; ## scale hue from 0 to 1.0
421             $h *= 360; ## and now scale it to 0 to 360
422              
423             $h /= 60; ## sector 0 to 5
424             $i = $h % 6;
425             $f = $h - $i; ## factorial part of h
426             $p = $v * (1 - $s);
427             $q = $v * (1 - $s * $f);
428             $t = $v * (1 - $s * (1 - $f));
429              
430             if ($i < 1) {
431             $r = $v;
432             $g = $t;
433             $b = $p;
434             }
435             elsif ($i < 2) {
436             $r = $q;
437             $g = $v;
438             $b = $p;
439             }
440             elsif ($i < 3) {
441             $r = $p;
442             $g = $v;
443             $b = $t;
444             }
445             elsif ($i < 4) {
446             $r = $p;
447             $g = $q;
448             $b = $v;
449             }
450             elsif ($i < 5) {
451             $r = $t;
452             $g = $p;
453             $b = $v;
454             }
455             else {
456             $r = $v;
457             $g = $p;
458             $b = $q;
459             }
460             return (int($r + 0.5), int($g + 0.5), int($b + 0.5));
461             }
462              
463             # ($hue,$saturation,$value) = GD::Simple->RGBtoHSV($hue,$saturation,$value)
464             #
465             # Convert a Red/Green/Blue (RGB) value into a Hue/Saturation/Value (HSV)
466             # triple. The hue, saturation and value are integers from 0 to 255.
467              
468             sub _RGBtoHSV {
469             my $self = shift;
470             my ($r, $g, $bl) = @_;
471             my ($min, undef, $max) = sort { $a <=> $b } ($r, $g, $bl);
472             return (0, 0, 0) unless $max > 0;
473              
474             my $v = $max;
475             my $s = 255 * ($max - $min) / $max;
476             my $h;
477             my $range = $max - $min;
478              
479             if ($range == 0) { # all colors are equal, so monochrome
480             return (0, 0, $max);
481             }
482              
483             if ($max == $r) {
484             $h = 60 * ($g - $bl) / $range;
485             }
486             elsif ($max == $g) {
487             $h = 60 * ($bl - $r) / $range + 120;
488             }
489             else {
490             $h = 60 * ($r - $g) / $range + 240;
491             }
492              
493             $h = int($h * 255 / 360 + 0.5);
494              
495             return ($h, $s, $v);
496             }
497              
498             1;
499              
500             __END__