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