File Coverage

lib/Graphics/Toolkit/Color/Space/Hub.pm
Criterion Covered Total %
statement 126 126 100.0
branch 74 94 78.7
condition 44 68 64.7
subroutine 15 15 100.0
pod 7 13 53.8
total 266 316 84.1


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   265858 use v5.12;
  14         54  
6 14     14   101 use warnings;
  14         26  
  14         30691  
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 192 100   192 0 47752 sub is_space_name { (ref get_space($_[0])) ? 1 : 0 }
18 122     122 0 3972 sub all_space_names { sort keys %space_obj }
19 129     129 0 1213 sub default_space_name { $default_space_name }
20 898     898 1 1788 sub default_space { get_space( $default_space_name ) }
21 6451 100 100 6451 1 35548 sub get_space { (defined $_[0] and exists $space_obj{ uc $_[0] }) ? $space_obj{ uc $_[0] } : '' }
22             sub try_get_space {
23 2051   66 2051 1 5839 my $name = shift || $default_space_name;
24 2051         3923 my $space = get_space( $name );
25 2051 100 66     5888 return $name if ref $name eq 'Graphics::Toolkit::Color::Space' and is_space_name( $name->name );
26 1991 100       5348 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 492 my $space = shift;
32 253 50       851 return 'got no Graphics::Toolkit::Color::Space object' if ref $space ne 'Graphics::Toolkit::Color::Space';
33 253         674 my $name = $space->name;
34 253 50       580 return "space objct has no name" unless $name;
35 253 50       524 return "color space name $name is already taken" if ref get_space( $name );
36 253         711 my @converter_target = $space->converter_names;
37 253 50 66     733 return "can not add color space $name, it has no converter" unless @converter_target or $name eq $default_space_name;
38 253         484 for my $converter_target (@converter_target){
39 239         410 my $target_space = get_space( $converter_target );
40 239 50       573 return "space object $name does convert into $converter_target, which is no known color space" unless $target_space;
41 239 100       600 $space->alias_converter_name( $converter_target, $target_space->alias ) if $target_space->alias;
42             }
43 253         739 $space_obj{ uc $name } = $space;
44 253 100 66     521 $space_obj{ uc $space->alias } = $space if $space->alias and not ref get_space( $space->alias );
45 253         134684 return 1;
46             }
47             sub remove_space {
48 3     3 0 928 my $name = shift;
49 3 50 33     21 return "need name of color space as argument in order to remove the space" unless defined $name and $name;
50 3         8 my $space = get_space( $name );
51 3 100       20 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 446     446 1 295221 my ($values, $target_space_name, $want_result_normalized, $source_space_name, $source_values) = @_;
59 446         880 my $target_space = try_get_space( $target_space_name );
60 446         928 my $source_space = try_get_space( $source_space_name );
61 446   100     1133 $want_result_normalized //= 0;
62 446 100       885 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 443 100       1112 return $target_space unless ref $target_space;
65 442 50 50     1895 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 442 50 66     1292 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              
70             # none conversion cases
71 442 100 100     1312 $values = $source_values if ref $source_values and $source_space eq $target_space;
72 442 100 100     1131 if ($target_space->name eq default_space()->name or $source_space eq $target_space) {
73 329 100       1861 return ($want_result_normalized) ? $values : $target_space->round($target_space->denormalize( $values ));
74             }
75             # find conversion chain
76 113         304 my $current_space = $target_space;
77 113         297 my @convert_chain = ($target_space->name);
78 113         281 while ($current_space->name ne $default_space_name ){
79 128         425 my ($next_space_name, @next_options) = $current_space->converter_names;
80 128   66     471 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
81 128 100       335 unshift @convert_chain, $next_space_name if $next_space_name ne $default_space_name;
82 128         255 $current_space = get_space( $next_space_name );
83             }
84             # actual conversion
85 113         194 my $values_are_normal = 1;
86 113         236 my $space_name_before = default_space_name();
87 113         337 for my $space_name (@convert_chain){
88 128         234 my $current_space = get_space( $space_name );
89 128 100       394 if ($current_space eq $source_space){
90 1         3 $values = $source_values;
91 1         2 $values_are_normal = 1;
92             } else {
93 127         412 my @normal_in_out = $current_space->converter_normal_states( 'from', $space_name_before );
94 127 0 33     366 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
95 127 50 33     517 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
96 127         338 $values = $current_space->convert_from( $space_name_before, $values);
97 127         300 $values_are_normal = $normal_in_out[1];
98             }
99 128         339 $space_name_before = $current_space->name;
100             }
101 113 50 33     391 $values = $target_space->normalize( $values ) if not $values_are_normal and $want_result_normalized;
102 113 100 66     544 $values = $target_space->denormalize( $values ) if $values_are_normal and not $want_result_normalized;
103 113 100       448 return $target_space->clamp( $values, ($want_result_normalized ? 'normal' : undef));
104             }
105             sub deconvert { # normalizd value tuple --> RGB tuple
106 133     133 1 15163 my ($space_name, $values, $want_result_normalized) = @_;
107 133 100       439 return "need a space name to convert to as first argument" unless defined $space_name;
108 132         368 my $original_space = try_get_space( $space_name );
109 132 100       347 return $original_space unless ref $original_space;
110 130 100 66     768 return "need an ARRAY ref with 3 or 4 values as first argument in order to deconvert them"
      100        
111             unless ref $values eq 'ARRAY' and (@$values == 3 or @$values == 4);
112 129   100     331 $want_result_normalized //= 0;
113 129 100       410 if ($original_space->name eq $default_space_name) { # nothing to convert
114 2 100       12 return ($want_result_normalized) ? $values : $original_space->round( $original_space->denormalize( $values ));
115             }
116              
117 127         256 my $current_space = $original_space;
118 127         204 my $values_are_normal = 1;
119 127         321 while (uc $current_space->name ne $default_space_name){
120 142         491 my ($next_space_name, @next_options) = $current_space->converter_names;
121 142   66     608 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
122 142         471 my @normal_in_out = $current_space->converter_normal_states( 'to', $next_space_name );
123 142 0 33     406 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
124 142 50 33     612 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
125 142         505 $values = $current_space->convert_to( $next_space_name, $values);
126 142         302 $values_are_normal = $normal_in_out[1];
127 142         330 $current_space = get_space( $next_space_name );
128             }
129 127 100       554 return ($want_result_normalized) ? $values : $current_space->round( $current_space->denormalize( $values ));
130             }
131              
132             sub deformat { # formatted color def --> normalized values
133 112     112 1 309423 my ($color_def, $ranges, $suffix) = @_;
134 112 50       311 return 'got no color definition' unless defined $color_def;
135 112         238 my ($values, $original_space, $format_name);
136 112         283 for my $space_name (all_space_names()) {
137 2075         4296 my $color_space = get_space( $space_name );
138 2075         5999 ($values, $format_name) = $color_space->deformat( $color_def );
139 2075 100       5179 if (defined $format_name){
140 65         105 $original_space = $color_space;
141 65         160 last;
142             }
143             }
144 112 100       960 return 'could not deformat color definition: "$color_def"' unless ref $original_space;
145 65         266 return $values, $original_space->name, $format_name;
146             }
147             sub deformat_partial_hash { # convert partial hash into
148 37     37 1 19187 my ($value_hash, $space_name) = @_;
149 37 100       131 return unless ref $value_hash eq 'HASH';
150 36         83 my $space = try_get_space( $space_name );
151 36 50       83 return $space unless ref $space;
152 36 100 66     247 my @space_name_options = (defined $space_name and $space_name) ? ($space->name) : (@search_order);
153 36         85 for my $space_name (@space_name_options) {
154 223         358 my $color_space = get_space( $space_name );
155 223         484 my $values = $color_space->tuple_from_partial_hash( $value_hash );
156 223 100       491 next unless ref $values;
157 23 50       109 return wantarray ? ($values, $color_space->name) : $values;
158             }
159 13         65 return undef;
160             }
161              
162             sub distance { # @c1 @c2 -- ~space ~select @range --> +
163 28     28 0 139 my ($values_a, $values_b, $space_name, $select_axis, $range) = @_;
164 28         71 my $color_space = try_get_space( $space_name );
165 28 50       72 return $color_space unless ref $color_space;
166 28         71 $values_a = convert( $values_a, $space_name, 'normal' );
167 28         72 $values_b = convert( $values_b, $space_name, 'normal' );
168 28         110 my $delta = $color_space->delta( $values_a, $values_b );
169 28         89 $delta = $color_space->denormalize_delta( $delta, $range );
170 28 100       123 if (defined $select_axis){
171 17 100       61 $select_axis = [$select_axis] unless ref $select_axis;
172 21         70 my @selected_values = grep {defined $_}
173 17         39 map {$color_space->select_tuple_value_from_name($_, $delta) } @$select_axis;
  21         65  
174 17         46 $delta = \@selected_values;
175             }
176 28         44 my $d = 0;
177 28         137 $d += $_ * $_ for @$delta;
178 28         307 return sqrt $d;
179             }
180              
181             1;
182              
183             __END__