File Coverage

blib/lib/Number/RGB.pm
Criterion Covered Total %
statement 45 77 58.4
branch 1 16 6.2
condition 0 3 0.0
subroutine 15 31 48.3
pod 7 7 100.0
total 68 134 50.7


line stmt bran cond sub pod time code
1             package Number::RGB;
2             # $Id: RGB.pm,v 1.2 2004/03/06 16:17:02 cwest Exp $
3 1     1   24172 use strict;
  1         3  
  1         40  
4              
5 1     1   5 use vars qw[$VERSION $CONSTRUCTOR_SPEC];
  1         2  
  1         369  
6             $VERSION = (qw$Revision: 1.2 $)[1];
7              
8             =head1 NAME
9              
10             Number::RGB - Manipulate RGB Tuples
11              
12             =head1 SYNOPSIS
13              
14             use Number::RGB;
15             my $white :RGB(255);
16             my $black :RGB(0);
17              
18             my $gray = $black + (($white - $black) / 2);
19              
20             my @rgb = @{ $white->rgb };
21             my $hex = $black->hex;
22              
23             my $blue = Number::RGB->new(rgb => [0,0,255]);
24             my $green = Number::RGB->new(hex => '#00FF00');
25              
26             my $red :RGB(255,0,0);
27              
28             my $purple = $blue + $green;
29             my $yellow = $red + $green;
30              
31             =cut
32              
33 1     1   7 use Carp;
  1         7  
  1         120  
34 1     1   1012 use Params::Validate qw[:all];
  1         11655  
  1         265  
35 1     1   10 use base qw[Class::Accessor::Fast];
  1         2  
  1         1000  
36 1     1   5143 use Attribute::Handlers;
  1         12920  
  1         7  
37              
38             sub import {
39 1     1   10 my $class = shift;
40 1         4 my $caller = (caller)[0];
41 1     1   6 eval qq[
  1     1   3  
  1     1   14  
  1         71  
  1         2  
  1         4  
  1         2230  
  1         73  
42             package $caller;
43             use Attribute::Handlers;
44             sub RGB :ATTR(SCALAR) { goto &$class\::RGB }
45             package $class;
46             ];
47             }
48              
49             use overload fallback => 1,
50             '""' => \&as_string,
51 0     0   0 '+' => sub { shift->_op_math('+', @_) },
52 0     0   0 '-' => sub { shift->_op_math('-', @_) },
53 0     0   0 '*' => sub { shift->_op_math('*', @_) },
54 0     0   0 '/' => sub { shift->_op_math('/', @_) },
55 0     0   0 '%' => sub { shift->_op_math('%', @_) },
56 0     0   0 '**' => sub { shift->_op_math('**', @_) },
57 0     0   0 '<<' => sub { shift->_op_math('<<', @_) },
58 0     0   0 '>>' => sub { shift->_op_math('>>', @_) },
59 0     0   0 '&' => sub { shift->_op_math('&', @_) },
60 0     0   0 '^' => sub { shift->_op_math('^', @_) },
61 1     1   2302 '|' => sub { shift->_op_math('|', @_) };
  1     0   1086  
  1         31  
  0         0  
62              
63             =head1 DESCRIPTION
64              
65             This module creates RGB tuple objects and overloads their operators to
66             make RGB math easier. An attribute is also exported to the caller to
67             make construction shorter.
68              
69             =head2 Methods
70              
71             =over 4
72              
73             =item C
74              
75             my $red = Number::RGB->new(rgb => [255,0,0])
76             my $blue = Number::RGB->new(hex => '#0000FF');
77             my $black = Number::RGB->new(rgb_number => 0);
78              
79             This constructor accepts named parameters. One of three parameters are
80             required.
81              
82             C is a list reference containing three intergers within the range
83             of C<0..255>. In order, each interger represents I, I, and
84             I.
85              
86             C is a hexidecimal representation of an RGB tuple commonly used in
87             Cascading Style Sheets. The format begins with an optional hash (C<#>)
88             and follows with three groups of hexidecimal numbers represending
89             I, I, and I in that order.
90              
91             C is a single integer which represents all primary colors.
92             This is shorthand to create I, I, and all shades of
93             I.
94              
95             This method throws and exception on error, which should be caught with
96             C.
97              
98             =cut
99              
100             sub new {
101 3     3 1 5 my $class = shift;
102 3         469 my %params = validate( @_, $CONSTRUCTOR_SPEC );
103 0 0       0 croak "$class->new() requires parameters" unless keys %params;
104              
105 0         0 my %rgb;
106 0 0       0 if ( defined $params{rgb} ) {
    0          
    0          
107 0         0 @rgb{qw[r g b]} = @{$params{rgb}};
  0         0  
108             } elsif ( defined $params{rgb_number} ) {
109 0         0 return $class->new(rgb => [($params{rgb_number})x3]);
110             } elsif ( defined $params{hex} ) {
111 0         0 my $hex = $params{hex};
112 0         0 $hex =~ s/^#//;
113 0 0       0 $hex =~ s/(.)/$1$1/g if length($hex) == 3;
114 0         0 @rgb{qw[r g b]} = map hex, $hex =~ /(.{2})/g;
115             }
116              
117 0         0 $class->SUPER::new(\%rgb);
118             }
119              
120             =pod
121              
122             =item C
123              
124             Accessor and mutator for the I value.
125              
126             =item C
127              
128             Accessor and mutator for the I value.
129              
130             =item C
131              
132             Accessor and mutator for the I value.
133              
134             =cut
135              
136             __PACKAGE__->mk_accessors( qw[r g b] );
137              
138             =pod
139              
140             =item C
141              
142             Returns a list reference containing three elements. In order they
143             represent I, I, and I.
144              
145             =item C
146              
147             Returns a hexidecimal represention of the tuple conforming to the format
148             used in Cascading Style Sheets.
149              
150             =item C
151              
152             Returns the same thing as C, but any hexidecimal numbers that
153             include C<'A'..'F'> will be uppercased.
154              
155             =item C
156              
157             Returns a string representation of the tuple. For example, I
158             would be the string C<255,255,255>.
159              
160             =cut
161              
162 0     0 1 0 sub rgb { [ map $_[0]->$_, qw[r g b] ] }
163 0     0 1 0 sub hex { '#' . join '', map { substr sprintf('0%x',$_[0]->$_), -2 } qw[r g b] }
  0         0  
164 0     0 1 0 sub hex_uc { uc shift->hex }
165             sub as_string {
166 0     0 1 0 join ',', map $_[0]->$_, qw[r g b]
167             }
168              
169             sub _op_math {
170 0     0   0 my ($self,$op, $other, $reversed) = @_;
171 0         0 ref($self)->new(rgb => [
172             map {
173 0         0 my $x = $self->$_;
174 0 0 0     0 my $y = ref($other) && overload::Overloaded($other) ? $other->$_ : $other;
175 0 0       0 int eval ($reversed ? "$y $op $x" : "$x $op $y");
176             } qw[r g b]
177             ] );
178             }
179              
180             =pod
181              
182             =item C
183              
184             my $color = Number::RGB->new_from_guess(input());
185              
186             This constructor tries to guess the format being used and returns a
187             tuple object. If it can't guess, an exception will be thrown.
188              
189             =back
190              
191             =cut
192              
193             sub new_from_guess {
194 1     1 1 4 my ($class, $value) = @_;
195 1         3 foreach my $param ( keys %{$CONSTRUCTOR_SPEC} ) {
  1         5  
196 3         6 my $self = eval { $class->new($param => $value) };
  3         9  
197 3 50       1634 return $self if defined $self;
198             }
199 1         186 croak "$class->new_from_guess() couldn't guess type for ($value)";
200             }
201              
202             =head2 Attributes
203              
204             =over 4
205              
206             =item C<:RGB()>
207              
208             my $red :RGB(255,0,0);
209             my $blue :RGB(#0000FF);
210             my $white :RGB(0);
211              
212             This attribute is exported to the caller and provides a shorthand wrapper
213             around C.
214              
215             =back
216              
217             =cut
218              
219             sub RGB :ATTR(SCALAR) {
220 1     1 1 4 my ($var, $data) = @_[2,4];
221 1         8 $$var = __PACKAGE__->new_from_guess($data);
222 1     1   1036 }
  1         4  
  1         8  
223              
224             $CONSTRUCTOR_SPEC = {
225             rgb => {
226             type => ARRAYREF,
227             optional => 1,
228             callbacks => {
229             'three elements' => sub { 3 == @{$_[0]} },
230             'only digits' => sub { 0 == grep /\D/, @{$_[0]} },
231             'between 0 and 255' => sub { 3 == grep { $_ >= 0 && $_ <= 255 } @{$_[0]} },
232             },
233             },
234             rgb_number => {
235             type => SCALAR,
236             optional => 1,
237             callbacks => {
238             'only digits' => sub { $_[0] !~ /\D/ },
239             'between 0 and 255' => sub { $_[0] >= 0 && $_[0] <= 255 },
240             },
241             },
242             hex => {
243             type => SCALAR,
244             optional => 1,
245             callbacks => {
246             'hex format' => sub { $_[0] =~ /^#?(?:[\da-f]{3}|[\da-f]{6})$/i },
247             },
248             }
249             };
250              
251             1;
252              
253             __END__