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   300245 use v5.12;
  14         55  
6 14     14   96 use warnings;
  14         40  
  14         32441  
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 40195 sub is_space_name { (ref get_space($_[0])) ? 1 : 0 }
18 121     121 0 3361 sub all_space_names { sort keys %space_obj }
19 129     129 0 1254 sub default_space_name { $default_space_name }
20 886     886 1 1620 sub default_space { get_space( $default_space_name ) }
21 6390 100 100 6390 1 31109 sub get_space { (defined $_[0] and exists $space_obj{ uc $_[0] }) ? $space_obj{ uc $_[0] } : '' }
22             sub try_get_space {
23 2026   66 2026 1 5322 my $name = shift || $default_space_name;
24 2026         6581 my $space = get_space( $name );
25 2026 100 66     4957 return $name if ref $name eq 'Graphics::Toolkit::Color::Space' and is_space_name( $name->name );
26 1966 100       4623 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 473 my $space = shift;
32 253 50       1923 return 'got no Graphics::Toolkit::Color::Space object' if ref $space ne 'Graphics::Toolkit::Color::Space';
33 253         704 my $name = $space->name;
34 253 50       595 return "space objct has no name" unless $name;
35 253 50       575 return "color space name $name is already taken" if ref get_space( $name );
36 253         676 my @converter_target = $space->converter_names;
37 253 50 66     708 return "can not add color space $name, it has no converter" unless @converter_target or $name eq $default_space_name;
38 253         555 for my $converter_target (@converter_target){
39 239         419 my $target_space = get_space( $converter_target );
40 239 50       2065 return "space object $name does convert into $converter_target, which is no known color space" unless $target_space;
41 239 100       541 $space->alias_converter_name( $converter_target, $target_space->alias ) if $target_space->alias;
42             }
43 253         737 $space_obj{ uc $name } = $space;
44 253 100 66     546 $space_obj{ uc $space->alias } = $space if $space->alias and not ref get_space( $space->alias );
45 253         140207 return 1;
46             }
47             sub remove_space {
48 3     3 0 973 my $name = shift;
49 3 50 33     20 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       6 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 440     440 1 286432 my ($values, $target_space_name, $want_result_normalized, $source_space_name, $source_values) = @_;
59 440         805 my $target_space = try_get_space( $target_space_name );
60 440         813 my $source_space = try_get_space( $source_space_name );
61 440   100     1038 $want_result_normalized //= 0;
62 440 100       777 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 437 100       1047 return $target_space unless ref $target_space;
65 436 50 50     1740 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 436 50 66     1032 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 436 100 100     1148 $values = $source_values if ref $source_values and $source_space eq $target_space;
72 436 100 100     918 if ($target_space->name eq default_space()->name or $source_space eq $target_space) {
73 323 100       1500 return ($want_result_normalized) ? $values : $target_space->round($target_space->denormalize( $values ));
74             }
75             # find conversion chain
76 113         330 my $current_space = $target_space;
77 113         317 my @convert_chain = ($target_space->name);
78 113         269 while ($current_space->name ne $default_space_name ){
79 128         393 my ($next_space_name, @next_options) = $current_space->converter_names;
80 128   66     456 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
81 128 100       300 unshift @convert_chain, $next_space_name if $next_space_name ne $default_space_name;
82 128         264 $current_space = get_space( $next_space_name );
83             }
84             # actual conversion
85 113         217 my $values_are_normal = 1;
86 113         262 my $space_name_before = default_space_name();
87 113         324 for my $space_name (@convert_chain){
88 128         268 my $current_space = get_space( $space_name );
89 128 100       419 if ($current_space eq $source_space){
90 1         3 $values = $source_values;
91 1         3 $values_are_normal = 1;
92             } else {
93 127         438 my @normal_in_out = $current_space->converter_normal_states( 'from', $space_name_before );
94 127 0 33     349 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
95 127 50 33     511 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
96 127         373 $values = $current_space->convert_from( $space_name_before, $values);
97 127         275 $values_are_normal = $normal_in_out[1];
98             }
99 128         345 $space_name_before = $current_space->name;
100             }
101 113 50 33     379 $values = $target_space->normalize( $values ) if not $values_are_normal and $want_result_normalized;
102 113 100 66     552 $values = $target_space->denormalize( $values ) if $values_are_normal and not $want_result_normalized;
103 113 100       445 return $target_space->clamp( $values, ($want_result_normalized ? 'normal' : undef));
104             }
105             sub deconvert { # normalizd value tuple --> RGB tuple
106 133     133 1 18335 my ($space_name, $values, $want_result_normalized) = @_;
107 133 100       341 return "need a space name to convert to as first argument" unless defined $space_name;
108 132         307 my $original_space = try_get_space( $space_name );
109 132 100       401 return $original_space unless ref $original_space;
110 130 100 66     657 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     364 $want_result_normalized //= 0;
113 129 100       321 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         241 my $current_space = $original_space;
118 127         223 my $values_are_normal = 1;
119 127         283 while (uc $current_space->name ne $default_space_name){
120 142         426 my ($next_space_name, @next_options) = $current_space->converter_names;
121 142   66     515 $next_space_name = shift @next_options while @next_options and $next_space_name ne $default_space_name;
122 142         402 my @normal_in_out = $current_space->converter_normal_states( 'to', $next_space_name );
123 142 0 33     390 $values = $current_space->normalize( $values ) if not $values_are_normal and $normal_in_out[0];
124 142 50 33     585 $values = $current_space->denormalize( $values ) if $values_are_normal and not $normal_in_out[0];
125 142         418 $values = $current_space->convert_to( $next_space_name, $values);
126 142         268 $values_are_normal = $normal_in_out[1];
127 142         297 $current_space = get_space( $next_space_name );
128             }
129 127 100       510 return ($want_result_normalized) ? $values : $current_space->round( $current_space->denormalize( $values ));
130             }
131              
132             sub deformat { # formatted color def --> normalized values
133 111     111 1 331299 my ($color_def, $ranges, $suffix) = @_;
134 111 50       399 return 'got no color definition' unless defined $color_def;
135 111         210 my ($values, $original_space, $format_name);
136 111         233 for my $space_name (all_space_names()) {
137 2051         4372 my $color_space = get_space( $space_name );
138 2051         5048 ($values, $format_name) = $color_space->deformat( $color_def );
139 2051 100       4806 if (defined $format_name){
140 65         127 $original_space = $color_space;
141 65         122 last;
142             }
143             }
144 111 100       861 return 'could not deformat color definition: "$color_def"' unless ref $original_space;
145 65         237 return $values, $original_space->name, $format_name;
146             }
147             sub deformat_partial_hash { # convert partial hash into
148 37     37 1 19792 my ($value_hash, $space_name) = @_;
149 37 100       106 return unless ref $value_hash eq 'HASH';
150 36         100 my $space = try_get_space( $space_name );
151 36 50       82 return $space unless ref $space;
152 36 100 66     216 my @space_name_options = (defined $space_name and $space_name) ? ($space->name) : (@search_order);
153 36         64 for my $space_name (@space_name_options) {
154 223         301 my $color_space = get_space( $space_name );
155 223         378 my $values = $color_space->tuple_from_partial_hash( $value_hash );
156 223 100       415 next unless ref $values;
157 23 50       69 return wantarray ? ($values, $color_space->name) : $values;
158             }
159 13         56 return undef;
160             }
161              
162             sub distance { # @c1 @c2 -- ~space ~select @range --> +
163 28     28 0 112 my ($values_a, $values_b, $space_name, $select_axis, $range) = @_;
164 28         73 my $color_space = try_get_space( $space_name );
165 28 50       97 return $color_space unless ref $color_space;
166 28         72 $values_a = convert( $values_a, $space_name, 'normal' );
167 28         72 $values_b = convert( $values_b, $space_name, 'normal' );
168 28         96 my $delta = $color_space->delta( $values_a, $values_b );
169 28         90 $delta = $color_space->denormalize_delta( $delta, $range );
170 28 100       87 if (defined $select_axis){
171 17 100       52 $select_axis = [$select_axis] unless ref $select_axis;
172 21         77 my @selected_values = grep {defined $_}
173 17         39 map {$color_space->select_tuple_value_from_name($_, $delta) } @$select_axis;
  21         65  
174 17         45 $delta = \@selected_values;
175             }
176 28         50 my $d = 0;
177 28         132 $d += $_ * $_ for @$delta;
178 28         243 return sqrt $d;
179             }
180              
181             1;
182              
183             __END__