| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
# public user level API: docs, help and arg cleaning |
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Graphics::Toolkit::Color; |
|
5
|
|
|
|
|
|
|
our $VERSION = '2.11'; |
|
6
|
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
433718
|
use v5.12; |
|
|
4
|
|
|
|
|
13
|
|
|
8
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
251
|
|
|
9
|
4
|
|
|
4
|
|
24
|
use Exporter 'import'; |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
183
|
|
|
10
|
4
|
|
|
4
|
|
1247
|
use Graphics::Toolkit::Color::Space::Util qw/is_nr/; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
263
|
|
|
11
|
4
|
|
|
4
|
|
1944
|
use Graphics::Toolkit::Color::SetCalculator; |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
14101
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
my $default_space_name = Graphics::Toolkit::Color::Space::Hub::default_space_name(); |
|
14
|
|
|
|
|
|
|
our @EXPORT_OK = qw/color is_in_gamut/; |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
## constructor ######################################################### |
|
17
|
|
|
|
|
|
|
|
|
18
|
24
|
|
|
24
|
1
|
744206
|
sub color { Graphics::Toolkit::Color->new ( @_ ) } |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub new { |
|
21
|
97
|
|
|
97
|
1
|
4835
|
my ($pkg, @args) = @_; |
|
22
|
97
|
|
|
|
|
171
|
my $help = <
|
|
23
|
|
|
|
|
|
|
constructor new of Graphics::Toolkit::Color object needs either: |
|
24
|
|
|
|
|
|
|
1. a color name: 'red' or 'SVG:red' |
|
25
|
|
|
|
|
|
|
2. RGB hex string '#FF0000' or '#f00' |
|
26
|
|
|
|
|
|
|
3. RGB list or ARRAY ref: ( 255, 0, 0 ) or ( [255, 0, 0] ) |
|
27
|
|
|
|
|
|
|
4. named list or named ARRAY: ( 'HSL', 255, 0, 0 ) or ( ['HSL', 255, 0, 0 ]) |
|
28
|
|
|
|
|
|
|
which works even nested: 'HSL' => [ 255, 0, 0 ] or ['HSL' => [ 255, 0, 0 ]] |
|
29
|
|
|
|
|
|
|
5. string: new( 'HSL: 0, 100, 50' ) or new( 'ncol(r0, 0%, 0%)' ) |
|
30
|
|
|
|
|
|
|
6. HASH or HASH ref with values from RGB or any other space: |
|
31
|
|
|
|
|
|
|
(r => 255, g => 0, b => 0) or { hue => 0, saturation => 100, lightness => 50 } |
|
32
|
|
|
|
|
|
|
7. or use the key 'color' with any SCALAR color definition in order to add |
|
33
|
|
|
|
|
|
|
the option 'raw' and/or 'range' |
|
34
|
|
|
|
|
|
|
EOH |
|
35
|
97
|
|
|
|
|
205
|
my ($color_def, $range_def, $raw) = _compact_color_def_into_scalar( @args ); |
|
36
|
97
|
100
|
|
|
|
258
|
return $help unless defined $color_def; |
|
37
|
86
|
|
|
|
|
188
|
my $self = _new_from_scalar_def( $color_def, $range_def, $raw ); |
|
38
|
86
|
100
|
|
|
|
574
|
return (ref $self) ? $self : $help; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
sub _compact_color_def_into_scalar { |
|
41
|
109
|
|
|
109
|
|
183
|
my (@args) = @_; |
|
42
|
109
|
100
|
|
|
|
268
|
return unless @args; |
|
43
|
106
|
100
|
100
|
|
|
473
|
if (not(@args % 2) and ($args[0] eq 'range' or $args[0] eq 'color' or $args[0] eq 'raw')){ |
|
|
|
|
100
|
|
|
|
|
|
44
|
8
|
50
|
33
|
|
|
38
|
if (@args == 2 and $args[0] eq 'color'){ shift @args } # ->new (color => ...) is allowed |
|
|
0
|
50
|
|
|
|
0
|
|
|
45
|
|
|
|
|
|
|
elsif (@args > 2) { |
|
46
|
8
|
|
|
|
|
26
|
my %h = @args; |
|
47
|
8
|
100
|
|
|
|
50
|
return (delete( $h{'color'} ), delete( $h{'range'} ), delete( $h{'raw'} )) if @args == (scalar keys %h) * 2; # prevent double key use |
|
48
|
|
|
|
|
|
|
} |
|
49
|
0
|
|
|
|
|
0
|
else { return } |
|
50
|
|
|
|
|
|
|
} |
|
51
|
99
|
|
|
|
|
282
|
my $first_arg_is_color_space = Graphics::Toolkit::Color::Space::Hub::is_space_name( $args[0] ); |
|
52
|
99
|
100
|
100
|
|
|
354
|
@args = ([ $args[0], @{$args[1]} ]) if @args == 2 and $first_arg_is_color_space and ref $args[1] eq 'ARRAY'; |
|
|
3
|
|
66
|
|
|
9
|
|
|
53
|
99
|
100
|
100
|
|
|
420
|
@args = ([ @args ]) if @args == 3 or (@args > 3 and $first_arg_is_color_space); |
|
|
|
|
100
|
|
|
|
|
|
54
|
99
|
100
|
100
|
|
|
418
|
@args = ({ @args }) if @args == 6 or @args == 8; |
|
55
|
99
|
100
|
|
|
|
352
|
return (@args == 1) ? $args[0] : undef; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
sub _new_from_scalar_def { # color defs of method arguments |
|
58
|
145
|
|
|
145
|
|
261
|
my ($color_def, $range_def, $raw) = @_; |
|
59
|
145
|
100
|
|
|
|
367
|
return $color_def if ref $color_def eq __PACKAGE__; |
|
60
|
125
|
|
|
|
|
478
|
return _new_from_value_obj( Graphics::Toolkit::Color::Values->new_from_any_input( $color_def, $range_def, $raw ) ); |
|
61
|
|
|
|
|
|
|
} |
|
62
|
|
|
|
|
|
|
sub _new_from_value_obj { |
|
63
|
299
|
|
|
299
|
|
466
|
my ($value_obj) = @_; |
|
64
|
299
|
100
|
|
|
|
685
|
return $value_obj unless ref $value_obj eq 'Graphics::Toolkit::Color::Values'; |
|
65
|
252
|
|
|
|
|
1310
|
return bless {values => $value_obj}; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
######################################################################## |
|
69
|
|
|
|
|
|
|
sub _split_named_args { |
|
70
|
173
|
|
|
173
|
|
435
|
my ($raw_args, $only_parameter, $required_parameter, $optional_parameter, $parameter_alias) = @_; |
|
71
|
173
|
50
|
66
|
|
|
696
|
@$raw_args = %{$raw_args->[0]} if @$raw_args == 1 and ref $raw_args->[0] eq 'HASH' and not |
|
|
0
|
|
0
|
|
|
0
|
|
|
|
|
|
33
|
|
|
|
|
|
72
|
|
|
|
|
|
|
(defined $only_parameter and $only_parameter eq 'to' and ref _new_from_scalar_def( $raw_args ) ); |
|
73
|
|
|
|
|
|
|
|
|
74
|
173
|
100
|
100
|
|
|
557
|
if (@$raw_args == 1 and defined $only_parameter and $only_parameter){ |
|
|
|
|
66
|
|
|
|
|
|
75
|
34
|
50
|
|
|
|
85
|
return "The one default argument can not cover multiple, required parameter !" if @$required_parameter > 1; |
|
76
|
34
|
50
|
66
|
|
|
107
|
return "The default argument does not cover the required argument!" |
|
77
|
|
|
|
|
|
|
if @$required_parameter and $required_parameter->[0] ne $only_parameter; |
|
78
|
|
|
|
|
|
|
|
|
79
|
34
|
|
|
|
|
196
|
my %defaults = %$optional_parameter; |
|
80
|
34
|
|
|
|
|
98
|
delete $defaults{$only_parameter}; |
|
81
|
34
|
|
|
|
|
217
|
return {$only_parameter => $raw_args->[0], %defaults}; |
|
82
|
|
|
|
|
|
|
} |
|
83
|
139
|
|
|
|
|
192
|
my %clean_arg; |
|
84
|
139
|
100
|
|
|
|
313
|
if (@$raw_args % 2) { |
|
85
|
2
|
50
|
33
|
|
|
12
|
return (defined $only_parameter and $only_parameter) |
|
86
|
|
|
|
|
|
|
? "Got odd number of values, please use key value pairs as arguments or one default argument !\n" |
|
87
|
|
|
|
|
|
|
: "Got odd number of values, please use key value pairs as arguments !\n" |
|
88
|
|
|
|
|
|
|
} |
|
89
|
137
|
|
|
|
|
324
|
my %arg_hash = @$raw_args; |
|
90
|
137
|
|
|
|
|
278
|
for my $parameter_name (@$required_parameter){ |
|
91
|
71
|
100
|
100
|
|
|
322
|
if (ref $parameter_alias eq 'HASH' and exists $parameter_alias->{ $parameter_name } |
|
|
|
|
100
|
|
|
|
|
|
92
|
|
|
|
|
|
|
and exists $arg_hash{ $parameter_alias->{$parameter_name} }){ |
|
93
|
10
|
|
|
|
|
23
|
$arg_hash{ $parameter_name } = delete $arg_hash{ $parameter_alias->{$parameter_name} }; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
71
|
100
|
|
|
|
187
|
return "Argument '$parameter_name' is missing\n" unless exists $arg_hash{$parameter_name}; |
|
96
|
65
|
|
|
|
|
219
|
$clean_arg{ $parameter_name } = delete $arg_hash{ $parameter_name }; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
131
|
|
|
|
|
385
|
for my $parameter_name (keys %$optional_parameter){ |
|
99
|
485
|
50
|
100
|
|
|
781
|
if (ref $parameter_alias eq 'HASH' and exists $parameter_alias->{ $parameter_name } |
|
|
|
|
66
|
|
|
|
|
|
100
|
|
|
|
|
|
|
and exists $arg_hash{ $parameter_alias->{$parameter_name} }){ |
|
101
|
0
|
|
|
|
|
0
|
$arg_hash{ $parameter_name } = delete $arg_hash{ $parameter_alias->{$parameter_name} }; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
$clean_arg{ $parameter_name } = exists $arg_hash{$parameter_name} |
|
104
|
|
|
|
|
|
|
? delete $arg_hash{ $parameter_name } |
|
105
|
485
|
100
|
|
|
|
1141
|
: $optional_parameter->{ $parameter_name }; |
|
106
|
|
|
|
|
|
|
} |
|
107
|
131
|
100
|
|
|
|
370
|
return "Inserted unknown argument(s): ".(join ',', keys %arg_hash)."\n" if %arg_hash; |
|
108
|
124
|
|
|
|
|
285
|
return \%clean_arg; |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
### getter ############################################################# |
|
112
|
|
|
|
|
|
|
sub values { |
|
113
|
65
|
|
|
65
|
1
|
17247
|
my ($self, @args) = @_; |
|
114
|
65
|
|
|
|
|
568
|
my $arg = _split_named_args( \@args, 'in', [], |
|
115
|
|
|
|
|
|
|
{ in => $default_space_name, as => 'list', raw => 0, |
|
116
|
|
|
|
|
|
|
precision => undef, range => undef, suffix => undef } ); |
|
117
|
65
|
|
|
|
|
257
|
my $help = <
|
|
118
|
|
|
|
|
|
|
GTC method 'values' accepts either no arguments, one color space name or four optional, named args: |
|
119
|
|
|
|
|
|
|
values ( ... |
|
120
|
|
|
|
|
|
|
in => 'HSL', # color space name, defaults to "$default_space_name" |
|
121
|
|
|
|
|
|
|
as => 'css_string', # output format name, default is "list" |
|
122
|
|
|
|
|
|
|
range => 1, # value range (SCALAR or ARRAY), default set by space def |
|
123
|
|
|
|
|
|
|
precision => 3, # value precision (SCALAR or ARRAY), default set by space |
|
124
|
|
|
|
|
|
|
suffix => '%', # value suffix (SCALAR or ARRAY), default set by color space |
|
125
|
|
|
|
|
|
|
raw => 1, # no value clamping, rounding and scaling only by arg request |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
EOH |
|
128
|
65
|
100
|
|
|
|
159
|
return $arg.$help unless ref $arg; |
|
129
|
64
|
|
|
|
|
320
|
$self->{'values'}->formatted( @$arg{qw/in as suffix range precision raw/} ); |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
sub name { |
|
133
|
36
|
|
|
36
|
1
|
8063
|
my ($self, @args) = @_; |
|
134
|
36
|
100
|
|
|
|
234
|
return $self->{'values'}->name unless @args; |
|
135
|
2
|
|
|
|
|
12
|
my $arg = _split_named_args( \@args, 'from', [], {from => 'default', all => 0, full => 0, distance => 0}); |
|
136
|
2
|
|
|
|
|
5
|
my $help = <
|
|
137
|
|
|
|
|
|
|
GTC method 'name' accepts three optional, named arguments: |
|
138
|
|
|
|
|
|
|
name ( ... |
|
139
|
|
|
|
|
|
|
'CSS', # color naming scheme works as only positional argument |
|
140
|
|
|
|
|
|
|
from => 'CSS', # same scheme (defaults to internal: X + CSS + PantoneReport) |
|
141
|
|
|
|
|
|
|
from => ['SVG', 'X'], # more color naming schemes at once, without duplicates |
|
142
|
|
|
|
|
|
|
all => 1, # returns list of all names with the object's RGB values (defaults 0) |
|
143
|
|
|
|
|
|
|
full => 1, # adds color scheme name to the color name. 'SVG:red' (defaults 0) |
|
144
|
|
|
|
|
|
|
distance => 3, # color names from within distance of 3 (defaults 0) |
|
145
|
|
|
|
|
|
|
EOH |
|
146
|
2
|
|
|
|
|
9
|
return Graphics::Toolkit::Color::Name::from_values( $self->{'values'}->shaped, @$arg{qw/from all full distance/}); |
|
147
|
|
|
|
|
|
|
} |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub closest_name { |
|
150
|
11
|
|
|
11
|
1
|
9901
|
my ($self, @args) = @_; |
|
151
|
11
|
|
|
|
|
78
|
my $arg = _split_named_args( \@args, 'from', [], {from => 'default', all => 0, full => 0}); |
|
152
|
11
|
|
|
|
|
29
|
my $help = <
|
|
153
|
|
|
|
|
|
|
GTC method 'closest_name' accepts three optional, named arguments: |
|
154
|
|
|
|
|
|
|
closest_name ( ... |
|
155
|
|
|
|
|
|
|
'CSS', # color naming scheme works as only positional argument |
|
156
|
|
|
|
|
|
|
from => 'CSS', # same scheme (defaults to internal: X + CSS + PantoneReport) |
|
157
|
|
|
|
|
|
|
from => ['SVG', 'X'], # more color naming schemes at once, without duplicates |
|
158
|
|
|
|
|
|
|
all => 1, # returns list of all names with the object's RGB values (defaults 0) |
|
159
|
|
|
|
|
|
|
full => 1, # adds color scheme name to the color name. 'SVG:red' (defaults 0) |
|
160
|
|
|
|
|
|
|
EOH |
|
161
|
|
|
|
|
|
|
my ($name, $distance) = Graphics::Toolkit::Color::Name::closest_from_values( |
|
162
|
11
|
|
|
|
|
101
|
$self->{'values'}->shaped, @$arg{qw/from all full/}); |
|
163
|
11
|
100
|
|
|
|
73
|
return wantarray ? ($name, $distance) : $name; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub distance { |
|
167
|
9
|
|
|
9
|
1
|
1738
|
my ($self, @args) = @_; |
|
168
|
9
|
|
|
|
|
59
|
my $arg = _split_named_args( \@args, 'to', ['to'], {in => $default_space_name, select => undef, range => undef}, |
|
169
|
|
|
|
|
|
|
{select => 'only'}); |
|
170
|
9
|
|
|
|
|
32
|
my $help = <
|
|
171
|
|
|
|
|
|
|
GTC method 'distance' computes the Euclidean distance between two colors (points) |
|
172
|
|
|
|
|
|
|
in a color space. It accepts as arguments either a scalar color definition or |
|
173
|
|
|
|
|
|
|
four named arguments, only the first being required: |
|
174
|
|
|
|
|
|
|
distance ( ... |
|
175
|
|
|
|
|
|
|
to => 'green', # color object or color definition (required) |
|
176
|
|
|
|
|
|
|
in => 'HSL', # color space name, defaults to "$default_space_name" |
|
177
|
|
|
|
|
|
|
select => 'red', # axis name or names (ARRAY ref), default is none |
|
178
|
|
|
|
|
|
|
only => 'red', # argument alias name to select |
|
179
|
|
|
|
|
|
|
range => 2**16, # value range definition, defaults come from color space def |
|
180
|
|
|
|
|
|
|
EOH |
|
181
|
9
|
100
|
|
|
|
26
|
return $arg.$help unless ref $arg; |
|
182
|
7
|
|
|
|
|
23
|
my $target_color = _new_from_scalar_def( $arg->{'to'} ); |
|
183
|
7
|
50
|
|
|
|
13
|
return "target color definition: $arg->{to} is ill formed" unless ref $target_color; |
|
184
|
7
|
|
|
|
|
44
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $arg->{'in'} ); |
|
185
|
7
|
50
|
|
|
|
16
|
return "$color_space\n".$help unless ref $color_space; |
|
186
|
7
|
100
|
|
|
|
16
|
if (defined $arg->{'select'}){ |
|
187
|
3
|
100
|
|
|
|
12
|
if (not ref $arg->{'select'}){ |
|
|
|
50
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
return $arg->{'select'}." is not an axis name in color space: ".$color_space->name |
|
189
|
2
|
50
|
|
|
|
10
|
unless $color_space->is_axis_name( $arg->{'select'} ); |
|
190
|
|
|
|
|
|
|
} elsif (ref $arg->{'select'} eq 'ARRAY'){ |
|
191
|
1
|
|
|
|
|
2
|
for my $axis_name (@{$arg->{'select'}}) { |
|
|
1
|
|
|
|
|
2
|
|
|
192
|
2
|
50
|
|
|
|
6
|
return "$axis_name is not an axis name in color space: ".$color_space->name |
|
193
|
|
|
|
|
|
|
unless $color_space->is_axis_name( $axis_name ); |
|
194
|
|
|
|
|
|
|
} |
|
195
|
0
|
|
|
|
|
0
|
} else { return "The 'select' argument needs one axis name or an ARRAY with several axis names". |
|
196
|
|
|
|
|
|
|
" from the same color space!" } |
|
197
|
|
|
|
|
|
|
} |
|
198
|
7
|
|
|
|
|
24
|
my $range_def = $color_space->shape->try_check_range_definition( $arg->{'range'} ); |
|
199
|
7
|
50
|
|
|
|
14
|
return $range_def unless ref $range_def; |
|
200
|
|
|
|
|
|
|
Graphics::Toolkit::Color::Space::Hub::distance( |
|
201
|
7
|
|
|
|
|
47
|
$self->{'values'}->normalized, $target_color->{'values'}->normalized, $color_space->name ,$arg->{'select'}, $range_def ); |
|
202
|
|
|
|
|
|
|
} |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub is_in_gamut { |
|
206
|
15
|
|
|
15
|
1
|
408
|
my ($self, @args) = @_; |
|
207
|
15
|
|
|
|
|
26
|
my $help = <
|
|
208
|
|
|
|
|
|
|
GTC method 'is_in_gamut' returns a perlish pseudo boolean (0/1), |
|
209
|
|
|
|
|
|
|
telling you if a color is inside the gamut (range) of a color space or not. |
|
210
|
|
|
|
|
|
|
It accepts any color definition 'new' would. And like 'new' you have to give |
|
211
|
|
|
|
|
|
|
the color as value of the argument 'color', if you want to add information |
|
212
|
|
|
|
|
|
|
about the color. If no color definition is provided, the method will operate |
|
213
|
|
|
|
|
|
|
upon the current color, held by the object. |
|
214
|
|
|
|
|
|
|
Unlike 'new', the argument 'raw' defaults here to true (1). |
|
215
|
|
|
|
|
|
|
is_in_gamut ( ... |
|
216
|
|
|
|
|
|
|
color => [12,1000,5], # color definition |
|
217
|
|
|
|
|
|
|
range => 2**16, # observe these value ranges while reading the color definition |
|
218
|
|
|
|
|
|
|
in => 'HSL', # check if color is in gamut of that space |
|
219
|
|
|
|
|
|
|
# if no space name is provided, the color will be checked |
|
220
|
|
|
|
|
|
|
# against the boundaries of the space the color was defined in |
|
221
|
|
|
|
|
|
|
raw => 0, # clamp values to boundaries of the space, the color was defined in, |
|
222
|
|
|
|
|
|
|
# before converting it into the space you check against |
|
223
|
|
|
|
|
|
|
EOH |
|
224
|
15
|
100
|
|
|
|
54
|
unshift @args, $self unless ref $self eq __PACKAGE__; |
|
225
|
15
|
50
|
66
|
|
|
44
|
return $help if not ref $self and not @args; |
|
226
|
15
|
|
|
|
|
38
|
my ($color_def, $space_name, $range_def, $raw); |
|
227
|
15
|
100
|
100
|
|
|
96
|
if (not @args % 2 and @args and ($args[0] eq 'color' or $args[0] eq 'range' or $args[0] eq 'in' or $args[0] eq 'raw')){ |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
228
|
3
|
|
|
|
|
12
|
my %args = @args; |
|
229
|
3
|
|
|
|
|
8
|
$color_def = delete $args{'color'}; |
|
230
|
3
|
|
|
|
|
6
|
$range_def = delete $args{'range'}; |
|
231
|
3
|
|
|
|
|
6
|
$raw = delete $args{'raw'}; |
|
232
|
3
|
|
|
|
|
7
|
$space_name = delete $args{'in'}; |
|
233
|
3
|
50
|
66
|
|
|
13
|
return "Got no color definition!\n\n".$help unless defined $color_def or ref $self; |
|
234
|
|
|
|
|
|
|
} else { |
|
235
|
12
|
|
|
|
|
36
|
$color_def = _compact_color_def_into_scalar(@args); |
|
236
|
12
|
50
|
66
|
|
|
56
|
return "Got no valid color definition!\n\n".$help if @args and not defined $color_def; |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
my $values = (defined $color_def) |
|
239
|
|
|
|
|
|
|
? Graphics::Toolkit::Color::Values->new_from_any_input( $color_def, $range_def, $raw // 1 ) |
|
240
|
15
|
100
|
50
|
|
|
98
|
: $self->{'values'}; |
|
241
|
15
|
50
|
|
|
|
44
|
return $values unless ref $values; |
|
242
|
15
|
|
|
|
|
54
|
$values->is_in_gamut( $space_name ); |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
## single color creation methods ####################################### |
|
246
|
|
|
|
|
|
|
sub apply { |
|
247
|
3
|
|
|
3
|
1
|
818
|
my ($self, @args) = @_; |
|
248
|
3
|
|
|
|
|
14
|
my $arg = _split_named_args( \@args, undef, ['gamma'], {in => $default_space_name} ); |
|
249
|
3
|
|
|
|
|
7
|
my $help = <
|
|
250
|
|
|
|
|
|
|
GTC method 'apply' accepts one named argument with a numeric value: |
|
251
|
|
|
|
|
|
|
apply ( ... |
|
252
|
|
|
|
|
|
|
gamma => 2.2, # reverse is with 1 / 2.2 |
|
253
|
|
|
|
|
|
|
gamma => {r=> 1, g=> 2, b=> 1.2}, # custom gamma per axis |
|
254
|
|
|
|
|
|
|
in => 'OKLAB', # compute in oklab space |
|
255
|
|
|
|
|
|
|
EOH |
|
256
|
3
|
50
|
|
|
|
22
|
return $arg.$help unless ref $arg; |
|
257
|
3
|
|
|
|
|
10
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $arg->{'in'} ); |
|
258
|
3
|
50
|
|
|
|
5
|
return "$color_space\n".$help unless ref $color_space; |
|
259
|
3
|
|
|
|
|
29
|
_new_from_value_obj( Graphics::Toolkit::Color::Calculator::apply_gamma( $self->{'values'}, $arg->{'gamma'}, $color_space ) ); |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
sub set_value { |
|
263
|
10
|
|
|
10
|
1
|
1369
|
my ($self, @args) = @_; |
|
264
|
10
|
50
|
33
|
|
|
26
|
@args = %{$args[0]} if @args == 1 and ref $args[0] eq 'HASH'; |
|
|
0
|
|
|
|
|
0
|
|
|
265
|
10
|
|
|
|
|
15
|
my $help = <
|
|
266
|
|
|
|
|
|
|
GTC method 'set_value' needs a value HASH (not a ref) whose keys are axis names or |
|
267
|
|
|
|
|
|
|
short names from one color space. If the chosen axis name(s) is/are ambiguous, |
|
268
|
|
|
|
|
|
|
you might add the "in" argument: |
|
269
|
|
|
|
|
|
|
set_value( green => 20 ) or set( g => 20 ) or |
|
270
|
|
|
|
|
|
|
set_value( hue => 240, in => 'HWB' ) |
|
271
|
|
|
|
|
|
|
EOH |
|
272
|
10
|
100
|
66
|
|
|
56
|
return $help if @args % 2 or not @args or @args > 10; |
|
|
|
|
66
|
|
|
|
|
|
273
|
9
|
|
|
|
|
23
|
my $partial_color = { @args }; |
|
274
|
9
|
|
|
|
|
12
|
my $space_name = delete $partial_color->{'in'}; |
|
275
|
9
|
|
|
|
|
25
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $space_name ); |
|
276
|
9
|
50
|
|
|
|
39
|
return "$color_space\n".$help unless ref $color_space; |
|
277
|
9
|
|
|
|
|
25
|
_new_from_value_obj( Graphics::Toolkit::Color::Calculator::set_value( $self->{'values'}, $partial_color, $space_name ) ); |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub add_value { |
|
281
|
10
|
|
|
10
|
1
|
887
|
my ($self, @args) = @_; |
|
282
|
10
|
50
|
33
|
|
|
31
|
@args = %{$args[0]} if @args == 1 and ref $args[0] eq 'HASH'; |
|
|
0
|
|
|
|
|
0
|
|
|
283
|
10
|
|
|
|
|
19
|
my $help = <
|
|
284
|
|
|
|
|
|
|
GTC method 'add_value' needs a value HASH (not a ref) whose keys are axis names or |
|
285
|
|
|
|
|
|
|
short names from one color space. If the chosen axis name(s) is/are ambiguous, |
|
286
|
|
|
|
|
|
|
you might add the "in" argument: |
|
287
|
|
|
|
|
|
|
add_value( blue => -10 ) or set( b => -10 ) |
|
288
|
|
|
|
|
|
|
add_value( hue => 100 , in => 'HWB' ) |
|
289
|
|
|
|
|
|
|
EOH |
|
290
|
10
|
100
|
66
|
|
|
55
|
return $help if @args % 2 or not @args or @args > 10; |
|
|
|
|
66
|
|
|
|
|
|
291
|
9
|
|
|
|
|
23
|
my $partial_color = { @args }; |
|
292
|
9
|
|
|
|
|
16
|
my $space_name = delete $partial_color->{'in'}; |
|
293
|
9
|
|
|
|
|
22
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $space_name ); |
|
294
|
9
|
50
|
|
|
|
17
|
return "$color_space\n".$help unless ref $color_space; |
|
295
|
9
|
|
|
|
|
29
|
_new_from_value_obj( Graphics::Toolkit::Color::Calculator::add_value( $self->{'values'}, $partial_color, $space_name ) ); |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub mix { |
|
299
|
24
|
|
|
24
|
1
|
4545
|
my ($self, @args) = @_; |
|
300
|
24
|
|
|
|
|
133
|
my $arg = _split_named_args( \@args, 'to', ['to'], {in => $default_space_name, amount => -1}); |
|
301
|
24
|
|
|
|
|
76
|
my $help = <
|
|
302
|
|
|
|
|
|
|
GTC method 'mix' accepts three named arguments, only the first being required: |
|
303
|
|
|
|
|
|
|
mix ( ... |
|
304
|
|
|
|
|
|
|
to => ['HSL', 240, 100, 50], # scalar color definition or ARRAY ref thereof |
|
305
|
|
|
|
|
|
|
amount => 20, # percentage value or ARRAY ref thereof, default is 50 |
|
306
|
|
|
|
|
|
|
in => 'HSL', # color space name, defaults to "$default_space_name" |
|
307
|
|
|
|
|
|
|
Please note that ARRAY for amount makes only sense if to got also an ARRAY. |
|
308
|
|
|
|
|
|
|
Both ARRAY have to have the same length. 'amount' refers to the color(s) picked with 'to'. |
|
309
|
|
|
|
|
|
|
It is possible to give to an ARRAY and amount a SCALAR. |
|
310
|
|
|
|
|
|
|
EOH |
|
311
|
24
|
100
|
|
|
|
91
|
return $arg.$help unless ref $arg; |
|
312
|
22
|
|
|
|
|
71
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( delete $arg->{'in'} ); |
|
313
|
22
|
100
|
|
|
|
53
|
return "$color_space\n".$help unless ref $color_space; |
|
314
|
21
|
|
|
|
|
48
|
my $recipe = _new_from_scalar_def( $arg->{'to'} ); |
|
315
|
21
|
100
|
|
|
|
40
|
if (ref $recipe){ |
|
316
|
10
|
100
|
|
|
|
33
|
return "argument 'amount' has to be a scalar value if only one color is mixed !\n".$help if ref $arg->{'amount'}; |
|
317
|
9
|
100
|
|
|
|
25
|
$arg->{'amount'} = 50 if $arg->{'amount'} < 0; |
|
318
|
9
|
100
|
|
|
|
21
|
$arg->{'amount'} = 100 if $arg->{'amount'} > 100; |
|
319
|
9
|
|
|
|
|
35
|
$recipe = [{color => $recipe->{'values'}, percent => $arg->{'amount'}}]; |
|
320
|
9
|
100
|
|
|
|
64
|
push @$recipe, {color => $self->{'values'}, percent => 100 - $arg->{'amount'} } if $arg->{'amount'} < 100; |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
} else { |
|
323
|
11
|
100
|
|
|
|
31
|
if (ref $arg->{'to'} ne 'ARRAY'){ |
|
324
|
2
|
|
|
|
|
22
|
return "target color definition (argument 'to'): '$arg->{to}' is ill formed. It has to be one color definition or an ARRAY of them."; |
|
325
|
|
|
|
|
|
|
} else { |
|
326
|
|
|
|
|
|
|
return "Argument 'amount' has to be an ARRAY of same length as argument 'to' (color definitions)!\n".$help |
|
327
|
9
|
100
|
66
|
|
|
48
|
if ref $arg->{'to'} eq 'ARRAY' and ref $arg->{'amount'} eq 'ARRAY' and @{$arg->{'amount'}} != @{$arg->{'to'}}; |
|
|
4
|
|
100
|
|
|
9
|
|
|
|
4
|
|
|
|
|
18
|
|
|
328
|
8
|
|
|
|
|
13
|
my $color_count = 1 + @{$arg->{'to'}}; |
|
|
8
|
|
|
|
|
16
|
|
|
329
|
8
|
100
|
|
|
|
16
|
unless (ref $arg->{'amount'}){ |
|
330
|
|
|
|
|
|
|
$arg->{'amount'} = ($arg->{'amount'} < 0) |
|
331
|
|
|
|
|
|
|
? [(100/$color_count) x $color_count] |
|
332
|
5
|
50
|
|
|
|
28
|
: [($arg->{'amount'}) x $color_count]; |
|
333
|
|
|
|
|
|
|
} |
|
334
|
8
|
|
|
|
|
14
|
$recipe = []; |
|
335
|
8
|
|
|
|
|
13
|
my $amount_sum = 0; |
|
336
|
8
|
|
|
|
|
13
|
for my $color_nr (0 .. $#{$arg->{'to'}}){ |
|
|
8
|
|
|
|
|
24
|
|
|
337
|
14
|
|
|
|
|
25
|
my $color_def = $arg->{'to'}[$color_nr]; |
|
338
|
14
|
|
|
|
|
18
|
my $color = _new_from_scalar_def( $color_def ); |
|
339
|
14
|
50
|
|
|
|
32
|
return "target color nr. $color_nr definition: '$color_def' is ill formed" unless ref $color; |
|
340
|
14
|
|
|
|
|
54
|
push @$recipe, { color => $color->{'values'}, percent => $arg->{'amount'}[$color_nr] }; |
|
341
|
14
|
|
|
|
|
37
|
$amount_sum += $arg->{'amount'}[$color_nr]; |
|
342
|
|
|
|
|
|
|
} |
|
343
|
8
|
100
|
|
|
|
31
|
push @$recipe, {color => $self->{'values'}, percent => 100 - $amount_sum } if $amount_sum < 100; |
|
344
|
8
|
100
|
|
|
|
20
|
if ($amount_sum > 100){ |
|
345
|
1
|
|
|
|
|
7
|
$_->{'percent'} = ($_->{'percent'} / $amount_sum * 100) for @$recipe; |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
} |
|
349
|
17
|
|
|
|
|
59
|
_new_from_value_obj( Graphics::Toolkit::Color::Calculator::mix( $self->{'values'}, $recipe, $color_space ) ); |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
sub invert { |
|
353
|
15
|
|
|
15
|
1
|
567
|
my ($self, @args) = @_; |
|
354
|
15
|
|
|
|
|
132
|
my $arg = _split_named_args( \@args, 'in', [], {in => $default_space_name, only => undef}); |
|
355
|
15
|
|
|
|
|
62
|
my $help = <
|
|
356
|
|
|
|
|
|
|
GTC method 'invert' accepts one optional argument, which can be positional or named: |
|
357
|
|
|
|
|
|
|
invert ( ... |
|
358
|
|
|
|
|
|
|
in => 'HSL', # color space name, defaults to "$default_space_name" |
|
359
|
|
|
|
|
|
|
only => 'Saturation', # inverts only second value of the tuple |
|
360
|
|
|
|
|
|
|
only => [qw/s l/], # axis name or names have to match selected space |
|
361
|
|
|
|
|
|
|
EOH |
|
362
|
15
|
100
|
66
|
|
|
98
|
return $arg.$help unless ref $arg and (not ref $arg->{'only'} or ref $arg->{'only'} eq 'ARRAY'); |
|
|
|
|
100
|
|
|
|
|
|
363
|
14
|
|
|
|
|
55
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $arg->{'in'} ); |
|
364
|
14
|
100
|
|
|
|
50
|
return "$color_space\n".$help unless ref $color_space; |
|
365
|
13
|
|
|
|
|
60
|
_new_from_value_obj( Graphics::Toolkit::Color::Calculator::invert( $self->{'values'}, $arg->{'only'}, $color_space ) ); |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
## color set creation methods ########################################## |
|
369
|
|
|
|
|
|
|
sub complement { |
|
370
|
12
|
|
|
12
|
1
|
544
|
my ($self, @args) = @_; |
|
371
|
12
|
|
|
|
|
78
|
my $arg = _split_named_args( \@args, 'steps', [], {steps => 1, tilt => 0, target => {}}); |
|
372
|
12
|
|
|
|
|
42
|
my $help = <
|
|
373
|
|
|
|
|
|
|
GTC method 'complement' is computed in HSL and has two named, optional arguments: |
|
374
|
|
|
|
|
|
|
complement ( ... |
|
375
|
|
|
|
|
|
|
steps => 20, # count of produced colors, default is 1 |
|
376
|
|
|
|
|
|
|
tilt => 10, # default is 0 |
|
377
|
|
|
|
|
|
|
target => {h => 10, s => 20, l => 3}, # sub-keys are independent, default to 0 |
|
378
|
|
|
|
|
|
|
EOH |
|
379
|
12
|
100
|
|
|
|
37
|
return $arg.$help unless ref $arg; |
|
380
|
11
|
100
|
|
|
|
49
|
return "Optional argument 'steps' has to be a number !\n".$help unless is_nr($arg->{'steps'}); |
|
381
|
9
|
50
|
|
|
|
30
|
return "Optional argument 'steps' is zero, no complement colors will be computed !\n".$help unless $arg->{'steps'}; |
|
382
|
9
|
100
|
|
|
|
22
|
return "Optional argument 'tilt' has to be a number !\n".$help unless is_nr($arg->{'tilt'}); |
|
383
|
8
|
100
|
|
|
|
41
|
return "Optional argument 'target' has to be a HASH ref !\n".$help if ref $arg->{'target'} ne 'HASH'; |
|
384
|
7
|
|
|
|
|
15
|
my ($target_values, $space_name); |
|
385
|
7
|
100
|
|
|
|
10
|
if (keys %{$arg->{'target'}}){ |
|
|
7
|
|
|
|
|
36
|
|
|
386
|
2
|
|
|
|
|
12
|
($target_values, $space_name) = Graphics::Toolkit::Color::Space::Hub::deformat_partial_hash( $arg->{'target'}, 'HSL' ); |
|
387
|
2
|
100
|
|
|
|
18
|
return "Optional argument 'target' got HASH keys that do not fit HSL space (use 'h','s','l') !\n".$help |
|
388
|
|
|
|
|
|
|
unless ref $target_values; |
|
389
|
5
|
|
|
|
|
11
|
} else { $target_values = [] } |
|
390
|
13
|
|
|
|
|
70
|
map {_new_from_value_obj( $_ )} |
|
391
|
6
|
|
|
|
|
38
|
Graphics::Toolkit::Color::SetCalculator::complement( $self->{'values'}, @$arg{qw/steps tilt/}, $target_values ); |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub gradient { |
|
395
|
14
|
|
|
14
|
1
|
537
|
my ($self, @args) = @_; |
|
396
|
14
|
|
|
|
|
184
|
my $arg = _split_named_args( \@args, 'to', ['to'], {steps => 10, tilt => 0, in => $default_space_name}); |
|
397
|
14
|
|
|
|
|
63
|
my $help = <
|
|
398
|
|
|
|
|
|
|
GTC method 'gradient' accepts four named arguments, only the first is required: |
|
399
|
|
|
|
|
|
|
gradient ( ... |
|
400
|
|
|
|
|
|
|
to => 'blue', # scalar color definition or ARRAY ref thereof |
|
401
|
|
|
|
|
|
|
steps => 20, # count of produced colors, defaults to 10 |
|
402
|
|
|
|
|
|
|
tilt => 1, # dynamics of color change, defaults to 0 |
|
403
|
|
|
|
|
|
|
in => 'HSL', # color space name, defaults to "$default_space_name" |
|
404
|
|
|
|
|
|
|
EOH |
|
405
|
14
|
100
|
|
|
|
65
|
return $arg.$help unless ref $arg; |
|
406
|
12
|
|
|
|
|
37
|
my @colors = ($self->{'values'}); |
|
407
|
12
|
|
|
|
|
45
|
my $target_color = _new_from_scalar_def( $arg->{'to'} ); |
|
408
|
12
|
100
|
|
|
|
38
|
if (ref $target_color) { |
|
409
|
9
|
|
|
|
|
31
|
push @colors, $target_color->{'values'} } |
|
410
|
|
|
|
|
|
|
else { |
|
411
|
3
|
100
|
66
|
|
|
51
|
return "Argument 'to' contains malformed color definition!\n".$help if ref $arg->{'to'} ne 'ARRAY' or not @{$arg->{'to'}}; |
|
|
2
|
|
|
|
|
11
|
|
|
412
|
2
|
|
|
|
|
5
|
for my $color_def (@{$arg->{'to'}}){ |
|
|
2
|
|
|
|
|
7
|
|
|
413
|
5
|
|
|
|
|
14
|
my $target_color = _new_from_scalar_def( $color_def ); |
|
414
|
5
|
100
|
|
|
|
41
|
return "Argument 'to' contains malformed color definition: $color_def !\n".$help unless ref $target_color; |
|
415
|
4
|
|
|
|
|
21
|
push @colors, $target_color->{'values'}; |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
return "Argument 'steps' has to be a number greater zero !\n".$help |
|
419
|
10
|
100
|
66
|
|
|
44
|
unless is_nr($arg->{'steps'}) and $arg->{'steps'} > 0; |
|
420
|
9
|
|
|
|
|
33
|
$arg->{'steps'} = int $arg->{'steps'}; |
|
421
|
9
|
100
|
|
|
|
25
|
return "Argument 'tilt' has to be a number !\n".$help unless is_nr($arg->{'tilt'}); |
|
422
|
8
|
|
|
|
|
37
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $arg->{'in'} ); |
|
423
|
8
|
100
|
|
|
|
41
|
return "$color_space\n".$help unless ref $color_space; |
|
424
|
53
|
|
|
|
|
103
|
map {_new_from_value_obj( $_ )} |
|
425
|
7
|
|
|
|
|
62
|
Graphics::Toolkit::Color::SetCalculator::gradient( \@colors, @$arg{qw/steps tilt/}, $color_space); |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
sub cluster { |
|
429
|
18
|
|
|
18
|
1
|
1102
|
my ($self, @args) = @_; |
|
430
|
18
|
|
|
|
|
108
|
my $arg = _split_named_args( \@args, undef, ['radius', 'minimal_distance'], {in => $default_space_name}, |
|
431
|
|
|
|
|
|
|
{radius => 'r', minimal_distance => 'min_d'} ); |
|
432
|
18
|
|
|
|
|
67
|
my $help = <
|
|
433
|
|
|
|
|
|
|
GTC method 'cluster' accepts three named arguments, the first two being required: |
|
434
|
|
|
|
|
|
|
cluster ( ... |
|
435
|
|
|
|
|
|
|
radius => 3 # ball shaped cluster with cuboctahedral packing or |
|
436
|
|
|
|
|
|
|
r => [10, 5, 3] # cuboid shaped cluster with cubical packing |
|
437
|
|
|
|
|
|
|
minimal_distance => 0.5 # minimal distance between colors in cluster |
|
438
|
|
|
|
|
|
|
min_d => 0.5 # short alias for minimal distance |
|
439
|
|
|
|
|
|
|
in => 'HSL' # color space name, defaults to "$default_space_name" |
|
440
|
|
|
|
|
|
|
EOH |
|
441
|
18
|
100
|
|
|
|
81
|
return $arg.$help unless ref $arg; |
|
442
|
12
|
|
|
|
|
34
|
my $color_space = Graphics::Toolkit::Color::Space::Hub::try_get_space( $arg->{'in'} ); |
|
443
|
12
|
100
|
|
|
|
36
|
return "$color_space\n".$help unless ref $color_space; |
|
444
|
|
|
|
|
|
|
return "Argument 'radius' has to be a number or an ARRAY of numbers".$help |
|
445
|
11
|
100
|
100
|
|
|
32
|
unless is_nr($arg->{'radius'}) or $color_space->is_number_tuple( $arg->{'radius'} ); |
|
446
|
|
|
|
|
|
|
return "Argument 'minimal_distance' (or 'min_d') has to be a number greater zero !\n".$help |
|
447
|
8
|
100
|
100
|
|
|
22
|
unless is_nr($arg->{'minimal_distance'}) and $arg->{'minimal_distance'} > 0; |
|
448
|
|
|
|
|
|
|
return "Ball shaped cluster works only in spaces with three dimensions !\n".$help |
|
449
|
6
|
100
|
100
|
|
|
17
|
if $color_space->axis_count > 3 and not ref $arg->{'radius'}; |
|
450
|
57
|
|
|
|
|
58
|
map {_new_from_value_obj( $_ )} |
|
451
|
5
|
|
|
|
|
28
|
Graphics::Toolkit::Color::SetCalculator::cluster( $self->{'values'}, @$arg{qw/radius minimal_distance/}, $color_space); |
|
452
|
|
|
|
|
|
|
} |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
1; |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
__END__ |