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