File Coverage

blib/lib/Color/Fade.pm
Criterion Covered Total %
statement 6 124 4.8
branch 0 68 0.0
condition n/a
subroutine 2 6 33.3
pod 3 4 75.0
total 11 202 5.4


line stmt bran cond sub pod time code
1             package Color::Fade;
2              
3 1     1   25844 use strict;
  1         3  
  1         50  
4 1     1   5 use warnings;
  1         2  
  1         2057  
5              
6             require Exporter;
7              
8             our @ISA = qw(Exporter);
9              
10             our @EXPORT_OK = qw (
11             color_fade
12             format_color
13             );
14              
15             our @EXPORT = qw(
16             );
17              
18             our $VERSION = '0.02';
19             our $debug = 0;
20              
21             sub debug {
22 0     0 0   my $val = shift;
23 0 0         if ($val) {
24 0           $debug = 1;
25             }
26 0           return $debug;
27             }
28              
29             sub format_color {
30 0     0 1   my ($format,@codes) = @_;
31              
32             # Some pre-defined formats.
33 0           my %formats = (
34             html => '$char',
35             ubb => '[color=$color]$char[/color]',
36             css => '$char',
37             );
38 0 0         if (exists $formats{$format}) {
39 0           $format = $formats{$format};
40             }
41              
42             # Loop through the given codes.
43 0           my @out = ();
44 0           foreach my $part (@codes) {
45 0           my ($color,$char) = $part =~ /^]+?)>(.+?)$/i;
46 0           my $result = $format;
47 0           $result =~ s/\$color/$color/ig;
48 0           $result =~ s/\$char/$char/ig;
49 0           push (@out,$result);
50             }
51 0 0         return wantarray ? (@out) : join("",@out);
52             }
53              
54             sub color_fade {
55 0     0 1   my ($text,@in_colors) = @_;
56              
57             # Validate the arguments.
58 0 0         if (not length $text) {
59 0           warn "You must pass a string with a length > 0 to color_fade.";
60 0           return;
61             }
62 0 0         if (not scalar(@in_colors)) {
63 0           warn "You must pass a series of hexadecimal color codes to color_fade.";
64 0           return;
65             }
66              
67             # There must be at least two colors.
68 0 0         if (scalar(@in_colors) < 2) {
69 0           warn "color_fade requires at least two colors.";
70 0           return;
71             }
72              
73             # Validate and clean up color codes.
74 0           my @nodes = ();
75 0           foreach my $ccode (@in_colors) {
76 0           $ccode =~ s/#//g; # Remove hex indicators.
77 0 0         if (length $ccode != 6) {
78 0           warn "You must pass 6 digit hexadecimal color codes to color_fade.";
79 0           return;
80             }
81 0 0         if ($ccode =~ /^[^A-Fa-f0-9]$/i) {
82 0           warn "You must pass 6 digit hexadecimal color codes to color_fade.";
83 0           return;
84             }
85 0           push (@nodes,$ccode);
86             }
87              
88             # Get the length of the string.
89 0           my $len = length $text;
90              
91             # Divide the length into segments (number of colors - 1)
92 0           my $sections = $len / (scalar(@nodes) - 1);
93 0 0         if ($sections =~ /\./) {
94             # If it was a decimal, add one and int it.
95 0           $sections += 1;
96             }
97 0           $sections = int($sections);
98              
99             # If the length of a given segment of text is too great, no fading will
100             # occur (for ex: 255-0-0 to 0-0-0 has a delta of -255 in the red. If the
101             # length of this segment is >255 characters, 255/(>255) will result in
102             # a fraction less than 1, so no per-character offset will be computed.
103             # So, if the length of the segments is gonna be too long, double the
104             # number of nodes...
105 0           my $giveup = 0;
106 0           while ($sections > 128) {
107 0           my @newNodes = ();
108 0           for (my $i = 0; $i < scalar(@nodes); $i++) {
109 0           my $color = $nodes[$i];
110 0           push (@newNodes,$color);
111              
112             # If we have another color after the one we're looping
113             # on right now--good.
114 0 0         if ($i < scalar(@nodes)) {
115 0           my $neighbor = $nodes[$i + 1];
116 0 0         $neighbor = $color unless defined $neighbor;
117              
118             # Find the average between these two colors.
119 0           my $average = Color::Fade::average_colors($color,$neighbor);
120 0           push (@newNodes,$average);
121             }
122             }
123 0           (@nodes) = @newNodes;
124              
125 0           $sections = $len / (scalar(@nodes) - 1);
126 0 0         if ($sections =~ /\./) {
127 0           $sections += 1;
128             }
129 0           $sections = int($sections);
130 0           $giveup++;
131 0 0         if ($giveup > 100) {
132             # After 100 tries to factor this down, let's just give up.
133 0           last;
134             }
135             }
136              
137             # Split the string into individual characters.
138 0           my @chars = split(//, $text);
139 0           my @faded = ();
140              
141 0 0         print "Color::Fade: preparing to fade a string.\n"
142             . "length of string: $len\n"
143             . "number of nodes (colors): " . scalar(@nodes) . "\n"
144             . "number of char per segment: $sections\n" if $debug;
145              
146 0 0         if ($giveup > 0) {
147 0 0         print "Note: the input string was very long: a given\n"
148             . "segment would be >128 characters in length, which\n"
149             . "doesn't make for a good fade effect. It was factors\n"
150             . "down $giveup times.\n" if $debug;
151             }
152              
153 0 0         print "Color::Fade: beginning the segment loop\n" if $debug;
154              
155 0           my $nodeStart = 0;
156 0           for (my $i = 0; $i < $len; $i += $sections) {
157             # Find the length of this segment.
158 0           my $seglen = ($i + $sections) - $i;
159              
160             # Separate the RGB components of the start and end colors.
161 0           my (@RGB_Hex_Start) = $nodes[$nodeStart] =~ /^(..)(..)(..)$/i; # /^([0-9A-Fa-f]{2}){3}$/i;
162 0           my (@RGB_Hex_End) = $nodes[$nodeStart + 1] =~ /^(..)(..)(..)$/i; # /^([0-9A-Fa-f]{2}){3}$/i;
163 0 0         (@RGB_Hex_End) = (@RGB_Hex_Start) unless scalar(@RGB_Hex_End);
164              
165             # Convert hexadecimal to decimal.
166 0           my @RGB_Dec_Start = (
167             hex ("0x" . $RGB_Hex_Start[0]),
168             hex ("0x" . $RGB_Hex_Start[1]),
169             hex ("0x" . $RGB_Hex_Start[2]),
170             );
171 0           my @RGB_Dec_End = (
172             hex ("0x" . $RGB_Hex_End[0]),
173             hex ("0x" . $RGB_Hex_End[1]),
174             hex ("0x" . $RGB_Hex_End[2]),
175             );
176              
177             # Find the distances in Red/Green/Blue values.
178 0           my $distR = $RGB_Dec_Start[0] - $RGB_Dec_End[0];
179 0           my $distG = $RGB_Dec_Start[1] - $RGB_Dec_End[1];
180 0           my $distB = $RGB_Dec_Start[2] - $RGB_Dec_End[2];
181              
182 0 0         $distR < 0 ? $distR = abs($distR) : $distR = -$distR;
183 0 0         $distG < 0 ? $distG = abs($distG) : $distG = -$distG;
184 0 0         $distB < 0 ? $distB = abs($distB) : $distB = -$distB;
185              
186             # Divide each distance by the length of this segment,
187             # so we can find out how many characters to operate on.
188 0           my $charsR = int($distR / $seglen);
189 0           my $charsG = int($distG / $seglen);
190 0           my $charsB = int($distB / $seglen);
191              
192 0 0         print " Segment length: $seglen\n"
193             . " RGB Start: " . join("-",@RGB_Dec_Start) . "\n"
194             . " RGB End: " . join("-",@RGB_Dec_End) . "\n"
195             . " RGB Delta: " . join(" : ",$distR,$distG,$distB) . "\n"
196             . " RGB Chars: " . join(" : ",$charsR,$charsG,$charsB) . "\n" if $debug;
197              
198             # For each character in this segment...
199 0           my ($r,$g,$b) = @RGB_Dec_Start;
200 0           for (my $c = $i; $c < ($i + $seglen); $c++) {
201 0 0         next unless defined $chars[$c];
202 0 0         print " Working with character $chars[$c]\n" if $debug;
203              
204             # Convert each color value back into hex.
205 0           my $hexR = sprintf ("%02x", $r);
206 0           my $hexG = sprintf ("%02x", $g);
207 0           my $hexB = sprintf ("%02x", $b);
208              
209             # Turn the hex values into a color code.
210 0           my $code = join ("", $hexR, $hexG, $hexB);
211              
212 0 0         print " Hex code: $code => $chars[$c]\n" if $debug;
213              
214             # Prepare an easy to parse color marker for this character.
215 0 0         $chars[$c] = " " if $chars[$c] =~ /^[\x0d\x0a]$/;
216 0           my $marker = "" . $chars[$c];
217              
218             # Append this color information to the output array.
219 0           push (@faded,$marker);
220              
221             # Increment each color by charsR, charsG, and charsB at a time.
222 0           $r += $charsR;
223 0           $g += $charsG;
224 0           $b += $charsB;
225              
226             # Keep the numbers within a valid range.
227 0 0         $r = 0 if $r < 0;
228 0 0         $g = 0 if $g < 0;
229 0 0         $b = 0 if $b < 0;
230 0 0         $r = 255 if $r > 255;
231 0 0         $g = 255 if $g > 255;
232 0 0         $b = 255 if $b > 255;
233              
234 0 0         print " RGB for next char: $r-$g-$b\n" if $debug;
235             }
236              
237 0           $nodeStart++;
238             }
239              
240 0 0         return wantarray ? @faded : join ("",@faded);
241             }
242              
243             sub average_colors {
244 0     0 1   my ($alpha,$beta) = @_;
245              
246             # This function, given two hex colors, returns the value of the color
247             # directly between the two colors (an average of two).
248              
249             # Separate the hex values.
250 0           my (@hexStart) = $alpha =~ /^(..)(..)(..)$/i;
251 0           my (@hexEnd) = $beta =~ /^(..)(..)(..)$/i;
252              
253             # Get their numeric counterparts.
254 0           my @decStart = (
255             hex("0x" . $hexStart[0]),
256             hex("0x" . $hexStart[1]),
257             hex("0x" . $hexStart[2]),
258             );
259 0           my @decEnd = (
260             hex("0x" . $hexEnd[0]),
261             hex("0x" . $hexEnd[1]),
262             hex("0x" . $hexEnd[2]),
263             );
264              
265             # Get the averages of each color.
266 0           my $avRed = int( ($decStart[0] + $decEnd[0]) / 2 );
267 0           my $avGrn = int( ($decStart[1] + $decEnd[1]) / 2 );
268 0           my $avBlu = int( ($decStart[2] + $decEnd[2]) / 2 );
269              
270             # And convert the averages back into hex.
271 0           my @hexAvg = (
272             sprintf ("%02x", $avRed),
273             sprintf ("%02x", $avGrn),
274             sprintf ("%02x", $avBlu),
275             );
276 0           return join("",@hexAvg);
277             }
278              
279             1;
280             __END__