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