File Coverage

lib/Graphics/Toolkit/Color/SetCalculator.pm
Criterion Covered Total %
statement 113 113 100.0
branch 21 24 87.5
condition 3 3 100.0
subroutine 6 6 100.0
pod 0 3 0.0
total 143 149 95.9


line stmt bran cond sub pod time code
1              
2             # color value operation generating color sets
3              
4             package Graphics::Toolkit::Color::SetCalculator;
5 5     5   574845 use v5.12;
  5         21  
6 5     5   31 use warnings;
  5         8  
  5         353  
7 5     5   2683 use Graphics::Toolkit::Color::Values;
  5         17  
  5         8877  
8              
9             my $HSL = Graphics::Toolkit::Color::Space::Hub::get_space('HSL');
10             my $half_hue_max = $HSL->shape->axis_value_max(0) / 2;
11             ########################################################################
12             sub complement { # :base_color +steps +tilt %target_delta --> @:values
13 13     13 0 3437 my ($start_color, $steps, $tilt, $target_delta) = @_;
14 13         85 my $start_values = $start_color->shaped( $HSL->name );
15 13         29 my $target_values = [@$start_values];
16 13         30 $target_values->[0] += $half_hue_max;
17 13         38 for my $axis_index (0 .. 2) {
18 39 100       99 $target_delta->[$axis_index] = 0 unless defined $target_delta->[$axis_index];
19 39         69 $target_values->[$axis_index] += $target_delta->[$axis_index];
20             }
21 13         33 $target_values = $HSL->clamp( $target_values ); # bring back out of bound linear axis values
22 13         32 $target_delta->[1] = $target_values->[1] - $start_values->[1];
23 13         31 $target_delta->[2] = $target_values->[2] - $start_values->[2];
24 13         25 my $result_count = int abs $steps;
25 13         29 my $scaling_exponent = abs($tilt) + 1;
26 13         47 my @hue_percent = map {($_ * 2 / $result_count) ** $scaling_exponent} 1 .. ($result_count - 1) / 2;
  9         55  
27 13 100       48 @hue_percent = map {1 - $_} reverse @hue_percent if $tilt > 0;
  4         19  
28 13         26 my $hue_delta = $half_hue_max + $target_delta->[0]; # real value size of half complement circle
29 13         35 my @result = ();
30             push( @result, Graphics::Toolkit::Color::Values->new_from_tuple(
31             [$start_values->[0] + ($hue_delta * $_),
32             $start_values->[1] + ($target_delta->[1] * $_),
33 13         76 $start_values->[2] + ($target_delta->[2] * $_)], $HSL->name)) for @hue_percent;
34 13 100 100     71 push @result, Graphics::Toolkit::Color::Values->new_from_tuple( $target_values, $HSL->name)
35             if $result_count == 1 or not $result_count % 2;
36 13         27 $hue_delta = $half_hue_max - $target_delta->[0];
37 13         29 @hue_percent = map {1 - $_} reverse @hue_percent;
  9         30  
38             push( @result, Graphics::Toolkit::Color::Values->new_from_tuple(
39             [$target_values->[0] + ($hue_delta * $_),
40             $target_values->[1] - ($target_delta->[1] * $_),
41 13         73 $target_values->[2] - ($target_delta->[2] * $_)], $HSL->name)) for @hue_percent;
42 13 100       42 push @result, $start_color if $result_count > 1;
43 13         120 return @result;
44             }
45              
46             ########################################################################
47             sub gradient { # @:colors, +steps, +tilt, :space --> @:values
48 15     15 0 14475 my ($colors, $steps, $tilt, $color_space) = @_;
49 15         39 my $scaling_exponent = abs($tilt) + 1; # tilt = exponential scaling
50 15         33 my $segment_count = @$colors - 1;
51 15         52 my @percent_in_gradient = map {(($_-1) / ($steps-1)) ** $scaling_exponent} 2 .. $steps - 1;
  55         157  
52 15 100       53 @percent_in_gradient = map {1 - $_} reverse @percent_in_gradient if $tilt < 0;
  1         5  
53 15         40 my @result = ($colors->[0]);
54 15         50 for my $step_nr (2 .. $steps - 1){
55 55         116 my $percent_in_gradient = $percent_in_gradient[$step_nr-2];
56 55         99 my $current_segment_nr = int ($percent_in_gradient * $segment_count);
57 55         181 my $percent_in_segment = 100 * $segment_count * ($percent_in_gradient - ($current_segment_nr / $segment_count));
58 55         314 push @result, $colors->[$current_segment_nr]->mix (
59             [{color => $colors->[$current_segment_nr+1], percent => $percent_in_segment}], $color_space );
60             }
61 15 50       60 push @result, pop @$colors if $steps > 1;
62 15         128 return @result;
63             }
64              
65             ########################################################################
66             my $adj_len_at_45_deg = sqrt(2) / 2;
67              
68             sub cluster { # :values, +radius @+|+distance, :space --> @:values
69 15     15 0 19597 my ($center_color, $cluster_radius, $color_distance, $color_space) = @_;
70 15         67 my $color_space_name = $color_space->name;
71 15         77 my $center_values = $center_color->shaped( $color_space_name );
72 15         40 my $center_x = $center_values->[0];
73 15         31 my $center_y = $center_values->[1];
74 15         34 my $center_z = $center_values->[2];
75 15         29 my @result_values;
76 15 100       68 if (ref $cluster_radius) { # cuboid shaped cluster
77 8         26 my $colors_in_direction = int $cluster_radius->[0] / $color_distance;
78 8         21 my $corner_value = $center_values->[0] - ($colors_in_direction * $color_distance);
79 8         29 @result_values = map {[$corner_value + ($_ * $color_distance)]} 0 .. 2 * $colors_in_direction;
  18         51  
80 8         34 for my $axis_index (1 .. $color_space->axis_count - 1){
81 17         38 my $colors_in_direction = int $cluster_radius->[$axis_index] / $color_distance;
82 17         32 my $corner_value = $center_values->[$axis_index] - ($colors_in_direction * $color_distance);
83             @result_values = map {
84 17         32 my @good_values = @$_[0 .. $axis_index-1];
  77         175  
85 77         135 map {[@good_values, ($corner_value + ($_ * $color_distance))]} 0 .. 2 * $colors_in_direction;
  285         694  
86             } @result_values;
87             }
88             } else { # ball shaped cluster (FCC)
89 7         29 my $layer_distance = sqrt( 2 * $color_distance * $color_distance ) / 2;
90 7         25 for my $layer_nr (0 .. $cluster_radius / $layer_distance){
91 14         32 my $layer_height = $layer_nr * $layer_distance;
92 14         29 my $layer_z_up = $center_z + $layer_height;
93 14         27 my $layer_z_dn = $center_z - $layer_height;
94 14         59 my $layer_radius = sqrt( ($cluster_radius**2) - ($layer_height**2) );
95 14         30 my $radius_in_colors = $layer_radius / $color_distance;
96 14 100       38 if ($layer_nr % 2){ # odd layer of cuboctahedral packing
97 6         18 my $contour_cursor = int ($radius_in_colors - 0.5);
98 6         18 my $grid_row_count = ($radius_in_colors * $adj_len_at_45_deg) - .5;
99 6 50       20 next if $grid_row_count < 0;
100 6         13 my @grid = ();
101 6         17 for my $x_index (0 .. $grid_row_count){
102 6 100       34 $contour_cursor-- if sqrt( (($contour_cursor+.5)**2) + (($x_index+.5)**2) ) > $radius_in_colors;
103 6         15 $grid[$x_index] = $contour_cursor;
104 6         16 $grid[$contour_cursor] = $x_index;
105             }
106 6         19 for my $x_index (0 .. $#grid){
107 7         18 my $delta_x = (0.5 + $x_index) * $color_distance;
108 7         20 my ($x1, $x2) = ($center_x + $delta_x, $center_x - $delta_x);
109 7         16 for my $y_index (0 .. $grid[$x_index]){
110 8         17 my $delta_y = (0.5 + $y_index) * $color_distance;
111 8         19 my ($y1, $y2) = ($center_y + $delta_y, $center_y - $delta_y);
112 8         63 push @result_values,
113             [$x1, $y1, $layer_z_up], [$x2, $y1, $layer_z_up],
114             [$x1, $y2, $layer_z_up], [$x2, $y2, $layer_z_up],
115             [$x1, $y1, $layer_z_dn], [$x2, $y1, $layer_z_dn],
116             [$x1, $y2, $layer_z_dn], [$x2, $y2, $layer_z_dn];
117             }
118             }
119             } else { # even layer of cuboctahedral packing
120 8         34 my $grid_row_count = int $radius_in_colors;
121 8         25 my @grid = ($grid_row_count);
122 8         21 $grid[$grid_row_count] = 0;
123 8         16 my $contour_cursor = $grid_row_count;
124 8         48 for my $x_index (1 .. $layer_radius * $adj_len_at_45_deg / $color_distance){
125 2 50       10 $contour_cursor-- if sqrt(($contour_cursor**2) + ($x_index**2)) > $radius_in_colors;
126 2         6 $grid[$x_index] = $contour_cursor;
127 2         5 $grid[$contour_cursor] = $x_index;
128             }
129 8         39 my @layer_values = map {[$center_x + ($_ * $color_distance), $center_y, $layer_z_up]}
  24         73  
130             -$grid_row_count .. $grid_row_count;
131 8         26 for my $y_index (1 .. $grid_row_count){
132 8         20 my $delta_y = $y_index * $color_distance;
133 8         55 my ($y1, $y2) = ($center_y + $delta_y, $center_y - $delta_y);
134 8         28 for my $x_index (-$grid[$y_index] .. $grid[$y_index]){
135 10         21 my $x = $center_x + ($x_index * $color_distance);
136 10         46 push @layer_values, [$x, $y1, $layer_z_up], [$x, $y2, $layer_z_up];
137             }
138             }
139 8 100       28 if ($layer_nr > 0){
140 1         26 push @result_values, [$_->[0], $_->[1], $layer_z_dn] for @layer_values;
141             }
142 8         36 push @result_values, @layer_values;
143             }
144             }
145             }
146             # check for linear space borders and constraints
147 283         869 return map { Graphics::Toolkit::Color::Values->new_from_tuple( $_, $color_space_name )}
148 15         45 grep { $color_space->is_in_linear_bounds($_) } @result_values;
  339         790  
149             }
150              
151             1;