File Coverage

blib/lib/Colouring/In.pm
Criterion Covered Total %
statement 173 197 87.8
branch 94 102 92.1
condition 41 61 67.2
subroutine 29 35 82.8
pod 26 26 100.0
total 363 421 86.2


line stmt bran cond sub pod time code
1             package Colouring::In;
2              
3 13     13   876338 use 5.006;
  13         148  
4 13     13   67 use strict;
  13         25  
  13         367  
5 13     13   71 use warnings;
  13         33  
  13         441  
6 13     13   5983 use smallnum;
  13         124249  
  13         56  
7             our $VERSION = '0.23';
8              
9             our (%TOOL, $ANOBJECT);
10              
11             use overload
12 13     13   1804 '""' => sub { $_[0]->toCSS() };
  13     3   30  
  13         68  
  3         324  
13              
14              
15             BEGIN {
16             %TOOL = (
17 30557         281891 clamp => sub { return $TOOL{min}( $TOOL{max}( $_[0], 0 ), $_[1]); },
18 30728 100 100     222574 max => sub { $_[ ($_[0] || 0) < ($_[1] || 0) ] || 0 },
      100        
19 30728 100 100     1228779 min => sub { $_[ ($_[0] || 0) > ($_[1] || 0) ] || 0 },
      100        
20             round => sub {
21 266 100       2070 return sprintf '%.' . ( defined $_[1] ? $_[1] : 0 ) . 'f', $_[0];
22             },
23 10148   100     49017 numIs => sub { return defined $_[0] && $_[0] =~ /^[0-9]+/; },
24 40         557 percent => sub { return ( $_[0] * 100 ) . '%'; },
25 20081         22850 depercent => sub { my $p = shift; $p =~ s/%$//; return $p / 100; },
  20081         25608  
  20081         28161  
26             joinRgb => sub {
27 54         116 return join ',', map { $TOOL{clamp}( $TOOL{round}($_), 255 ); } @_;
  162         3123  
28             },
29             rgb2hs => sub {
30 83         173 my @rgb = map { $_ / 255 } @_;
  249         2456  
31 83         1114 push @rgb, $TOOL{max}( $TOOL{max}( $rgb[0], $rgb[1] ), $rgb[2] );
32 83         4170 push @rgb, $TOOL{min}( $TOOL{min}( $rgb[0], $rgb[1] ), $rgb[2] );
33 83         4256 push @rgb, ( $rgb[3] - $rgb[4] );
34 83         1272 return @rgb;
35             },
36             hue => sub {
37 30221         1089008 my ( $h, $m1, $m2 ) = @_;
38 30221 100       47798 $h = $h < 0 ? $h + 1 : ( $h > 1 ? $h - 1 : $h );
    100          
39 30221 100       474835 if ( $h * 6 < 1 ) { return $m1 + ( $m2 - $m1 ) * $h * 6; }
  5091 100       99484  
    100          
40 9993         369334 elsif ( $h * 2 < 1 ) { return $m2; }
41             elsif ( $h * 3 < 2 ) {
42 5063         274756 return $m1 + ( $m2 - $m1 ) * ( 2 / 3 - $h ) * 6;
43             }
44 20228         548995 return $m1;
45             },
46             scaled => sub {
47 197         1467 my ( $n, $size ) = @_;
48 197 100       364 return ( $n =~ s/%// )
49             ? sprintf( '%.f2', (($n * $size) / 100 ))
50             : return sprintf( "%d", $n );
51             },
52             convertColour => sub {
53 10082         29325 my $colour = shift;
54 10082         22315 my %converter = (
55             '#' => 'hex2rgb',
56             'rgb' => 'rgb2rgb',
57             'hsl' => 'hsl2rgb',
58             'hsla' => 'hsl2rgb',
59             );
60 10082         34221 my $reg = join '|', reverse sort keys %converter;
61 10082 100       49073 if ( $colour =~ s/^($reg)// ) {
62 10081         24090 return $TOOL{ $converter{$1} }($colour);
63             }
64 1   50     22 die $TOOL{MESSAGES}{INVALID_COLOUR} || 'Cannot convert the colour format';
65             },
66             rgb2rgb => sub {
67 19         47 my @numbers = $TOOL{numbers}(shift);
68 19 100 50     83 die $TOOL{MESSAGES}{INVALID_RGB} || 'Cannot convert rgb colour format' unless (scalar @numbers > 2);
69 18         319 return @numbers;
70             },
71             hex2rgb => sub {
72 53         722 my $hex = shift;
73 53         90 my $l = length $hex;
74             return $l != 6
75             ? $l == 3
76 13 100 100     147 ? map { my $h = hex( $_ . $_ ); $_ =~ 0 || $h ? $h : die( $TOOL{MESSAGES}{INVALID_HEX} || 'Cannot convert hex colour format' ) } $hex =~ m/./g
  13   50     110  
77             : die 'hex length must be 3 or 6'
78 53 100 66     453 : map { my $h = hex( $_ ); $_ =~ m/00/ || $h ? $h : die( $TOOL{MESSAGES}{INVALID_HEX} || 'Cannot convert hex colour format' ) } $hex =~ m/../g;
  141 50 0     1045  
  141 100       990  
79             },
80             hsl2rgb => sub {
81 10074 100       19240 my ( $h, $s, $l, $a, $m1, $m2 ) = scalar @_ > 1 ? @_ : $TOOL{numbers}(shift);
82 10074   66     77319 defined $_ && $_ =~ m/([0-9.]+)/ or die $TOOL{MESSAGES}{INVALID_HSL} || 'Cannot convert hsl colour format' for ($h, $s, $l);
      50        
      100        
83 10073         22119 $h = ( $h % 360 ) / 360;
84 10073 100       170839 unless ($m1) {
85 10009         14529 $s = $TOOL{depercent}($s);
86 10009         101956 $l = $TOOL{depercent}($l);
87             }
88 10073 100       102415 $m2 = $l <= 0.5 ? $l * ( $s + 1 ) : $l + $s - $l * $s;
89 10073         291113 $m1 = $l * 2 - $m2;
90             return (
91             ($TOOL{clamp}($TOOL{hue}( $h + 1 / 3, $m1, $m2 ), 1) * 255),
92             ($TOOL{clamp}($TOOL{hue}( $h, $m1, $m2 ), 1) * 255),
93 10073 100       194237 ($TOOL{clamp}($TOOL{hue}( $h - 1 / 3, $m1, $m2 ), 1) * 255),
94             ( defined $a ? $a : () ),
95             );
96             },
97             numbers => sub {
98 10029         115127 return ( $_[0] =~ m/([0-9.]+)/g );
99             },
100             hsl => sub {
101 63         112 my $colour = shift;
102 63 100       231 if ( ref \$colour eq 'SCALAR' ) {
103 50         149 $colour = Colouring::In->new($colour);
104             }
105 63         148 my $hsl = $TOOL{asHSL}($colour);
106 63         1585 return ( $hsl, $colour );
107             },
108             hash2array => sub {
109 63         101 my $hash = shift;
110 63         117 return map { $hash->{$_} } @_;
  252         531  
111             },
112             asHSL => sub {
113 75         271 my ( $r, $g, $b, $max, $min, $d, $h, $s, $l ) = $TOOL{rgb2hs}( $_[0]->colour );
114              
115 75         184 $l = ( $max + $min ) / 2;
116 75 100       1744 if ( $max == $min ) {
117 68         987 $h = $s = 0;
118             }
119             else {
120 7         88 $d = smallnum::smallnum($d); #grrr
121 7 100       33 $s = $l > 0.5 ? ($d / ( 2 - $max - $min )) : ($d / ( $max + $min ));
122 7 100       252 $h = ( $max == $r )
    100          
    100          
123             ? ( $g - $b ) / $d + ( $g < $b ? 6 : 0 )
124             : ( $max == $g )
125             ? ( $b - $r ) / $d + +2
126             : ( $r - $g ) / $d + 4;
127 7         404 $h /= 6;
128             }
129              
130             return {
131             h => $h * 360,
132             s => $s,
133             l => $l,
134             a => $_[0]->{alpha},
135 75         276 };
136             }
137 13     13   32035 );
138             }
139              
140             sub import {
141 13     13   323 my ($pkg, @exports) = @_;
142 13         41 my $caller = caller;
143 13 100       60 $TOOL{MESSAGES} = pop @exports if (ref $exports[-1] eq 'HASH');
144 13 100       11455 if (scalar @exports) {
145 13     13   985 no strict 'refs';
  13         26  
  13         653  
146 5         15 *{"${caller}::${_}"} = \&{"${_[0]}::${_}"} foreach @exports;
  5         8729  
  5         24  
147             }
148             }
149              
150             sub rgb {
151 1     1 1 762 return $_[0]->rgba( $_[1], $_[2], $_[3], $_[4] );
152             }
153              
154             sub rgba {
155 65     65 1 2373 my $rgb = [ map { $TOOL{scaled}( $_, 255 ) } ( $_[1], $_[2], $_[3] ) ];
  195         2731  
156 65         1328 return Colouring::In->new( $rgb, $TOOL{clamp}($_[4], 1) );
157             }
158              
159             sub hsl {
160 1     1 1 3 my $self = shift;
161 1         4 return $self->rgba($TOOL{hsl2rgb}(@_, 1));
162             }
163              
164             sub hsla {
165 63     63 1 115 my $self = shift;
166 63         164 return $self->rgba($TOOL{hsl2rgb}(@_, 1));
167             }
168              
169             sub new {
170 10142     10142 1 244454 my ( $pkg, $rgb, $a ) = @_;
171              
172 10142         15659 my $self = bless {}, $pkg;
173             # The end goal here, is to parse the arguments
174             # into an integer triplet, such as `128, 255, 0`
175 10142 100       14968 if ( ref $rgb eq 'ARRAY' ) {
176 78 100       232 scalar @$rgb == 4 and $a = pop @$rgb;
177 78         920 $self->{colour} = $rgb;
178             } else {
179 10064         14707 $self->{colour} = [ $TOOL{convertColour}($rgb) ];
180 10061 100       478657 scalar @{ $self->{colour} } == 4 and $a = pop @{$self->{colour}};
  10014         73015  
  10061         19556  
181             }
182 10139 100       16560 $self->{alpha} = $TOOL{numIs}($a) ? $a : 1;
183 10139         20791 return $self;
184             }
185              
186             sub toCSS {
187 18     18 1 95 my $alpha = $TOOL{round}( $_[0]->{alpha}, $_[1] );
188 18 100       392 return ( $alpha != 1 ) ? $_[0]->toRGBA() : $_[0]->toHEX( $_[2] );
189             }
190              
191             sub toTerm {
192 5     5 1 3447 return sprintf( "r%sg%sb%s", $_[0]->colour );
193             }
194              
195             sub toOnTerm {
196 5     5 1 20 return sprintf( "on_r%sg%sb%s", $_[0]->colour );
197             }
198              
199             sub toRGB {
200 9 50 66 9 1 4158 return $_[0]->toRGBA( $_[1] ) if $TOOL{numIs}( $_[1] ) and $_[1] != 1;
201 9         34 return sprintf( 'rgb(%s)', ( $TOOL{joinRgb}( $_[0]->colour ) ) );
202             }
203              
204             sub toRGBA {
205             return sprintf 'rgba(%s,%s)', $TOOL{joinRgb}( $_[0]->colour ),
206 45     45 1 232 $_[0]->{alpha};
207             }
208              
209             sub toHEX {
210             my $colour = sprintf(
211             "#%02lx%02lx%02lx",
212             (
213 28     28 1 427 map { my $c = $TOOL{clamp}( $TOOL{round}($_), 255 ); $c }
  84         174  
  84         4172  
214             $_[0]->colour
215             )
216             );
217 28 100       453 unless ( $_[1] ) {
218 23 100       138 if ( $colour =~ /#(.)\1(.)\2(.)\3/g ) {
219 22         99 $colour = sprintf "#%s%s%s", $1, $2, $3;
220             }
221             }
222 28         149 return $colour;
223             }
224              
225             sub toHSL {
226 12     12 1 40 my $hsl = $TOOL{asHSL}($_[0]);
227             sprintf( "hsl(%s,%s,%s)",
228             $hsl->{h},
229             $TOOL{percent}( $hsl->{s} ),
230 12         263 $TOOL{percent}( $hsl->{l} ),
231             );
232             }
233              
234             sub toHSV {
235 8     8 1 4659 my ( $r, $g, $b, $max, $min, $d, $h, $s, $v ) = $TOOL{rgb2hs}( $_[0]->colour );
236              
237 8         17 $v = $max;
238 8 100       16 $s = ( $max == 0 ) ? $max : $d / $max;
239              
240 8 100       181 if ( $max == $min ) {
241 2         27 $h = 0;
242             }
243             else {
244 6 100       79 $h = ( $max == $r ) ? ( $g - $b ) / $d + ( $g < $b ? 6 : 0 )
    100          
    100          
245             : ( $max == $g ) ? ( $b - $r ) / $d + 2
246             : ( $r - $g ) / $d + 4;
247 6         243 $h /= 6;
248             }
249              
250             return sprintf( "hsv(%s,%s,%s)",
251             ( $h * 360 ),
252             $TOOL{percent}($s),
253 8         74 $TOOL{percent}($v),
254             );
255             }
256              
257             sub lighten {
258 11     11 1 5070 my ( $colour, $amt, $meth, $hsl ) = @_;
259              
260 11         30 ( $hsl, $colour ) = $TOOL{hsl}($colour);
261              
262 11         26 $amt = $TOOL{depercent}($amt);
263             $hsl->{l} += $TOOL{clamp}(
264             ( $meth && $meth eq 'relative' )
265 11 100 100     183 ? (($hsl->{l} || 1) * $amt)
      50        
266             : $amt, 1
267             );
268              
269 11         653 return $colour->hsla( $TOOL{hash2array}( $hsl, 'h', 's', 'l', 'a' ) );
270             }
271              
272             sub darken {
273 12     12 1 8457 my ( $colour, $amt, $meth, $hsl ) = @_;
274              
275 12         46 ( $hsl, $colour ) = $TOOL{hsl}($colour);
276              
277 12         36 $amt = $TOOL{depercent}($amt);
278             $hsl->{l} -= $TOOL{clamp}(
279             ( $meth && $meth eq 'relative' )
280 12 100 100     219 ? $hsl->{l} * $amt
281             : $amt, 1,
282             );
283              
284 12         730 return $colour->hsla( $TOOL{hash2array}( $hsl, 'h', 's', 'l', 'a' ) );
285             }
286              
287             sub fade {
288 12     12 1 8673 my ($colour, $amt, $hsl) = @_;
289              
290 12         32 ($hsl, $colour) = $TOOL{hsl}($colour);
291 12         29 $hsl->{a} = $TOOL{depercent}($amt);
292              
293 12         169 return $colour->hsla( $TOOL{hash2array}( $hsl, 'h', 's', 'l', 'a' ) );
294             }
295              
296             sub fadeout {
297 14     14 1 8977 my ($colour, $amt, $meth, $hsl) = @_;
298              
299 14         36 ($hsl, $colour) = $TOOL{hsl}($colour);
300             $hsl->{a} -= (($meth && $meth eq 'relative')
301             ? $hsl->{a} * $TOOL{depercent}($amt)
302 14 100 100     64 : $TOOL{depercent}($amt));
303 14         367 return $colour->hsla( $TOOL{hash2array}( $hsl, 'h', 's', 'l', 'a' ) );
304             }
305              
306             sub fadein {
307 14     14 1 11113 my ($colour, $amt, $meth, $hsl) = @_;
308 14         47 ($hsl, $colour) = $TOOL{hsl}($colour);
309             $hsl->{a} += ($meth && $meth eq 'relative')
310             ? $hsl->{a} * $TOOL{depercent}($amt)
311 14 100 100     76 : $TOOL{depercent}($amt);
312 14         321 $hsl->{a} = smallnum::smallnum($hsl->{a});
313 14         97 return $colour->hsla( $TOOL{hash2array}( $hsl, 'h', 's', 'l', 'a' ) );
314             }
315              
316             sub mix {
317 0     0 1 0 my ($colour1, $colour2, $weight) = @_;
318 0         0 my ($h1, $c1, $h2, $c2) = ($TOOL{hsl}($colour1), $TOOL{hsl}($colour2));
319 0   0     0 $weight = ($weight || 50) / 100;
320 0         0 my $a = $h1->{a} - $h2->{a};
321 0         0 my $w = ($weight * 2) - 1;
322 0 0       0 my $w1 = ((($w * $a == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
323 0         0 my $w2 = 1 - $w1;
324             return Colouring::In->new([
325             ($c1->{colour}[0] * $w1) + ($c2->{colour}[0] * $w2),
326             ($c1->{colour}[1] * $w1) + ($c2->{colour}[1] * $w2),
327             ($c1->{colour}[2] * $w1) + ($c2->{colour}[2] * $w2),
328 0         0 ($c1->{alpha} * $weight) + ($c2->{alpha} * 1 - $weight)
329             ]);
330             }
331              
332             sub tint {
333 0     0 1 0 my ($colour, $weight) = @_;
334 0         0 mix(
335             'rgb(255,255,255)',
336             $colour,
337             $weight
338             );
339             }
340              
341             sub shade {
342 0     0 1 0 my ($colour, $weight) = @_;
343 0         0 mix(
344             'rgb(0, 0, 0)',
345             $colour,
346             $weight
347             );
348             }
349              
350             sub saturate {
351 0     0 1 0 my ($colour, $amt, $meth) = @_;
352 0         0 my ($h1, $c1) = $TOOL{hsl}($colour);
353 0         0 $amt = $TOOL{depercent}($amt);
354             $h1->{s} += $TOOL{clamp}(
355             ( $meth && $meth eq 'relative' )
356 0 0 0     0 ? $h1->{s} * $amt
357             : $amt, 1,
358             );
359 0         0 return $c1->hsla( $TOOL{hash2array}( $h1, 'h', 's', 'l', 'a' ) );
360             }
361              
362             sub desaturate {
363 0     0 1 0 my ($colour, $amt, $meth) = @_;
364 0         0 my ($h1, $c1) = $TOOL{hsl}($colour);
365 0         0 $amt = $TOOL{depercent}($amt);
366             $h1->{s} -= $TOOL{clamp}(
367             ( $meth && $meth eq 'relative' )
368 0 0 0     0 ? $h1->{s} * $amt
369             : $amt, 1,
370             );
371 0         0 return $c1->hsla( $TOOL{hash2array}( $h1, 'h', 's', 'l', 'a' ) );
372             }
373              
374             sub greyscale {
375 0     0 1 0 my ($colour) = @_;
376 0         0 desaturate($colour, 100);
377             }
378              
379             sub colour {
380 10193     10193 1 26408 my @rgb = @{ $_[0]->{colour} };
  10193         15963  
381 10193 100       76474 my $r = defined $rgb[0] ? $rgb[0] : 255;
382 10193 100       13795 my $g = defined $rgb[1] ? $rgb[1] : 255;
383 10193 100       12694 my $b = defined $rgb[2] ? $rgb[2] : 255;
384 10193         18396 return ( $r, $g, $b );
385             }
386              
387             sub validate {
388 4     4 1 1986 my ($self, $colour) = @_;
389 4         6 my $new = eval { $self->new($colour) };
  4         10  
390 4 100       43 if ($@) {
391             return {
392             valid => \0,
393 3   50     16 message => $TOOL{MESSAGES}{VALIDATE_ERROR} || 'The string passed to Colouring::In::validate is not a valid color.',
394             color => $colour
395             };
396             }
397             return {
398             valid => \1,
399 1   50     11 message => $TOOL{MESSAGES}{VALIDATE} || 'The string passed to Colouring::In::validate is a valid color',
400             color => $colour,
401             colour => $new
402             };
403             }
404              
405             1;
406              
407             __END__