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