File Coverage

lib/Graphics/Toolkit/Color.pm
Criterion Covered Total %
statement 136 150 90.6
branch 56 74 75.6
condition 25 31 80.6
subroutine 34 38 89.4
pod 19 29 65.5
total 270 322 83.8


line stmt bran cond sub pod time code
1              
2             # read only color holding object with methods for relation, mixing and transitions
3              
4             package Graphics::Toolkit::Color;
5             our $VERSION = '1.61';
6 4     4   324248 use v5.12;
  4         75  
7              
8 4     4   25 use Carp;
  4         15  
  4         295  
9 4     4   2231 use Graphics::Toolkit::Color::Name;
  4         12  
  4         175  
10 4     4   29 use Graphics::Toolkit::Color::Values;
  4         6  
  4         91  
11              
12 4     4   22 use Exporter 'import';
  4         7  
  4         10141  
13             our @EXPORT_OK = qw/color/;
14              
15             my $new_help = 'constructor of Graphics::Toolkit::Color object needs either:'.
16             ' 1. hash or ref (RGB, HSL or any other): ->new(r => 255, g => 0, b => 0), ->new({ h => 0, s => 100, l => 50 })'.
17             ' 2. RGB array or ref: ->new( [255, 0, 0 ]) or >new( 255, 0, 0 )'.
18             ' 3. hex form "#FF0000" or "#f00" 4. a name: "red" or "SVG:red".';
19              
20             ## constructor #########################################################
21              
22 10     10 1 723 sub color { Graphics::Toolkit::Color->new ( @_ ) }
23              
24             sub new {
25 49     49 1 11393 my ($pkg, @args) = @_;
26 49 100 66     209 @args = ([@args]) if @args == 3 or Graphics::Toolkit::Color::Space::Hub::is_space( $args[0]);
27 49 100 66     223 @args = ({ @args }) if @args == 6 or @args == 8;
28 49 100       184 return carp $new_help unless @args == 1;
29 44         94 _new_from_scalar($args[0]);
30             }
31             sub _new_from_scalar {
32 156     156   226 my ($color_def) = shift;
33 156         214 my ($value_obj, @rgb, $name, $origin);
34             # strings that are not '#112233' or 'rgb: 23,34,56'
35 156 100 100     724 if (not ref $color_def and substr($color_def, 0, 1) =~ /\w/ and $color_def !~ /,/){
    100 66        
36 15         25 $name = $color_def;
37 15         18 $origin = 'name';
38 15         43 my $i = index( $color_def, ':');
39 15 100       26 if ($i > -1 ){ # resolve pallet:name
40 1         2 my $pallet_name = substr $color_def, 0, $i;
41 1         4 my $color_name = Graphics::Toolkit::Color::Name::_clean(substr $color_def, $i+1);
42 1         2 my $module_base = 'Graphics::ColorNames';
43 1     1   328 eval "use $module_base";
  0         0  
  0         0  
  1         74  
44 1 50       36 return carp "$module_base is not installed, but it's needed to load external colors" if $@;
45 0         0 my $module = $module_base.'::'.$pallet_name;
46 0         0 eval "use $module";
47 0 0       0 return carp "$module is not installed, but needed to load color '$pallet_name:$color_name'" if $@;
48              
49 0         0 my $pallet = Graphics::ColorNames->new( $pallet_name );
50 0         0 @rgb = $pallet->rgb( $color_name );
51 0 0       0 return carp "color '$color_name' was not found, propably not part of $module" unless @rgb == 3;
52             } else { # resolve name ->
53 14         42 @rgb = Graphics::Toolkit::Color::Name::rgb_from_name( $color_def );
54 14 100       58 return carp "'$color_def' is an unknown color name, please check Graphics::Toolkit::Color::Name::all()." unless @rgb == 3;
55             }
56 13         64 $value_obj = Graphics::Toolkit::Color::Values->new( [@rgb] );
57             } elsif (ref $color_def eq __PACKAGE__) { # enables color objects to be passed as arguments
58 33         74 $name = $color_def->name;
59 33         228 $value_obj = Graphics::Toolkit::Color::Values->new( $color_def->{'values'}->string );
60             } else { # define color by numbers in any format
61 108         309 my $value_obj = Graphics::Toolkit::Color::Values->new( $color_def );
62 108 100       4674 return unless ref $value_obj;
63 98         193 return _new_from_value_obj($value_obj);
64             }
65 46         196 bless {name => $name, values => $value_obj};
66             }
67             sub _new_from_value_obj {
68 121     121   1546 my ($value_obj) = @_;
69 121 100       238 return unless ref $value_obj eq 'Graphics::Toolkit::Color::Values';
70 118         251 bless {name => scalar Graphics::Toolkit::Color::Name::name_from_rgb( $value_obj->get() ), values => $value_obj};
71             }
72              
73             ## getter ##############################################################
74              
75 77     77 1 3896 sub name { $_[0]{'name'} }
76              
77 3 100   3 1 20 sub string { $_[0]{'name'} || $_[0]->{'values'}->string }
78 24     24 1 58 sub rgb { $_[0]->values( ) }
79 10     10 0 5199 sub red {($_[0]->values( in => 'rgb'))[0] }
80 10     10 0 25 sub green {($_[0]->values( in => 'rgb'))[1] }
81 10     10 0 32 sub blue {($_[0]->values( in => 'rgb'))[2] }
82 8     8 1 24 sub rgb_hex { $_[0]->values( in => 'rgb', as => 'hex') }
83 4     4 1 15 sub rgb_hash { $_[0]->values( in => 'rgb', as => 'hash') }
84 24     24 1 65 sub hsl { $_[0]->values( in => 'hsl') }
85 9     9 1 22 sub hue {($_[0]->values( in => 'hsl'))[0] }
86 33     33 1 1559 sub saturation {($_[0]->values( in => 'hsl'))[1] }
87 33     33 1 88 sub lightness {($_[0]->values( in => 'hsl'))[2] }
88 4     4 1 12 sub hsl_hash { $_[0]->values( in => 'hsl', as => 'hash') }
89              
90             sub values {
91 181     181 1 289 my ($self) = shift;
92 181 50       611 my %args = (not @_ % 2) ? @_ :
    100          
93             (@_ == 1) ? (in => $_[0])
94             : return carp "accept three optional, named arguments: in => 'HSL', as => 'css_string', range => 16";
95 181         765 $self->{'values'}->get( $args{'in'}, $args{'as'}, $args{'range'} );
96             }
97              
98             ## measurement methods ##############################################################
99              
100 0     0 0 0 sub distance_to { distance(@_) }
101             sub distance {
102 66     66 1 149 my ($self) = shift;
103 66 50       340 my %args = (not @_ % 2) ? @_ :
    100          
104             (@_ == 1) ? (to => $_[0])
105             : return carp "accept four optional, named arguments: to => 'color or color definition', in => 'RGB', metric => 'r', range => 16";
106 66         181 my ($c2, $space_name, $metric, $range) = ($args{'to'}, $args{'in'}, $args{'metric'}, $args{'range'});
107 66 50       146 return carp "missing argument: color object or scalar color definition" unless defined $c2;
108 66         124 $c2 = _new_from_scalar( $c2 );
109 66 50       164 return carp "second color for distance calculation (named argument 'to') is badly defined" unless ref $c2 eq __PACKAGE__;
110 66         171 $self->{'values'}->distance( $c2->{'values'}, $space_name, $metric );
111             }
112              
113             ## single color creation methods #######################################
114              
115             sub _get_arg_hash {
116 32 100   32   155 my $arg = (ref $_[0] eq 'HASH') ? $_[0]
    100          
117             : (not @_ % 2) ? {@_}
118             : {} ;
119 32 100       136 return (keys %$arg) ? $arg : carp "need arguments as hash (with or without braces)";
120             }
121              
122             sub set {
123 8     8 1 1549 my ($self, @args) = @_;
124 8         18 my $arg = _get_arg_hash( @args );
125 8 100       1233 return unless ref $arg;
126 6         20 _new_from_value_obj( $self->{'values'}->set( $arg ) );
127             }
128              
129             sub add {
130 6     6 1 160 my ($self, @args) = @_;
131 6         13 my $arg = _get_arg_hash( @args );
132 6 100       537 return unless ref $arg;
133 5         20 _new_from_value_obj( $self->{'values'}->add( $arg ) );
134             }
135              
136 0     0 0 0 sub blend_with { $_[0]->blend( with => $_[1], pos => $_[2], in => 'HSL') }
137             sub blend {
138 12     12 1 1739 my ($self, @args) = @_;
139 12         25 my $arg = _get_arg_hash( @args );
140 12 50       32 return unless ref $arg;
141 12         28 my $c2 = _new_from_scalar( $arg->{'with'} );
142 12 50       27 return croak "need a second color under the key 'with' ( with => { h=>1, s=>2, l=>3 })" unless ref $c2;
143 12   66     49 my $pos = $arg->{'pos'} // $arg->{'position'} // 0.5;
      50        
144 12   100     34 my $space_name = $arg->{'in'} // 'HSL';
145 12 50       23 return carp "color space $space_name is unknown" unless Graphics::Toolkit::Color::Space::Hub::is_space( $space_name );
146 12         36 _new_from_value_obj( $self->{'values'}->blend( $c2->{'values'}, $pos, $space_name ) );
147             }
148              
149             ## color set creation methods ##########################################
150              
151              
152             # for compatibility
153 4     4 0 16 sub gradient_to { hsl_gradient_to( @_ ) }
154 0     0 0 0 sub rgb_gradient_to { $_[0]->gradient( to => $_[1], steps => $_[2], dynamic => $_[3], in => 'RGB' ) }
155 4     4 0 13 sub hsl_gradient_to { $_[0]->gradient( to => $_[1], steps => $_[2], dynamic => $_[3], in => 'HSL' ) }
156             sub gradient {
157 6     6 1 35 my ($self, @args) = @_;
158 6         17 my $arg = _get_arg_hash( @args );
159 6 50       17 return unless ref $arg eq 'HASH';
160 6         14 my $c2 = _new_from_scalar( $arg->{'to'} );
161 6 50       14 return croak "need a second color under the key 'to' : ( to => ['HSL', 10, 20, 30])" unless ref $c2;
162 6   100     23 my $space_name = $arg->{'in'} // 'HSL';
163 6   50     14 my $steps = int(abs($arg->{'steps'} // 3));
164 6   100     13 my $power = $arg->{'dynamic'} // 0;
165 6 100       17 $power = ($power >= 0) ? $power + 1 : -(1/($power-1));
166 6 100       25 return $self if $steps == 1;
167 5         18 my $space = Graphics::Toolkit::Color::Space::Hub::get_space( $space_name );
168 5 50       14 return carp "color space $space_name is unknown" unless ref $space;
169 5         15 my @val1 = $self->{'values'}->get( $space_name, 'list', 'normal' );
170 5         15 my @val2 = $c2->{'values'}->get( $space_name, 'list', 'normal' );
171 5         12 my @delta_val = $space->delta (\@val1, \@val2 );
172 5         16 my @colors = ();
173 5         18 for my $nr (1 .. $steps-2){
174 7         35 my $pos = ($nr / ($steps-1)) ** $power;
175 7         18 my @rval = map {$val1[$_] + ($pos * $delta_val[$_])} 0 .. $space->dimensions - 1;
  21         46  
176 7         17 @rval = $space->denormalize ( \@rval );
177 7         22 push @colors, _new_from_scalar( [ $space_name, @rval ] );
178             }
179 5         47 return $self, @colors, $c2;
180             }
181              
182 3     3 0 713 sub complementary { complement(@_) }
183             sub complement { # steps => +, delta => {}
184 9     9 1 33 my ($self) = shift;
185 9   100     38 my ($count) = int ((shift // 1) + 0.5);
186 9   100     29 my ($saturation_change) = shift // 0;
187 9   100     25 my ($lightness_change) = shift // 0;
188 9         28 my @hsl2 = my @hsl_l = my @hsl_r = $self->values('HSL');
189 9         32 my $HSL = Graphics::Toolkit::Color::Space::Hub::get_space('HSL');
190 9         12 $hsl2[0] += 180;
191 9         14 $hsl2[1] += $saturation_change;
192 9         12 $hsl2[2] += $lightness_change;
193 9         49 my $c2 = _new_from_scalar( [ 'HSL', @hsl2 ] );
194 9 100       53 return $c2 if $count < 2;
195 5         20 my (@colors_r, @colors_l);
196 5         19 my @delta = (360 / $count, (($hsl2[1] - $hsl_r[1]) * 2 / $count), (($hsl2[2] - $hsl_r[2]) * 2 / $count) );
197 5         15 for (1 .. ($count - 1) / 2){
198 6         21 $hsl_r[$_] += $delta[$_] for 0..2;
199 6         7 $hsl_l[0] -= $delta[0];
200 6         14 $hsl_l[$_] = $hsl_r[$_] for 1,2;
201 6 100       12 $hsl_l[0] += 360 if $hsl_l[0] < 0;
202 6 50       10 $hsl_r[0] -= 360 if $hsl_l[0] >= 360;
203 6         16 push @colors_r, _new_from_scalar( [ 'HSL', @hsl_r ] );
204 6         29 unshift @colors_l, _new_from_scalar( [ 'HSL', @hsl_l ] );
205             }
206 5 100       17 push @colors_r, $c2 unless $count % 2;
207 5         47 $self, @colors_r, @colors_l;
208             }
209              
210             sub bowl {
211 0     0 0   my ($self, @args) = @_;
212 0           my $arg = _get_arg_hash( @args );
213 0 0         return unless ref $arg eq 'HASH';
214             # radius size in
215             # distance | count
216             }
217              
218             1;
219              
220             __END__