File Coverage

lib/Graphics/Toolkit/Color/Space/Hub.pm
Criterion Covered Total %
statement 129 129 100.0
branch 74 94 78.7
condition 46 68 67.6
subroutine 15 15 100.0
pod 7 13 53.8
total 271 319 84.9


line stmt bran cond sub pod time code
1              
2             # store all clolor space objects, to convert check, convert and measure color values
3              
4             package Graphics::Toolkit::Color::Space::Hub;
5 14     14   187244 use v5.12;
  14         41  
6 14     14   64 use warnings;
  14         32  
  14         22035  
7              
8             #### internal space loading ############################################
9             our $default_space_name = 'RGB';
10             my @search_order = ($default_space_name,
11             qw/CMY CMYK HSL HSV HSB HWB NCol YIQ YUV/,
12             qw/CIEXYZ CIELAB CIELUV CIELCHab CIELCHuv OKLAB OKLCH HunterLAB/);
13             my %space_obj;
14             add_space( require "Graphics/Toolkit/Color/Space/Instance/$_.pm" ) for @search_order;
15              
16             #### space API #########################################################
17 198 100   198 0 25264 sub is_space_name { (ref get_space($_[0])) ? 1 : 0 }
18 128     128 0 3217 sub all_space_names { sort keys %space_obj }
19 131     131 0 1230 sub default_space_name { $default_space_name }
20 944     944 1 1294 sub default_space { get_space( $default_space_name ) }
21 6728 100 100 6728 1 27210 sub get_space { (defined $_[0] and exists $space_obj{ uc $_[0] }) ? $space_obj{ uc $_[0] } : '' }
22             sub try_get_space {
23 2135   66 2135 1 4253 my $name = shift || $default_space_name;
24 2135         2688 my $space = get_space( $name );
25 2135 100 66     4139 return $name if ref $name eq 'Graphics::Toolkit::Color::Space' and is_space_name( $name->name );
26 2075 100       3750 return (ref $space) ? $space
27             : "$name is an unknown color space, try one of: ".(join ', ', all_space_names());
28             }
29              
30             sub add_space {
31 253     253 0 325 my $space = shift;
32 253 50       492 return 'got no Graphics::Toolkit::Color::Space object' if ref $space ne 'Graphics::Toolkit::Color::Space';
33 253         477 my $name = $space->name;
34 253 50       427 return "space objct has no name" unless $name;
35 253 50       416 return "color space name $name is already taken" if ref get_space( $name );
36 253         507 my @converter_target = $space->converter_names;
37 253 50 66     508 return "can not add color space $name, it has no converter" unless @converter_target or $name eq $default_space_name;
38 253         351 for my $converter_target (@converter_target){
39 239         295 my $target_space = get_space( $converter_target );
40 239 50       416 return "space object $name does convert into $converter_target, which is no known color space" unless $target_space;
41 239 100       390 $space->alias_converter_name( $converter_target, $target_space->alias ) if $target_space->alias;
42             }
43 253         490 $space_obj{ uc $name } = $space;
44 253 100 66     362 $space_obj{ uc $space->alias } = $space if $space->alias and not ref get_space( $space->alias );
45 253         99344 return 1;
46             }
47             sub remove_space {
48 3     3 0 606 my $name = shift;
49 3 50 33     22 return "need name of color space as argument in order to remove the space" unless defined $name and $name;
50 3         33 my $space = get_space( $name );
51 3 100       19 return "can not remove unknown color space: $name" unless ref $space;
52 1 50       5 delete $space_obj{ uc $space->alias } if $space->alias;
53 1         5 delete $space_obj{ uc $space->name };
54             }
55              
56             #### value API #########################################################
57             sub convert { # normalized RGB tuple, ~space_name --> ?normalized tuple in wanted space
58 469     469 1 195176 my ($values, $target_space_name, $want_result_normalized, $source_space_name, $source_values) = @_;
59 469         629 my $target_space = try_get_space( $target_space_name );
60 469         672 my $source_space = try_get_space( $source_space_name );
61 469   100     877 $want_result_normalized //= 0;
62 469 100       703 return "need an ARRAY ref with 3 RGB values as first argument in order to convert them"
63             unless default_space()->is_value_tuple( $values );
64 466 100       872 return $target_space unless ref $target_space;
65 465 50 50     1461 return "arguments source_space_name and source_values have to be provided both or none."
66             if defined $source_space_name xor defined $source_values;
67 465 50 66     930 return "argument source_values has to be a tuple, if provided"
68             if $source_values and not $source_space->is_value_tuple( $source_values );
69 465         1004 $values = [@$values];
70              
71             # none conversion cases
72 465 100 100     963 $values = $source_values if ref $source_values and $source_space eq $target_space;
73 465 100 100     798 if ($target_space->name eq default_space()->name or $source_space eq $target_space) {
74 350 100       1242 return ($want_result_normalized) ? $values : $target_space->round($target_space->denormalize( $values ));
75             }
76             # find conversion chain
77 115         243 my $current_space = $target_space;
78 115         212 my @convert_chain = ($target_space->name);
79 115         247 while ($current_space->name ne $default_space_name ){
80 130         306 my ($next_space_name, @next_options) = $current_space->converter_names;
81 130   100     580 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
82 130 100       315 unshift @convert_chain, $next_space_name if $next_space_name ne $default_space_name;
83 130         244 $current_space = get_space( $next_space_name );
84             }
85             # actual conversion
86 115         168 my $values_are_normal = 1;
87 115         208 my $space_name_before = default_space_name();
88 115         247 for my $space_name (@convert_chain){
89 130         206 my $current_space = get_space( $space_name );
90 130 100       357 if ($current_space eq $source_space){
91 1         2 $values = $source_values;
92 1         2 $values_are_normal = 1;
93             } else {
94 129         378 my @normal_in_out = $current_space->converter_normal_states( 'from', $space_name_before );
95 129 0 33     264 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
96 129 50 33     432 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
97 129         282 $values = $current_space->convert_from( $space_name_before, $values);
98 129         240 $values_are_normal = $normal_in_out[1];
99             }
100 130         272 $space_name_before = $current_space->name;
101             }
102 115 50 33     247 $values = $target_space->normalize( $values ) if not $values_are_normal and $want_result_normalized;
103 115 100 66     370 $values = $target_space->denormalize( $values ) if $values_are_normal and not $want_result_normalized;
104 115 100       309 return $target_space->clamp( $values, ($want_result_normalized ? 'normal' : undef));
105             }
106             sub deconvert { # normalizd value tuple --> RGB tuple
107 136     136 1 9281 my ($space_name, $values, $want_result_normalized) = @_;
108 136 100       273 return "need a space name to convert to as first argument" unless defined $space_name;
109 135         289 my $original_space = try_get_space( $space_name );
110 135 100       271 return $original_space unless ref $original_space;
111 133 100 66     600 return "need an ARRAY ref with 3 or 4 values as first argument in order to deconvert them"
      100        
112             unless ref $values eq 'ARRAY' and (@$values == 3 or @$values == 4);
113 132   100     276 $want_result_normalized //= 0;
114 132 100       241 if ($original_space->name eq $default_space_name) { # nothing to convert
115 2 100       6 return ($want_result_normalized) ? $values : $original_space->round( $original_space->denormalize( $values ));
116             }
117              
118 130         188 my $current_space = $original_space;
119 130         185 my $values_are_normal = 1;
120 130         213 while (uc $current_space->name ne $default_space_name){
121 145         298 my ($next_space_name, @next_options) = $current_space->converter_names;
122 145   100     604 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
123 145         343 my @normal_in_out = $current_space->converter_normal_states( 'to', $next_space_name );
124 145 0 33     288 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
125 145 50 33     413 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
126 145         297 $values = $current_space->convert_to( $next_space_name, $values);
127 145         184 $values_are_normal = $normal_in_out[1];
128 145         229 $current_space = get_space( $next_space_name );
129             }
130 130 100       394 return ($want_result_normalized) ? $values : $current_space->round( $current_space->denormalize( $values ));
131             }
132              
133             sub deformat { # formatted color def --> normalized values
134 118     118 1 270599 my ($color_def, $ranges, $suffix) = @_;
135 118 50       324 return 'got no color definition' unless defined $color_def;
136 118         225 my ($values, $original_space, $format_name);
137 118         278 for my $space_name (all_space_names()) {
138 2204         3948 my $color_space = get_space( $space_name );
139 2204         5401 ($values, $format_name) = $color_space->deformat( $color_def );
140 2204 100       4631 if (defined $format_name){
141 71         118 $original_space = $color_space;
142 71         148 last;
143             }
144             }
145 118 100       951 return 'could not deformat color definition: "$color_def"' unless ref $original_space;
146 71         291 return $values, $original_space->name, $format_name;
147             }
148             sub deformat_partial_hash { # convert partial hash into
149 40     40 1 32007 my ($value_hash, $space_name) = @_;
150 40 100       141 return unless ref $value_hash eq 'HASH';
151 39         80 my $space = try_get_space( $space_name );
152 39 50       107 return $space unless ref $space;
153 39 100 66     217 my @space_name_options = (defined $space_name and $space_name) ? ($space->name) : (@search_order);
154 39         72 for my $space_name (@space_name_options) {
155 209         298 my $color_space = get_space( $space_name );
156 209         412 my $values = $color_space->tuple_from_partial_hash( $value_hash );
157 209 100       418 next unless ref $values;
158 25 50       79 return wantarray ? ($values, $color_space->name) : $values;
159             }
160 14         56 return undef;
161             }
162              
163             sub distance { # @c1 @c2 -- ~space ~select @range --> +
164 28     28 0 93 my ($values_a, $values_b, $space_name, $select_axis, $range) = @_;
165 28         63 my $color_space = try_get_space( $space_name );
166 28 50       68 return $color_space unless ref $color_space;
167 28         106 $values_a = convert( $values_a, $space_name, 'normal' );
168 28         63 $values_b = convert( $values_b, $space_name, 'normal' );
169 28         103 my $delta = $color_space->delta( $values_a, $values_b );
170 28         68 $delta = $color_space->denormalize_delta( $delta, $range );
171 28 100       63 if (defined $select_axis){
172 17 100       77 $select_axis = [$select_axis] unless ref $select_axis;
173 21         49 my @selected_values = grep {defined $_} map {$delta->[$_]}
  21         40  
174 17         38 grep {defined $_} map {$color_space->pos_from_axis_name($_)} @$select_axis;
  21         47  
  21         57  
175 17         43 $delta = \@selected_values;
176             }
177 28         43 my $d = 0;
178 28         90 $d += $_ * $_ for @$delta;
179 28         284 return sqrt $d;
180             }
181              
182             1;
183              
184             __END__