File Coverage

blib/lib/ICC/Support/spline.pm
Criterion Covered Total %
statement 19 687 2.7
branch 2 348 0.5
condition 1 175 0.5
subroutine 6 44 13.6
pod 15 19 78.9
total 43 1273 3.3


line stmt bran cond sub pod time code
1             package ICC::Support::spline;
2              
3 2     2   79867 use strict;
  2         11  
  2         46  
4 2     2   8 use Carp;
  2         3  
  2         134  
5              
6             our $VERSION = 0.12;
7              
8             # revised 2018-09-04
9             #
10             # Copyright © 2004-2020 by William B. Birkett
11              
12             # inherit from Shared
13 2     2   342 use parent qw(ICC::Shared);
  2         257  
  2         9  
14              
15             # add complex math functions
16 2     2   1114 use Math::Complex qw(root sqrt cbrt Im Re);
  2         17435  
  2         166  
17              
18             # enable static variables
19 2     2   12 use feature 'state';
  2         4  
  2         16514  
20              
21             # create new spline object
22             # hash keys are: 'input', 'output', 'type', 'damp', 'fix_hl', 'fix_sh'
23             # default values for 'input' and 'output' are [0, 1]
24             # parameters: ([ref_to_attribute_hash])
25             # returns: (ref_to_object)
26             sub new {
27              
28             # get object class
29 1     1 1 861 my $class = shift();
30              
31             # create empty spline object
32 1         3 my $self = [
33             {}, # object header
34             [], # input values
35             [], # output values
36             [], # derivative values
37             [], # min/max output values
38             [], # parametric derivative matrix
39             ];
40              
41             # if one parameter, a hash reference
42 1 50 33     7 if (@_ == 1 && ref($_[0]) eq 'HASH') {
    50          
43            
44             # set object contents from hash
45 0         0 _new_from_hash($self, $_[0]);
46            
47             } elsif (@_) {
48            
49             # error
50 0         0 croak('invalid parameter(s)');
51            
52             }
53              
54             # return blessed object
55 1         3 return(bless($self, $class));
56              
57             }
58              
59             # create inverse object
60             # swaps the input and output arrays
61             # returns: (ref_to_object)
62             sub inv {
63              
64             # clone new object
65 0     0 1   my $inv = Storable::dclone(shift());
66              
67             # get min/max values
68 0           my @s = _minmax($inv);
69              
70             # verify object is monotonic
71 0 0         (! @s) or croak("cannot invert a non-monotonic 'spline' object");
72              
73             # if input is a range
74 0 0 0       if (@{$inv->[1]} == 2 && @{$inv->[2]} > 2) {
  0            
  0            
75            
76             # interpolate input array
77 0           $inv->[1] = [map {(1 - $_/$#{$inv->[2]}) * $inv->[1][0] + $_/$#{$inv->[2]} * $inv->[1][-1]} (0 .. $#{$inv->[2]})];
  0            
  0            
  0            
  0            
78            
79             }
80              
81             # swap input and output arrays
82 0           ($inv->[1], $inv->[2]) = ($inv->[2], $inv->[1]);
83              
84             # clear parameteric derivative matrix
85 0           $inv->[5] = [];
86              
87             # sort input/output values
88 0           _sort_values($inv);
89              
90             # compute knot derivatives
91 0           _objderv($inv);
92              
93             # add segment min/max y-values
94 0           _minmax($inv);
95              
96             # return inverted object
97 0           return($inv);
98              
99             }
100              
101             # write spline object to ICC profile
102             # note: writes an equivalent 'curv' object
103             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
104             sub write_fh {
105              
106             # verify 4 parameters
107 0 0   0 0   (@_ == 4) or croak('wrong number of parameters');
108              
109             # write spline data to profile
110 0           goto &_writeICCspline;
111              
112             }
113              
114             # get tag size (for writing to profile)
115             # note: writes an equivalent curv object
116             # returns: (tag_size)
117             sub size {
118              
119             # get parameters
120 0     0 0   my ($self) = @_;
121              
122             # get count value (defaults to 4096, the maximum allowed in an 'mft2' tag)
123 0   0       my $n = $self->[0]{'curv_points'} // 4096;
124              
125             # return size
126 0           return(12 + $n * 2);
127              
128             }
129              
130             # compute curve function
131             # parameters: (input_value)
132             # returns: (output_value)
133             sub transform {
134              
135             # get parameters
136 0     0 1   my ($self, $in) = @_;
137              
138             # local variable
139 0           my ($low);
140              
141             # if an extrapolated solution (s < 0)
142 0 0         if ($in < $self->[1][0]) {
    0          
    0          
143            
144             # return extrapolated value
145 0           return($self->[2][0] + $self->[3][0] * ($in - $self->[1][0]));
146            
147             # if an extrapolated solution (s >= 1)
148             } elsif ($in >= $self->[1][-1]) {
149            
150             # return extrapolated value
151 0           return($self->[2][-1] + $self->[3][-1] * ($in - $self->[1][-1]));
152            
153             # if input array contains x-values
154 0           } elsif ($#{$self->[1]} > 1) {
155            
156             # initilize segment
157 0           $low = 0;
158            
159             # while input value > upper knot value
160 0           while ($in > $self->[1][$low + 1]) {
161            
162             # increment segment
163 0           $low++;
164            
165             }
166            
167             # return interpolated value
168 0           return(_fwd($self, ($self->[1][$low] - $in)/($self->[1][$low] - $self->[1][$low + 1]), $low));
169            
170             # input array contains range
171             } else {
172            
173             # return interpolated value
174 0           return(_fwd($self, POSIX::modf($#{$self->[2]} * ($in - $self->[1][0])/($self->[1][1] - $self->[1][0]))));
  0            
175            
176             }
177            
178             }
179              
180             # compute inverse curve function
181             # note: there may be multiple solutions
182             # parameters: (input_value)
183             # returns: (output_value -or- array_of_output_values)
184             sub inverse {
185              
186             # get parameters
187 0     0 1   my ($self, $in) = @_;
188              
189             # local variables
190 0           my ($ix, @sol, @t);
191              
192             # get output array upper index
193 0           $ix = $#{$self->[2]};
  0            
194              
195             # if an extrapolated solution (s < 0)
196 0 0 0       if (($in < $self->[2][0] && $self->[3][0] > 0) || ($in > $self->[2][0] && $self->[3][0] < 0)) {
      0        
      0        
197            
198             # add solution
199 0           push(@sol, $self->[1][0] + ($in - $self->[2][0])/$self->[3][0]);
200            
201             }
202              
203             # for each segment (0 <= s < 1)
204 0           for my $i (0 .. $ix - 1) {
205            
206             # if input is lower knot
207 0 0         if ($in == $self->[2][$i]) {
208            
209             # add solution (x-values : range)
210 0 0         push(@sol, ($#{$self->[1]} > 1) ? $self->[1][$i] : ($self->[1][0] * ($ix - $i) + $self->[1][1] * $i)/$ix);
  0            
211            
212             }
213            
214             # if input lies within the y-value range
215 0 0 0       if ($in >= $self->[4][$i][0] && $in <= $self->[4][$i][1]) {
216            
217             # compute inverse values (t-values)
218 0           @t = _rev($self, $in, $i);
219            
220             # if input array contains x-values
221 0 0         if ($#{$self->[1]} > 1) {
  0            
222            
223             # add solution(s)
224 0           push(@sol, map {(1 - $_) * $self->[1][$i] + $_ * $self->[1][$i + 1]} @t);
  0            
225            
226             # input array contains range
227             } else {
228            
229             # add solution(s)
230 0           push(@sol, map {($self->[1][0] * ($ix - $i - $_) + $self->[1][1] * ($i + $_))/$ix} @t);
  0            
231            
232             }
233            
234             }
235            
236             }
237              
238             # if input is last knot (s == 1)
239 0 0         if ($in == $self->[2][-1]) {
240            
241             # add solution
242 0           push(@sol, $self->[1][-1]);
243            
244             }
245              
246             # if an extrapolated solution (s > 1)
247 0 0 0       if (($in > $self->[2][-1] && $self->[3][-1] > 0) || ($in < $self->[2][-1] && $self->[3][-1] < 0)) {
      0        
      0        
248            
249             # add solution
250 0           push(@sol, $self->[1][-1] + ($in - $self->[2][-1])/$self->[3][-1]);
251            
252             }
253              
254             # return result (array or first solution)
255 0 0         return(wantarray ? @sol : $sol[0]);
256              
257             }
258              
259             # compute curve derivative
260             # parameters: (input_value)
261             # returns: (derivative_value)
262             sub derivative {
263              
264             # get parameters
265 0     0 1   my ($self, $in) = @_;
266              
267             # local variable
268 0           my ($low);
269              
270             # if an extrapolated solution (s < 0)
271 0 0         if ($in < $self->[1][0]) {
    0          
    0          
272            
273             # return endpoint derivative
274 0           return($self->[3][0]);
275            
276             # if an extrapolated solution (s >= 1)
277             } elsif ($in >= $self->[1][-1]) {
278            
279             # return endpoint derivative
280 0           return($self->[3][-1]);
281            
282             # if input array contains x-values
283 0           } elsif ($#{$self->[1]} > 1) {
284            
285             # initilize segment
286 0           $low = 0;
287            
288             # while input value > upper knot value
289 0           while ($in > $self->[1][$low + 1]) {
290            
291             # increment segment
292 0           $low++;
293            
294             }
295            
296             # return interpolated derivative value
297 0           return(_derv($self, ($self->[1][$low] - $in)/($self->[1][$low] - $self->[1][$low + 1]), $low));
298            
299             # input array contains range
300             } else {
301            
302             # return interpolated derivative value
303 0           return(_derv($self, POSIX::modf($#{$self->[2]} * ($in - $self->[1][0])/($self->[1][1] - $self->[1][0]))));
  0            
304            
305             }
306            
307             }
308              
309             # compute parametric partial derivatives
310             # parameters: (input_value)
311             # returns: (partial_derivative_array)
312             sub parametric {
313              
314             # get parameters
315 0     0 1   my ($self, $in) = @_;
316              
317             # local variables
318 0           my ($dx, $low, $t, $tc, $h00, $h01, $h10, $h11, @pj);
319              
320             # update parametric derivative matrix, if necessary
321 0 0 0       _objpara($self) if (! defined($self->[5]) || $#{$self->[5]} != $#{$self->[2]});
  0            
  0            
322              
323             # if an extrapolated solution (s < 0)
324 0 0         if ($in < $self->[1][0]) {
    0          
325            
326             # for each output value (parameter)
327 0           for my $i (0 .. $#{$self->[2]}) {
  0            
328            
329             # compute partial derivative
330 0           $pj[$i] = ($i == 0) + $self->[5][0][$i] * ($in - $self->[1][0]);
331            
332             }
333            
334             # if an extrapolated solution (s >= 1)
335             } elsif ($in >= $self->[1][-1]) {
336            
337             # for each output value (parameter)
338 0           for my $i (0 .. $#{$self->[2]}) {
  0            
339            
340             # compute partial derivative
341 0           $pj[$i] = ($i == $#{$self->[2]}) + $self->[5][-1][$i] * ($in - $self->[1][-1]);
  0            
342            
343             }
344            
345             } else {
346            
347             # if input array contains x-values
348 0 0         if ($#{$self->[1]} > 1) {
  0            
349            
350             # initialize segment
351 0           $low = 0;
352            
353             # while input value > upper knot value
354 0           while ($in > $self->[1][$low + 1]) {
355            
356             # increment segment
357 0           $low++;
358            
359             }
360            
361             # compute delta x-value
362 0           $dx = $self->[1][$low + 1] - $self->[1][$low];
363            
364             # compute t-value
365 0           $t = ($in - $self->[1][$low])/$dx;
366            
367             # input array contains range
368             } else {
369            
370             # compute delta x-value
371 0           $dx = ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
372            
373             # compute t-value, index
374 0           ($t, $low) = POSIX::modf(($in - $self->[1][0])/$dx);
375            
376             }
377            
378             # compute Hermite coefficients
379 0           $tc = 1 - $t;
380 0           $h00 = (1 + 2 * $t) * $tc * $tc;
381 0           $h01 = 1 - $h00;
382 0           $h10 = $t * $tc * $tc;
383 0           $h11 = -$t * $t * $tc;
384            
385             # for each output value (parameter)
386 0           for my $i (0 .. $#{$self->[2]}) {
  0            
387            
388             # compute partial derivative
389 0           $pj[$i] = $h00 * ($low == $i) + $h01 * ($low + 1 == $i) + $h10 * $dx * $self->[5][$low][$i] + $h11 * $dx * $self->[5][$low + 1][$i];
390            
391             }
392            
393             }
394              
395             # return
396 0           return(@pj);
397              
398             }
399              
400             # get/set header hash reference
401             # parameters: ([ref_to_new_hash])
402             # returns: (ref_to_hash)
403             sub header {
404              
405             # get object reference
406 0     0 1   my $self = shift();
407              
408             # if there are parameters
409 0 0         if (@_) {
410            
411             # if one parameter, a hash reference
412 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
413            
414             # set header to copy of hash
415 0           $self->[0] = Storable::dclone(shift());
416            
417             } else {
418            
419             # error
420 0           croak('parameter must be a hash reference');
421            
422             }
423            
424             }
425              
426             # return reference
427 0           return($self->[0]);
428              
429             }
430              
431             # get/set input array reference
432             # an array of two values is a range
433             # otherwise, it contains input values
434             # parameters: ([ref_to_array])
435             # returns: (ref_to_array)
436             sub input {
437              
438             # get object reference
439 0     0 1   my $self = shift();
440              
441             # if one parameter supplied
442 0 0         if (@_ == 1) {
    0          
443            
444             # verify 'input' vector (two or more numeric values)
445 0 0 0       (ICC::Shared::is_num_vector($_[0]) && 2 <= @{$_[0]}) or croak('invalid input values');
  0            
446            
447             # verify 'input' vector size
448 0 0 0       (@{$_[0]} == 2 || @{$_[0]} == @{$self->[2]}) or carp('number of input values differs from object output values');
  0            
  0            
  0            
449            
450             # store output values
451 0           $self->[1] = [@{$_[0]}];
  0            
452            
453             # update object internal elements
454 0           update($self, 1);
455            
456             } elsif (@_) {
457            
458             # error
459 0           carp('too many parameters');
460            
461             }
462              
463             # return array reference
464 0           return($self->[1]);
465              
466             }
467              
468             # get/set output array reference
469             # parameters: ([ref_to_array])
470             # returns: (ref_to_array)
471             sub output {
472              
473             # get object reference
474 0     0 1   my $self = shift();
475              
476             # if one parameter supplied
477 0 0         if (@_ == 1) {
    0          
478            
479             # verify 'output' vector (two or more numeric values)
480 0 0 0       (ICC::Shared::is_num_vector($_[0]) && 2 <= @{$_[0]}) or croak('invalid output values');
  0            
481            
482             # verify 'output' vector size
483 0 0 0       (@{$self->[1]} == 2 || @{$_[0]} == @{$self->[1]}) or carp('number of output values differs from object input values');
  0            
  0            
  0            
484            
485             # store output values
486 0           $self->[2] = [@{$_[0]}];
  0            
487            
488             # update object internal elements
489 0           update($self);
490            
491             } elsif (@_) {
492            
493             # error
494 0           carp('too many parameters');
495            
496             }
497              
498             # return array reference
499 0           return($self->[2]);
500              
501             }
502              
503             # normalize transform
504             # adjusts Bernstein coefficients linearly
505             # adjusts 'input' and 'output' by default
506             # hash keys: 'input', 'output'
507             # parameter: ([hash_ref])
508             # returns: (ref_to_object)
509             sub normalize {
510              
511             # get parameters
512 0     0 1   my ($self, $hash) = @_;
513              
514             # local variables
515 0           my ($m, $b, $val, $src, $x0, $x1, $y0, $y1, $f0, $f1, @minmax, $p);
516              
517             # set hash key array
518 0           my @key = qw(input output);
519              
520             # for ('input', 'output')
521 0           for my $i (0, 1) {
522            
523             # undefine slope
524 0           $m = undef;
525            
526             # if no parameter (default)
527 0 0         if (! defined($hash)) {
528            
529             # if array has 2 or more coefficients
530 0 0 0       if (@{$self->[$i + 1]} > 1 && ($x0 = $self->[$i + 1][0]) != ($x1 = $self->[$i + 1][-1])) {
  0            
531            
532             # special case, if 'output' and 'fix_hl' was called
533 0 0 0       $x0 = 0 if ($i && defined($self->[0]{'hl_retention'}));
534            
535             # compute adjustment values
536 0           $m = abs(1/($x0 - $x1));
537 0 0         $b = -$m * ($x0 < $x1 ? $x0 : $x1);
538            
539             # set endpoints
540 0 0         ($y0, $y1) = $x0 < $x1 ? (0, 1) : (1, 0);
541            
542             }
543            
544             } else {
545            
546             # if hash contains key
547 0 0         if (defined($val = $hash->{$key[$i]})) {
548            
549             # if array has 2 or more coefficients
550 0 0 0       if (@{$self->[$i + 1]} > 1 && $self->[$i + 1][0] != $self->[$i + 1][-1]) {
  0            
551            
552             # if hash value is an array reference
553 0 0         if (ref($val) eq 'ARRAY') {
554            
555             # if two parameters
556 0 0         if (@{$val} == 2) {
  0 0          
    0          
557            
558             # get parameters
559 0           ($y0, $y1) = @{$val};
  0            
560            
561             # get endpoints
562 0           $x0 = $self->[$i + 1][0];
563 0           $x1 = $self->[$i + 1][-1];
564            
565             # if three parameters
566 0           } elsif (@{$val} == 3) {
567            
568             # get parameters
569 0           ($src, $y0, $y1) = @{$val};
  0            
570            
571             # if source is 'endpoints'
572 0 0 0       if (! ref($src) && $src eq 'endpoints') {
    0 0        
573            
574             # get endpoints
575 0           $x0 = $self->[$i + 1][0];
576 0           $x1 = $self->[$i + 1][-1];
577            
578             # if source is 'minmax'
579             } elsif (! ref($src) && $src eq 'minmax') {
580            
581             # if 'output' curve
582 0 0         if ($i) {
583            
584             # get all min/max values
585 0           @minmax = (map {@{$_}} @{$self->[4]});
  0            
  0            
  0            
586            
587             # get min/max values
588 0           $x0 = List::Util::min(@minmax);
589 0           $x1 = List::Util::max(@minmax);
590            
591             # if 'input' curve
592             } else {
593            
594             # get min/max values (endpoints)
595 0           $x0 = $self->[1][0];
596 0           $x1 = $self->[1][-1];
597            
598             }
599            
600             }
601            
602             # if four parameters
603 0           } elsif (@{$val} == 4) {
604            
605             # get parameters
606 0           ($x0, $x1, $y0, $y1) = @{$val};
  0            
607            
608             }
609            
610             # if x/y values are numeric
611 0 0 0       if ((4 == grep {Scalar::Util::looks_like_number($_)} ($x0, $x1, $y0, $y1)) && ($x0 != $x1)) {
  0            
612            
613             # compute slope
614 0           $m = ($y0 - $y1)/($x0 - $x1);
615            
616             # compute offset
617 0           $b = $y0 - $m * $x0;
618            
619             } else {
620            
621             # warning
622 0           carp("invalid '$key[$i]' parameter(s)");
623            
624             }
625            
626             }
627            
628             } else {
629            
630             # warning
631 0           carp("can't normalize '$key[$i]' coefficients");
632            
633             }
634            
635             }
636            
637             }
638            
639             # if slope is defined
640 0 0         if (defined($m)) {
641            
642             # set endpoint flags
643 0           $f0 = $self->[$i + 1][0] == $x0;
644 0           $f1 = $self->[$i + 1][-1] == $x1;
645            
646             # adjust Bernstein coefficients (y = mx + b)
647 0           @{$self->[$i + 1]} = map {$m * $_ + $b} @{$self->[$i + 1]};
  0            
  0            
  0            
648            
649             # set endpoints (to ensure exact values)
650 0 0         $self->[$i + 1][0] = $y0 if ($f0);
651 0 0         $self->[$i + 1][-1] = $y1 if ($f1);
652            
653             # save input polarity
654 0 0         $p = $m < 0 if ($i == 0);
655            
656             # if 'input' and 'hl_retention' is defined
657 0 0 0       if (! $i && defined($val = $self->[0]{'hl_retention'})) {
658            
659             # adjust value
660 0           $self->[0]{'hl_retention'} = $m * $val + $b;
661            
662             }
663            
664             # if 'output' and 'hl_original' is defined
665 0 0 0       if ($i && defined($val = $self->[0]{'hl_original'})) {
666            
667             # adjust values
668 0           $self->[0]{'hl_original'} = [map {$m * $_ + $b} @{$val}];
  0            
  0            
669            
670             }
671            
672             # if 'output' and 'sh_original' is defined
673 0 0 0       if ($i && defined($val = $self->[0]{'sh_original'})) {
674            
675             # adjust values
676 0           $self->[0]{'sh_original'} = [map {$m * $_ + $b} @{$val}];
  0            
  0            
677            
678             }
679            
680             }
681            
682             }
683              
684             # update object internals, sorting values if 'input' polarity changed
685 0           update($self, $p);
686              
687             # return
688 0           return($self);
689              
690             }
691              
692             # check if monotonic
693             # returns min/max values
694             # returns s-values by default
695             # returns input-values if format is 'input'
696             # returns output-values if format is 'output'
697             # curve is monotonic if no min/max values
698             # parameters: ([format])
699             # returns: (array_of_values)
700             sub monotonic {
701              
702             # get object reference
703 0     0 1   my ($self, $fmt) = @_;
704            
705             # local variables
706 0           my ($t, $low);
707              
708             # if format undefined
709 0 0         if (! defined($fmt)) {
    0          
    0          
710            
711             # return s-values
712 0           return(_minmax($self));
713            
714             # if format 'input'
715             } elsif ($fmt eq 'input') {
716            
717             # if input array contains x-values
718 0 0         if ($#{$self->[1]} > 1) {
  0            
719            
720             # return input values
721 0           return(map {($t, $low) = POSIX::modf($_); (1 - $t) * $self->[1][$low] + $t * $self->[1][$low + 1]} _minmax($self));
  0            
  0            
722            
723             # input array contains range
724             } else {
725            
726             # return input values
727 0           return(map {$t = $_/$#{$self->[2]}; (1 - $t) * $self->[1][0] + $t * $self->[1][1]} _minmax($self));
  0            
  0            
  0            
728            
729             }
730            
731             # if format 'output'
732             } elsif ($fmt eq 'output') {
733            
734             # return output values
735 0           return(map {_fwd($self, POSIX::modf($_))} _minmax($self));
  0            
736            
737             } else {
738            
739             # error
740 0           croak('unsupported format for min/max values');
741            
742             }
743            
744             }
745              
746             # update internal object elements
747             # this method used primarily when optimizing
748             # call after changing 'input' or 'output' values
749             # if the 'input' values were changed, set the flag
750             # parameter: ([flag])
751             sub update {
752              
753             # get parameters
754 0     0 1   my ($self, $flag) = @_;
755              
756             # sort values if flag set
757 0 0         _sort_values($self) if ($flag);
758              
759             # recompute knot derivatives
760 0           _objderv($self);
761              
762             # recompute segment min/max y-values
763 0           _minmax($self);
764              
765             # recompute parametric derivative matrix if flag set, or 'akima' type spline
766 0 0 0       _objpara($self) if ($flag || (defined($self->[0]{'type'} && $self->[0]{'type'} eq 'akima')));
      0        
767              
768             }
769              
770             # make table for 'curv' objects
771             # assumes curve domain/range is (0 - 1)
772             # parameters: (number_of_table_entries, [direction])
773             # returns: (ref_to_table_array)
774             sub table {
775              
776             # get parameters
777 0     0 1   my ($self, $n, $dir) = @_;
778              
779             # local variables
780 0           my ($up, $table);
781              
782             # validate number of table entries
783 0 0 0       ($n == int($n) && $n >= 2) or carp('invalid number of table entries');
784              
785             # purify direction flag
786 0 0         $dir = ($dir) ? 1 : 0;
787              
788             # array upper index
789 0           $up = $n - 1;
790              
791             # for each table entry
792 0           for my $i (0 .. $up) {
793            
794             # compute table value
795 0           $table->[$i] = _transform($self, $dir, $i/$up);
796            
797             }
798              
799             # return table reference
800 0           return($table);
801              
802             }
803              
804             # make equivalent 'curv' object
805             # assumes curve domain/range is (0 - 1)
806             # parameters: (number_of_table_entries, [direction])
807             # returns: (ref_to_curv_object)
808             sub curv {
809              
810             # return 'curv' object reference
811 0     0 1   return(ICC::Profile::curv->new(table(@_)));
812              
813             }
814              
815             # print object contents to string
816             # format is an array structure
817             # parameter: ([format])
818             # returns: (string)
819             sub sdump {
820              
821             # get parameters
822 0     0 1   my ($self, $p) = @_;
823              
824             # local variables
825 0           my ($s, $fmt, $fmt2);
826              
827             # resolve parameter to an array reference
828 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
829              
830             # get format string
831 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
832              
833             # set string to object ID
834 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
835              
836             # if input_parameter_array defined
837 0 0         if (defined($self->[1])) {
838            
839             # set format
840 0           $fmt2 = join(', ', ('%.3f') x @{$self->[1]});
  0            
841            
842             # append parameter string
843 0           $s .= sprintf(" input parameters [$fmt2]\n", @{$self->[1]});
  0            
844            
845             }
846              
847             # if output_parameter_array defined
848 0 0         if (defined($self->[2])) {
849            
850             # set format
851 0           $fmt2 = join(', ', ('%.3f') x @{$self->[2]});
  0            
852            
853             # append parameter string
854 0           $s .= sprintf(" output parameters [$fmt2]\n", @{$self->[2]});
  0            
855            
856             }
857              
858             # return
859 0           return($s);
860              
861             }
862              
863             # combined transform
864             # direction: 0 - normal, 1 - inverse
865             # parameters: (ref_to_object, direction, input_value)
866             # returns: (output_value)
867             sub _transform {
868              
869             # get parameters
870 0     0     my ($self, $dir, $in) = @_;
871              
872             # if inverse direction
873 0 0         if ($dir) {
874            
875             # return inverse
876 0           return(scalar(inverse($self, $in)));
877            
878             } else {
879            
880             # return transform
881 0           return(transform($self, $in));
882            
883             }
884            
885             }
886              
887             # combined derivative
888             # direction: 0 - normal, 1 - inverse
889             # parameters: (ref_to_object, direction, input_value)
890             # returns: (derivative_value)
891             sub _derivative {
892              
893             # get parameters
894 0     0     my ($self, $dir, $in) = @_;
895              
896             # if inverse direction
897 0 0         if ($dir) {
898            
899             # unimplemented function error
900 0           croak('unimplemented function');
901            
902             } else {
903            
904             # return derivative
905 0           return(derivative($self, $in));
906            
907             }
908            
909             }
910              
911             # compute knot derivatives
912             # parameter: (ref_to_object)
913             sub _objderv {
914              
915             # get parameter
916 0     0     my $self = shift();
917              
918             # verify input values are unique
919 0 0         _unique($self->[1]) or croak('input values not unique');
920            
921             # if spline type undefined or 'natural'
922 0 0 0       if (! defined($self->[0]{'type'}) || $self->[0]{'type'} eq 'natural') {
    0          
923            
924             # compute knot derivatives for natural spline
925 0           _natural_derv($self)
926            
927             # if spline type is 'akima'
928             } elsif ($self->[0]{'type'} eq 'akima') {
929            
930             # compute knot derivatives for Akima spline
931 0           _akima_derv($self);
932            
933             } else {
934            
935             # error
936 0           croak('undefined spline type');
937            
938             }
939            
940             }
941              
942             # compute knot derivatives for natural spline
943             # parameter: (ref_to_object)
944             sub _natural_derv {
945              
946             # get parameter
947 0     0     my $self = shift();
948              
949             # local variables
950 0           my ($ix, $rhs, $dx, $info, $derv);
951              
952             # verify object has two or more knots
953 0 0         (($ix = $#{$self->[2]}) > 0) or croak('object must have two or more knots');
  0            
954              
955             # check if ICC::Support::Lapack module is loaded
956 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
957              
958             # make empty Math::Matrix object
959 0           $rhs = bless([], 'Math::Matrix');
960              
961             # if input array contains input values
962 0 0         if ($#{$self->[1]} > 1) {
  0            
963            
964             # for each inner element
965 0           for my $i (1 .. $ix - 1) {
966            
967             # compute rhs (6 * (y[i + 1] - y[i - 1])/(x[i + 1] - x[i - 1]))
968 0           $rhs->[$i][0] = 6 * ($self->[2][$i + 1] - $self->[2][$i - 1])/($self->[1][$i + 1] - $self->[1][$i - 1]);
969            
970             }
971            
972             # set rhs endpoint values
973 0           $rhs->[0][0] = 3 * ($self->[2][1] - $self->[2][0])/($self->[1][1] - $self->[1][0]);
974 0           $rhs->[$ix][0] = 3 * ($self->[2][$ix] - $self->[2][$ix - 1])/($self->[1][$ix] - $self->[1][$ix - 1]);
975            
976             # input array contains range
977             } else {
978            
979             # compute x-segment length
980 0           $dx = ($self->[1][1] - $self->[1][0])/$ix;
981            
982             # for each inner element
983 0           for my $i (1 .. $ix - 1) {
984            
985             # compute rhs (6 * (y[i + 1] - y[i - 1]))/(x[i + 1] - x[i - 1])
986 0           $rhs->[$i][0] = 3 * ($self->[2][$i + 1] - $self->[2][$i - 1])/$dx;
987            
988             }
989            
990             # set rhs endpoint values
991 0           $rhs->[0][0] = 3 * ($self->[2][1] - $self->[2][0])/$dx;
992 0           $rhs->[$ix][0] = 3 * ($self->[2][$ix] - $self->[2][$ix - 1])/$dx;
993            
994             }
995              
996             # if ICC::Support::Lapack module is loaded
997 0 0         if ($lapack) {
998            
999             # solve for derivative matrix
1000 0           ($info, $derv) = ICC::Support::Lapack::trisolve([(1) x $ix], [2, (4) x ($ix - 1), 2], [(1) x $ix], $rhs);
1001            
1002             # otherwise, use Math::Matrix module
1003             } else {
1004            
1005             # solve for derivative matrix
1006 0           $derv = Math::Matrix->tridiagonal([2, (4) x ($ix - 1), 2])->concat($rhs)->solve();
1007            
1008             }
1009              
1010             # for each knot
1011 0           for my $i (0 .. $ix) {
1012            
1013             # set derivative value
1014 0           $self->[3][$i] = $derv->[$i][0];
1015            
1016             }
1017              
1018             }
1019              
1020             # compute knot derivatives for Akima spline
1021             # parameter: (ref_to_object)
1022             sub _akima_derv {
1023              
1024             # get parameter
1025 0     0     my $self = shift();
1026              
1027             # local variables
1028 0           my ($ix, @m, $s, $b, $d1, $d2);
1029              
1030             # verify object has two or more knots
1031 0 0         (($ix = $#{$self->[2]}) > 0) or croak('object must have two or more knots');
  0            
1032              
1033             # compute uniform segment length (for 'input' range)
1034 0 0         $s = ($self->[1][1] - $self->[1][0])/$ix if ($#{$self->[1]} == 1);
  0            
1035              
1036             # for each segment
1037 0           for my $i (0 .. ($ix - 1)) {
1038            
1039             # compute segment slope (range // x-values)
1040 0   0       $m[$i + 2] = ($self->[2][$i + 1] - $self->[2][$i])/($s // ($self->[1][$i + 1] - $self->[1][$i]));
1041            
1042             }
1043              
1044             # if 2 points
1045 0 0         if ($ix == 1) {
1046            
1047             # linear slopes
1048 0           $m[0] = $m[1] = $m[3] = $m[4] = $m[2];
1049            
1050             # if 3 or more points
1051             } else {
1052            
1053             # quadratic slopes
1054 0           $m[1] = 2 * $m[2] - $m[3];
1055 0           $m[0] = 2 * $m[1] - $m[2];
1056 0           $m[$ix + 2] = 2 * $m[$ix + 1] - $m[$ix];
1057 0           $m[$ix + 3] = 2 * $m[$ix + 2] - $m[$ix + 1];
1058            
1059             }
1060              
1061             # if damping value is undefined
1062 0 0         if (! defined($self->[0]{'damp'})) {
1063            
1064             # set default damping value
1065 0           $self->[0]{'damp'} = List::Util::sum(map {$_ * $_} @m)/(@m * 50);
  0            
1066            
1067             }
1068              
1069             # Akima used abs() functions to calulate the slopes
1070             # this can lead to abrupt changes in curve shape and parametric derivatives
1071             # we replace abs(x) with sqrt(x^2 + b) to add stability
1072              
1073             # get damping value and verify >= 0
1074 0 0         (($b = $self->[0]{'damp'}) >= 0) or carp('akima damping value is negative');
1075              
1076             # compute Akima derivative
1077 0           for my $i (0 .. $ix) {
1078            
1079             # compute slope differences (with damping)
1080 0           $d1 = sqrt(($m[$i + 1] - $m[$i])**2 + $b);
1081 0           $d2 = sqrt(($m[$i + 3] - $m[$i + 2])**2 + $b);
1082            
1083             # if denominator not 0
1084 0 0         if ($d1 + $d2) {
1085            
1086             # use Akima slope (weighted average with damping)
1087 0           $self->[3][$i] = ($d2 * $m[$i + 1] + $d1 * $m[$i + 2])/($d1 + $d2);
1088            
1089             } else {
1090            
1091             # otherwise, use average slope of adjoining segments
1092 0           $self->[3][$i] = ($m[$i + 1] + $m[$i + 2])/2;
1093            
1094             }
1095            
1096             }
1097            
1098             }
1099              
1100             # compute partial derivative matrix
1101             # parameter: (ref_to_object)
1102             sub _objpara {
1103              
1104             # get parameter
1105 0     0     my $self = shift();
1106              
1107             # verify input values are unique
1108 0 0         _unique($self->[1]) or croak('input values not unique');
1109            
1110             # if spline type undefined or 'natural'
1111 0 0 0       if (! defined($self->[0]{'type'}) || $self->[0]{'type'} eq 'natural') {
    0          
1112            
1113             # compute knot derivatives for natural spline
1114 0           _natural_para($self)
1115            
1116             # if spline type is 'akima'
1117             } elsif ($self->[0]{'type'} eq 'akima') {
1118            
1119             # compute knot derivatives for Akima spline
1120 0           _akima_para($self);
1121            
1122             } else {
1123            
1124             # error
1125 0           croak('undefined spline type');
1126            
1127             }
1128            
1129             }
1130              
1131             # compute partial derivative matrix for natural spline
1132             # parameter: (ref_to_object)
1133             sub _natural_para {
1134              
1135             # get parameter
1136 0     0     my $self = shift();
1137              
1138             # local variables
1139 0           my ($ix, $n, $rhs, $x, $info, $derv);
1140              
1141             # verify object has two or more knots
1142 0 0         (($ix = $#{$self->[2]}) > 0) or croak('object must have two or more knots');
  0            
1143              
1144             # check if ICC::Support::Lapack module is loaded
1145 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
1146              
1147             # make rhs matrix (filled with zeros)
1148 0 0         $rhs = $lapack ? ICC::Support::Lapack::zeros($ix + 1) : bless([map {[(0) x ($ix + 1)]} (0 .. $ix)], 'Math::Matrix');
  0            
1149              
1150             # if input array contains input values
1151 0 0         if ($#{$self->[1]} > 1) {
  0            
1152            
1153             # for each inner element
1154 0           for my $i (1 .. $ix - 1) {
1155            
1156             # set rhs diagonal values
1157 0           $rhs->[$i][$i - 1] = $x = 6/($self->[1][$i - 1] - $self->[1][$i + 1]);
1158 0           $rhs->[$i][$i + 1] = -$x;
1159            
1160             }
1161            
1162             # set rhs endpoint values
1163 0           $rhs->[0][0] = $x = 3/($self->[1][0] - $self->[1][1]);
1164 0           $rhs->[0][1] = -$x;
1165 0           $rhs->[$ix][$ix] = $x = 3/($self->[1][$ix] - $self->[1][$ix - 1]);
1166 0           $rhs->[$ix][$ix -1] = -$x;
1167            
1168             # input array contains range
1169             } else {
1170            
1171             # compute rhs value
1172 0           $x = 3 * $ix/($self->[1][1] - $self->[1][0]);
1173            
1174             # for each inner element
1175 0           for my $i (1 .. $ix - 1) {
1176            
1177             # set rhs diagonal values
1178 0           $rhs->[$i - 1][$i] = $x;
1179 0           $rhs->[$i + 1][$i] = -$x;
1180            
1181             }
1182            
1183             # set rhs endpoint values
1184 0           $rhs->[0][0] = -$x;
1185 0           $rhs->[1][0] = -$x;
1186 0           $rhs->[$ix][$ix] = $x;
1187 0           $rhs->[$ix - 1][$ix] = $x;
1188            
1189             }
1190              
1191             # if ICC::Support::Lapack module is loaded
1192 0 0         if ($lapack) {
1193            
1194             # solve for derivative matrix
1195 0           ($info, $derv) = ICC::Support::Lapack::trisolve([(1) x $ix], [2, (4) x ($ix - 1), 2], [(1) x $ix], $rhs);
1196            
1197             # set object
1198 0           $self->[5] = bless($derv, 'Math::Matrix');
1199            
1200             # otherwise, use Math::Matrix module
1201             } else {
1202            
1203             # solve for derivative matrix
1204 0           $self->[5] = Math::Matrix->tridiagonal([2, (4) x ($ix - 1), 2])->concat($rhs)->solve();
1205            
1206             }
1207            
1208             }
1209              
1210             # compute partial derivative matrix for Akima spline
1211             # parameter: (ref_to_object)
1212             sub _akima_para {
1213              
1214             # get object
1215 0     0     my $self = shift();
1216              
1217             # local variables
1218 0           my ($ix, $s, @m, @x, $b);
1219 0           my ($d1, $d2, $dif, @dm, $dd1, $dd2, $derv);
1220            
1221             # see 'akima_parametric.plx' for details on this function
1222              
1223             # verify object has two or more knots
1224 0 0         (($ix = $#{$self->[2]}) > 0) or croak('object must have two or more knots');
  0            
1225              
1226             # compute uniform segment length (for 'input' range)
1227 0 0         $s = ($self->[1][1] - $self->[1][0])/$ix if ($#{$self->[1]} == 1);
  0            
1228              
1229             # for each segment
1230 0           for my $i (0 .. ($ix - 1)) {
1231            
1232             # compute segment x-length
1233 0   0       $x[$i + 2] = $s // ($self->[1][$i + 1] - $self->[1][$i]);
1234            
1235             # compute segment slope (range // x-values)
1236 0           $m[$i + 2] = ($self->[2][$i + 1] - $self->[2][$i])/$x[$i + 2];
1237            
1238             }
1239              
1240             # if 2 points
1241 0 0         if ($ix == 1) {
1242            
1243             # linear slopes
1244 0           $m[0] = $m[1] = $m[3] = $m[4] = $m[2];
1245            
1246             # if 3 or more points
1247             } else {
1248            
1249             # quadratic slopes
1250 0           $m[1] = 2 * $m[2] - $m[3];
1251 0           $m[0] = 2 * $m[1] - $m[2];
1252 0           $m[$ix + 2] = 2 * $m[$ix + 1] - $m[$ix];
1253 0           $m[$ix + 3] = 2 * $m[$ix + 2] - $m[$ix + 1];
1254            
1255             }
1256              
1257             # get the damping value
1258 0           $b = $self->[0]{'damp'};
1259              
1260             # for each node
1261 0           for my $i (0 .. $ix) {
1262            
1263             # add row of zeros
1264 0           $derv->[$i] = [(0) x ($ix + 1)];
1265            
1266             }
1267              
1268             # if 2 knots
1269 0 0         if ($ix == 1) {
1270            
1271             # set partial derivatives
1272 0           $derv = [[-1/$x[2], 1/$x[2]], [-1/$x[2], 1/$x[2]]];
1273            
1274             } else {
1275            
1276             # for each row
1277 0           for my $i (0 .. $ix) {
1278            
1279             # compute slope differences (with damping)
1280 0           $d1 = sqrt(($m[$i + 1] - $m[$i])**2 + $b);
1281 0           $d2 = sqrt(($m[$i + 3] - $m[$i + 2])**2 + $b);
1282            
1283             # for each column
1284 0           for my $j (0 .. $ix) {
1285            
1286             # compute difference
1287 0           $dif = $j - $i;
1288            
1289             # skip outer corner regions
1290 0 0         next if (abs($dif) > 2);
1291            
1292             # the @dm array contains (∂m0/∂n, ∂m1/∂n, ∂m2/∂n, ∂m3/∂n)
1293             # where m0 - m3 are the chord slopes, and n is the node value
1294            
1295             # if difference -2
1296 0 0         if ($dif == -2) {
    0          
    0          
    0          
    0          
1297            
1298             # if row 1
1299 0 0         if ($i == $ix) {
1300            
1301             # set partial derivatives
1302 0           @dm = (-1/$x[$i], 0, 1/$x[$i], 2/$x[$i]);
1303            
1304             } else {
1305            
1306             # set partial derivatives
1307 0           @dm = (-1/$x[$i], 0, 0, 0);
1308            
1309             }
1310            
1311             # if difference -1
1312             } elsif ($dif == -1) {
1313            
1314             # if row 1
1315 0 0         if ($i == 1) {
    0          
    0          
1316            
1317             # if more than 3 knots
1318 0 0         if ($ix > 2) {
1319            
1320             # set partial derivatives
1321 0           @dm = (-2/$x[$i + 1], -1/$x[$i + 1], 0, 0);
1322            
1323             } else {
1324            
1325             # set partial derivatives
1326 0           @dm = (-2/$x[$i + 1], -1/$x[$i + 1], 0, 1/$x[$i + 1]);
1327            
1328             }
1329            
1330             # if next to last row
1331             } elsif ($i == $ix - 1) {
1332            
1333             # set partial derivatives
1334 0           @dm = (1/$x[$i], -1/$x[$i + 1], 0, 1/$x[$i + 1]);
1335            
1336             # if last row
1337             } elsif ($i == $ix) {
1338            
1339             # set partial derivatives
1340 0           @dm = (1/$x[$i], -1/$x[$i + 1], -2/$x[$i + 1] - 1/$x[$i], -3/$x[$i + 1] - 2/$x[$i]);
1341            
1342             } else {
1343            
1344             # set partial derivatives
1345 0           @dm = (1/$x[$i], -1/$x[$i + 1], 0, 0);
1346            
1347             }
1348            
1349             # if difference 0
1350             } elsif ($dif == 0) {
1351            
1352             # if row 0
1353 0 0         if ($i == 0) {
    0          
    0          
    0          
1354            
1355             # set partial derivatives
1356 0           @dm = (-3/$x[$i + 2], -2/$x[$i + 2], -1/$x[$i + 2], 0);
1357            
1358             # if row 1
1359             } elsif ($i == 1) {
1360            
1361             # if more than three knots
1362 0 0         if ($ix > 2) {
1363            
1364             # set partial derivatives
1365 0           @dm = (2/$x[$i + 1] + 1/$x[$i + 2], 1/$x[$i + 1], -1/$x[$i + 2], 0);
1366            
1367             } else {
1368            
1369             # set partial derivatives
1370 0           @dm = (2/$x[$i + 1] + 1/$x[$i + 2], 1/$x[$i + 1], -1/$x[$i + 2], -2/$x[$i + 2] - 1/$x[$i + 1]);
1371            
1372             }
1373            
1374             # if next to last row
1375             } elsif ($i == $ix - 1) {
1376            
1377             # set partial derivatives
1378 0           @dm = (0, 1/$x[$i + 1], -1/$x[$i + 2], -2/$x[$i + 2] - 1/$x[$i + 1]);
1379            
1380             # if last row
1381             } elsif ($i == $ix) {
1382            
1383             # set partial derivatives
1384 0           @dm = (0, 1/$x[$i + 1], 2/$x[$i + 1], 3/$x[$i + 1]);
1385            
1386             } else {
1387            
1388             # set partial derivatives
1389 0           @dm = (0, 1/$x[$i + 1], -1/$x[$i + 2], 0);
1390            
1391             }
1392            
1393             # if difference 1
1394             } elsif ($dif == 1) {
1395            
1396             # if row 0
1397 0 0         if ($i == 0) {
    0          
    0          
1398            
1399             # set partial derivatives
1400 0           @dm = (3/$x[$i + 2] + 2/$x[$i + 3], 2/$x[$i + 2] + 1/$x[$i + 3], 1/$x[$i + 2], -1/$x[$i + 3]);
1401            
1402             # if row 1
1403             } elsif ($i == 1) {
1404            
1405             # if more than 3 knots
1406 0 0         if ($ix > 2) {
1407            
1408             # set partial derivatives
1409 0           @dm = (-1/$x[$i + 2], 0, 1/$x[$i + 2], -1/$x[$i + 3]);
1410            
1411             } else {
1412            
1413             # set partial derivatives
1414 0           @dm = (-1/$x[$i + 2], 0, 1/$x[$i + 2], 2/$x[$i + 2]);
1415            
1416             }
1417            
1418             # if next to last row
1419             } elsif ($i == $ix - 1) {
1420            
1421             # set partial derivatives
1422 0           @dm = (0, 0, 1/$x[$i + 2], 2/$x[$i + 2]);
1423            
1424             } else {
1425            
1426             # set partial derivatives
1427 0           @dm = (0, 0, 1/$x[$i + 2], -1/$x[$i + 3]);
1428            
1429             }
1430            
1431             # if difference 2
1432             } elsif ($dif == 2) {
1433            
1434             # if row 0
1435 0 0         if ($i == 0) {
1436            
1437             # set partial derivatives
1438 0           @dm = (-2/$x[$i + 3], -1/$x[$i + 3], 0, 1/$x[$i + 3]);
1439            
1440             } else {
1441            
1442             # set partial derivatives
1443 0           @dm = (0, 0, 0, 1/$x[$i + 3]);
1444            
1445             }
1446            
1447             }
1448            
1449             # compute ∂d1/∂n
1450 0           $dd1 = akima_dpd($m[$i], $m[$i + 1], $dm[0], $dm[1], $d1);
1451            
1452             # compute ∂d2/∂n
1453 0           $dd2 = akima_dpd($m[$i + 2], $m[$i + 3], $dm[2], $dm[3], $d2);
1454            
1455             # compute ∂s/∂n
1456 0           $derv->[$i][$j] = akima_spd($d1, $d2, $m[$i + 1], $m[$i + 2], $dd1, $dd2, $dm[1], $dm[2]);
1457            
1458             }
1459            
1460             }
1461            
1462             }
1463              
1464             # set object
1465 0           $self->[5] = bless($derv, 'Math::Matrix');
1466              
1467             }
1468              
1469             # compute partial derivative function for _akima_para
1470             # parameters: (d1, d2, m1, m2, ∂d1/∂n, ∂d2/∂n, ∂m1/∂n, ∂m2/∂n)
1471             # returns; (∂s/∂n)
1472             sub akima_spd {
1473              
1474             =pod
1475              
1476             https://www.wolframalpha.com/input/?i=d%2Fdx+(a(x)+*+c(x)+%2B+b(x)+*+d(x))%2F(a(x)+%2B+b(x))
1477              
1478             d/dx((a(x) c(x) + b(x) d(x))/(a(x) + b(x))) =
1479              
1480             (c(x) a'(x) + a(x) c'(x) + d(x) b'(x) + b(x) d'(x))/(a(x) + b(x)) - ((a'(x) + b'(x)) (a(x) c(x) + b(x) d(x)))/(a(x) + b(x))^2
1481              
1482             =cut
1483              
1484             # return partial derivative
1485 0     0 0   return(($_[2] * $_[5] + $_[1] * $_[6] + $_[3] * $_[4] + $_[0] * $_[7])/($_[1] + $_[0]) - (($_[5] + $_[4]) * ($_[1] * $_[2] + $_[0] * $_[3]))/($_[1] + $_[0])**2);
1486              
1487             }
1488              
1489             # compute partial derivative function for _akima_para
1490             # parameters: (m1, m2, ∂m1/∂n, ∂m2/∂n, d)
1491             # returns; (∂d/∂n)
1492             sub akima_dpd {
1493              
1494             =pod
1495              
1496             https://www.wolframalpha.com/input/?i=d%2Fdx+(sqrt((a(x)+-+b(x))%5E2+%2B+c))
1497              
1498             d/dx(sqrt((a(x) - b(x))^2 + c)) =
1499              
1500             ((a(x) - b(x)) (a'(x) - b'(x)))/sqrt((a(x) - b(x))^2 + c)
1501              
1502             =cut
1503              
1504             # return partial derivative
1505 0     0 0   return(($_[0] - $_[1]) * ($_[2] - $_[3])/$_[4]);
1506              
1507             }
1508              
1509             # add local min/max values
1510             # parameter: (ref_to_object)
1511             # returns: (s-value_array)
1512             sub _minmax {
1513              
1514             # get object reference
1515 0     0     my $self = shift();
1516              
1517             # local variables
1518 0           my (@t, @y, @s);
1519              
1520             # for each interval
1521 0           for my $i (0 .. $#{$self->[2]} - 1) {
  0            
1522            
1523             # get min/max location(s)
1524 0           @t = _local($self, $i);
1525            
1526             # add min/max s-values
1527 0           push(@s, map {$_ + $i} @t);
  0            
1528            
1529             # add inner knot s-value, if derivative is 0
1530 0 0 0       push(@s, $i) if ($i && $self->[3][$i] == 0);
1531            
1532             # compute local min/max y-values
1533 0           @y = map {_fwd($self, $_, $i)} @t;
  0            
1534            
1535             # add the knot values
1536 0           push(@y, ($self->[2][$i], $self->[2][$i + 1]));
1537            
1538             # sort the values
1539 0           @y = sort {$a <=> $b} @y;
  0            
1540            
1541             # save y-value min/max span
1542 0           $self->[4][$i] = [$y[0], $y[-1]];
1543            
1544             }
1545              
1546             # return min/max s-values
1547 0           return(sort {$a <=> $b} @s);
  0            
1548              
1549             }
1550              
1551             # compute local min/max value(s)
1552             # values are within the segment (t)
1553             # parameters: (ref_to_object, segment)
1554             # returns: (t-value_array)
1555             sub _local {
1556              
1557             # get parameters
1558 0     0     my ($self, $low) = @_;
1559              
1560             # local variables
1561 0           my ($dx, $y1, $y2, $m1, $m2);
1562 0           my ($a, $b, $c, $dscr, @t);
1563              
1564             # compute delta x-value (x-values : range)
1565 0 0         $dx = ($#{$self->[1]} > 1) ? ($self->[1][$low + 1] - $self->[1][$low]) : ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
  0            
1566              
1567             # get endpoint values
1568 0           $y1 = $self->[2][$low];
1569 0           $y2 = $self->[2][$low + 1];
1570 0           $m1 = $self->[3][$low] * $dx;
1571 0           $m2 = $self->[3][$low + 1] * $dx;
1572              
1573             # compute coefficients of quadratic equation (at^2 + bt + c = 0)
1574 0           $a = 6 * ($y1 - $y2) + 3 * ($m1 + $m2);
1575 0           $b = -6 * ($y1 - $y2) - 2 * $m2 - 4 * $m1;
1576 0           $c = $m1;
1577              
1578             # return if constant
1579 0 0 0       return() if (abs($a) < 1E-15 && abs($b) < 1E-15);
1580              
1581             # if linear equation (a is zero)
1582 0 0         if (abs($a) < 1E-15) {
1583            
1584             # add solution
1585 0           push(@t, -$c/$b);
1586            
1587             # if quadratic equation
1588             } else {
1589            
1590             # compute discriminant
1591 0           $dscr = $b**2 - 4 * $a * $c;
1592            
1593             # if discriminant > zero
1594 0 0         if ($dscr > 0) {
1595            
1596             # add solutions (critical points)
1597 0           push(@t, -($b + sqrt($dscr))/(2 * $a));
1598 0           push(@t, -($b - sqrt($dscr))/(2 * $a));
1599            
1600             }
1601            
1602             }
1603              
1604             # return solution(s) within interval (0 < t < 0)
1605 0 0         return (grep {$_ > 0 && $_ < 1} @t);
  0            
1606              
1607             }
1608              
1609             # compute second derivative for unit spline segment
1610             # parameters: (ref_to_object, t-value, segment)
1611             # returns: (second_derivative)
1612             sub _derv2 {
1613              
1614             # get parameters
1615 0     0     my ($self, $t, $low) = @_;
1616              
1617             # local variables
1618 0           my ($dx, $h00, $h01, $h10, $h11);
1619              
1620             # compute delta x-value (x-values : range)
1621 0 0         $dx = ($#{$self->[1]} > 1) ? ($self->[1][$low + 1] - $self->[1][$low]) : ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
  0            
1622              
1623             # compute Hermite derivative coefficients
1624 0           $h00 = 12 * $t - 6;
1625 0           $h01 = - $h00;
1626 0           $h10 = 6 * $t - 4;
1627 0           $h11 = 6 * $t - 2;
1628              
1629             # interpolate value
1630 0           return(($h00 * $self->[2][$low]/$dx + $h01 * $self->[2][$low + 1]/$dx + $h10 * $self->[3][$low] + $h11 * $self->[3][$low + 1])/$dx);
1631              
1632             }
1633              
1634             # compute derivative for unit spline segment
1635             # parameters: (ref_to_object, t-value, segment)
1636             # returns: (derivative)
1637             sub _derv {
1638              
1639             # get parameters
1640 0     0     my ($self, $t, $low) = @_;
1641              
1642             # local variables
1643 0           my ($dx, $tc, $ttc, $h00, $h01, $h10, $h11);
1644              
1645             # compute delta x-value (x-values : range)
1646 0 0         $dx = ($#{$self->[1]} > 1) ? ($self->[1][$low + 1] - $self->[1][$low]) : ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
  0            
1647              
1648             # if position is non-zero
1649 0 0         if ($t) {
1650            
1651             # compute Hermite derivative coefficients
1652 0           $tc = (1 - $t);
1653 0           $ttc = -3 * $t * $tc;
1654 0           $h00 = 2 * $ttc;
1655 0           $h01 = -2 * $ttc;
1656 0           $h10 = $ttc + $tc;
1657 0           $h11 = $ttc + $t;
1658            
1659             # interpolate value
1660 0           return($h00 * $self->[2][$low]/$dx + $h01 * $self->[2][$low + 1]/$dx + $h10 * $self->[3][$low] + $h11 * $self->[3][$low + 1]);
1661            
1662             } else {
1663            
1664             # use lower knot derivative value
1665 0           return($self->[3][$low]);
1666            
1667             }
1668            
1669             }
1670              
1671             # compute transform value
1672             # parameters: (ref_to_object, t-value, segment)
1673             # returns: (y-value)
1674             sub _fwd {
1675              
1676             # get parameters
1677 0     0     my ($self, $t, $low) = @_;
1678              
1679             # local variables
1680 0           my ($dx, $tc, $h00, $h01, $h10, $h11);
1681              
1682             # check if ICC::Support::Lapack module is loaded
1683 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
1684              
1685             # if position is non-zero
1686 0 0         if ($t) {
1687            
1688             # compute delta x-value (x-values : range)
1689 0 0         $dx = ($#{$self->[1]} > 1) ? ($self->[1][$low + 1] - $self->[1][$low]) : ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
  0            
1690            
1691             # interpolate value (Lapack XS)
1692             # if ICC::Support::Lapack module is loaded
1693 0 0         if ($lapack) {
1694            
1695 0           return(ICC::Support::Lapack::hermite($t, $self->[2][$low], $self->[2][$low + 1], $dx * $self->[3][$low], $dx * $self->[3][$low + 1]));
1696            
1697             } else {
1698            
1699             # compute Hermite coefficients
1700 0           $tc = 1 - $t;
1701 0           $h00 = (1 + 2 * $t) * $tc * $tc;
1702 0           $h01 = 1 - $h00;
1703 0           $h10 = $t * $tc * $tc;
1704 0           $h11 = -$t * $t * $tc;
1705            
1706             # interpolate value (no Lapack XS)
1707 0           return($h00 * $self->[2][$low] + $h01 * $self->[2][$low + 1] + $h10 * $dx * $self->[3][$low] + $h11 * $dx * $self->[3][$low + 1]);
1708            
1709             }
1710            
1711             } else {
1712            
1713             # use lower knot output value
1714 0           return($self->[2][$low]);
1715            
1716             }
1717            
1718             }
1719              
1720             # compute inverse value
1721             # parameters: (ref_to_object, y-value, segment)
1722             # there are 0 to 3 solutions in the range 0 < t < 1
1723             # returns: (t-value_array)
1724             sub _rev {
1725              
1726             # get parameters
1727 0     0     my ($self, $in, $low) = @_;
1728              
1729             # local variables
1730 0           my ($dx, $y1, $y2, $m1, $m2);
1731 0           my ($A, $B, $C, $D, $dscr, @t);
1732 0           my ($d0, $d1, $cs, $cc, @r, $ccr, $sol, $lim0, $lim1);
1733              
1734             # compute delta x-value (x-values : range)
1735 0 0         $dx = ($#{$self->[1]} > 1) ? ($self->[1][$low + 1] - $self->[1][$low]) : ($self->[1][1] - $self->[1][0])/$#{$self->[2]};
  0            
  0            
1736              
1737             # get endpoint values
1738 0           $y1 = $self->[2][$low];
1739 0           $y2 = $self->[2][$low + 1];
1740 0           $m1 = $self->[3][$low] * $dx;
1741 0           $m2 = $self->[3][$low + 1] * $dx;
1742              
1743             # compute coefficients of cubic equation (At^3 + Bt^2 + Ct + D = 0)
1744 0           $A = 2 * ($y1 - $y2) + $m1 + $m2;
1745 0           $B = -3 * ($y1 - $y2) -2 * $m1 - $m2;
1746 0           $C = $m1;
1747 0           $D = $y1 - $in;
1748              
1749             # constant equation (A, B, C are zero)
1750 0 0 0       if (abs($A) < 5E-15 && abs($B) < 5E-15 && abs($C) < 5E-15) {
    0 0        
    0 0        
1751            
1752             # add solution
1753 0 0         push(@t, 0.5) if ($D == 0);
1754            
1755             # linear equation (A, B are zero)
1756             } elsif (abs($A) < 5E-15 && abs($B) < 5E-15) {
1757            
1758             # add solution
1759 0           push(@t, -$D/$C);
1760            
1761             # quadratic equation (A is zero)
1762             } elsif (abs($A) < 5E-15) {
1763            
1764             # compute discriminant: > 0 two real, == 0 one real, < 0 two complex
1765 0           $dscr = $C**2 - 4 * $B * $D;
1766            
1767             # if discriminant is zero
1768 0 0         if (abs($dscr) < 5E-15) {
    0          
1769            
1770             # add solution (double root)
1771 0           push(@t, -$C/(2 * $B));
1772            
1773             # if discriminant > zero
1774             } elsif ($dscr > 0) {
1775            
1776             # add solutions
1777 0           push(@t, -($C + sqrt($dscr))/(2 * $B));
1778 0           push(@t, -($C - sqrt($dscr))/(2 * $B));
1779            
1780             }
1781            
1782             # cubic equation
1783             } else {
1784            
1785             # compute discriminant: > 0 three real, == 0 one or two real, < 0 one real and two complex
1786 0           $dscr = 18 * $A * $B * $C * $D - 4 * $B**3 * $D + $B**2 * $C**2 - 4 * $A * $C**3 - 27 * $A**2 * $D**2;
1787            
1788             # compute ∆0
1789 0           $d0 = $B**2 - 3 * $A * $C;
1790            
1791             # if discriminant is zero
1792 0 0         if (abs($dscr) < 5E-15) {
1793            
1794             # if ∆0 is zero
1795 0 0         if (abs($d0) < 5E-15) {
1796            
1797             # add solution (triple root)
1798 0           push(@t, -$B/(3 * $A));
1799            
1800             } else {
1801            
1802             # add solutions (double root and single root)
1803 0           push(@t, (9 * $A * $D - $B * $C)/(2 * $d0));
1804 0           push(@t, (4 * $A * $B * $C - 9 * $A**2 * $D - $B**3)/($A * $d0));
1805            
1806             }
1807            
1808             } else {
1809            
1810             # compute ∆1
1811 0           $d1 = 2 * $B**3 - 9 * $A * $B * $C + 27 * $A**2 * $D;
1812            
1813             # compute C (a complex number)
1814 0           $cs = sqrt($d1**2 - 4 * $d0**3);
1815 0 0         $cc = cbrt($d1 == $cs ? ($d1 + $cs)/2 : ($d1 - $cs)/2);
1816            
1817             # compute cube roots of 1 (three complex numbers)
1818 0           @r = root(1, 3);
1819            
1820             # for each root
1821 0           for my $i (0 .. 2) {
1822            
1823             # multiply by cube root of 1
1824 0           $ccr = $r[$i] * $cc;
1825            
1826             # compute solution (a complex number)
1827 0           $sol = ($B + $ccr + $d0/$ccr)/(-3 * $A);
1828            
1829             # add solution, if real
1830 0 0 0       push(@t, Re($sol)) if ($dscr > 0 || abs(Im($sol)) < 1E-15);
1831            
1832             }
1833            
1834             }
1835            
1836             }
1837              
1838             # set test limits to avoid duplicates at knots
1839 0 0         $lim0 = ($in == $y1) ? 1E-14 : 0;
1840 0 0         $lim1 = ($in == $y2) ? (1 - 1E-14) : 1;
1841              
1842             # return valid solutions
1843 0 0         return(sort {$a <=> $b} grep {$_ > $lim0 && $_ < $lim1} @t);
  0            
  0            
1844              
1845             }
1846              
1847             # test vector values
1848             # returns true if values are unique
1849             # parameter: (vector)
1850             # returns: (flag)
1851             sub _unique {
1852              
1853             # get parameter
1854 0     0     my $vec = shift();
1855              
1856             # local variable
1857 0           my (%x);
1858              
1859             # make count hash
1860 0           for (@{$vec}) {$x{$_}++}
  0            
  0            
1861              
1862             # return
1863 0           return(@{$vec} == keys(%x));
  0            
1864              
1865             }
1866              
1867             # sort object input/output values
1868             # parameter: (ref_to_object)
1869             sub _sort_values {
1870              
1871             # get parameter
1872 0     0     my $self = shift();
1873              
1874             # local variable
1875 0           my (@p);
1876              
1877             # if input contains x-values
1878 0 0         if ($#{$self->[1]} > 1) {
  0            
1879            
1880             # warn if vectors not same length
1881 0 0         ($#{$self->[1]} == $#{$self->[2]}) or carp('input and output vectors not same length');
  0            
  0            
1882            
1883             # for each point
1884 0           for my $i (0 .. $#{$self->[1]}) {
  0            
1885            
1886             # copy x-y values
1887 0           $p[$i] = [$self->[1][$i], $self->[2][$i]];
1888            
1889             }
1890            
1891             # sort points by input value
1892 0           @p = sort {$a->[0] <=> $b->[0]} @p;
  0            
1893            
1894             # for each point
1895 0           for my $i (0 .. $#{$self->[1]}) {
  0            
1896            
1897             # copy x-y values
1898 0           $self->[1][$i] = $p[$i]->[0];
1899 0           $self->[2][$i] = $p[$i]->[1];
1900            
1901             }
1902            
1903             } else {
1904            
1905             # if x-range is decreasing
1906 0 0         if ($self->[1][0] > $self->[1][-1]) {
1907            
1908             # reverse x and y vectors
1909 0           @{$self->[1]} = reverse(@{$self->[1]});
  0            
  0            
1910 0           @{$self->[2]} = reverse(@{$self->[2]});
  0            
  0            
1911            
1912             }
1913            
1914             }
1915            
1916             }
1917              
1918             # fix highlight region data
1919             # corrects the print-specific
1920             # problem of highlight dot retention
1921             # samples below the limit (0 - 1) are fixed
1922             # parameter: (ref_to_object, limit)
1923             sub _fix_hl {
1924              
1925             # get parameter
1926 0     0     my ($self, $limit) = @_;
1927              
1928             # local variable
1929 0           my ($span, $lo, $hi, $n, @ix, @xr, @x, @y, $bern, @new);
1930              
1931             # compute 'output' range
1932 0           $span = ($self->[2][-1] - $self->[2][0]);
1933              
1934             # set 'output' limits
1935 0           $lo = $self->[2][0] + 0.002 * $span;
1936 0           $hi = $self->[2][0] + 2 * $limit * $span;
1937              
1938             # get upper index of 'output' array
1939 0           $n = $#{$self->[2]};
  0            
1940              
1941             # find samples within limits
1942 0 0         @ix = grep {$self->[2][$_] >= $lo && $self->[2][$_] <= $hi} (1 .. $n);
  0            
1943              
1944             # use at least 4 samples
1945 0 0         @ix = ($ix[0] .. $ix[0] + 3) if (@ix < 4);
1946              
1947             # if input is a range
1948 0 0         if (@{$self->[1]} == 2) {
  0            
1949            
1950             # interpolate x-values
1951 0           @xr = map {(1 - $_/$n) * $self->[1][0] + ($_/$n) * $self->[1][1]} (0 .. $n);
  0            
1952            
1953             # get 'fit' x-values
1954 0           @x = @xr[@ix];
1955            
1956             } else {
1957            
1958             # get 'fit' x-values
1959 0           @x = @{$self->[1]}[@ix];
  0            
1960            
1961             }
1962              
1963             # get 'fit' y-values
1964 0           @y = @{$self->[2]}[@ix];
  0            
1965              
1966             # make 'bern' object of degree 3, fitted to x-y values (requires Lapack module)
1967 0           $bern = ICC::Support::bern->new({'input' => [$x[0], $x[-1]], 'output' => [(undef) x 4], 'fit' => [\@x, \@y]});
1968              
1969             # set 'output' limit
1970 0           $hi = $self->[2][0] + $limit * $span;
1971              
1972             # for each sample
1973 0           for my $i (reverse(0 .. $n)) {
1974            
1975             # if 'output' value ≤ limit
1976 0 0 0       if (@new || $self->[2][$i] <= $hi) {
1977            
1978             # compute new 'output' value using 'bern' curve
1979 0 0         push(@new, $bern->transform(@{$self->[1]} > 2 ? $self->[1][$i] : $xr[$i]));
  0            
1980            
1981             }
1982            
1983             }
1984              
1985             # splice new values, saving originals in hash
1986 0           $self->[0]{'hl_original'} = [splice(@{$self->[2]}, 0, @new, reverse(@new))];
  0            
1987              
1988             # save x-axis intercept (highlight dot retention value)
1989 0           $self->[0]{'hl_retention'} = $bern->inverse(0);
1990              
1991             # compute knot derivatives
1992 0           _objderv($self);
1993              
1994             # add segment min/max y-values
1995 0           _minmax($self);
1996              
1997             }
1998              
1999             # fix shadow region data
2000             # corrects the print-specific problem of
2001             # non-monotonic data is the shadow region
2002             # samples above the limit (0 - 1) are fixed
2003             # parameter: (ref_to_object, limit)
2004             sub _fix_sh {
2005              
2006             # get parameter
2007 0     0     my ($self, $limit) = @_;
2008              
2009             # local variable
2010 0           my ($span, $lo, $n, @ix, @xr, @x, @y, $bern, @new);
2011              
2012             # compute 'output' range
2013 0           $span = ($self->[2][-1] - $self->[2][0]);
2014              
2015             # set 'output' limit
2016 0           $lo = $self->[2][-1] - 2 * (1 - $limit) * $span;
2017              
2018             # get upper index of 'output' array
2019 0           $n = $#{$self->[2]};
  0            
2020              
2021             # find samples with 'output' > limit
2022 0           @ix = grep {$self->[2][$_] >= $lo} (0 .. $n);
  0            
2023              
2024             # use at least 4 samples
2025 0 0         @ix = (($ix[-1] - 3) .. $ix[-1]) if (@ix < 4);
2026              
2027             # if input is a range
2028 0 0         if (@{$self->[1]} == 2) {
  0            
2029            
2030             # interpolate x-values
2031 0           @xr = map {(1 - $_/$n) * $self->[1][0] + ($_/$n) * $self->[1][1]} (0 .. $n);
  0            
2032            
2033             # get 'fit' x-values
2034 0           @x = @xr[@ix];
2035            
2036             } else {
2037            
2038             # get 'fit' x-values
2039 0           @x = @{$self->[1]}[@ix];
  0            
2040            
2041             }
2042              
2043             # get 'fit' y-values
2044 0           @y = @{$self->[2]}[@ix];
  0            
2045              
2046             # make 'bern' object of degree 3, fitted to x-y values (requires Lapack module)
2047 0           $bern = ICC::Support::bern->new({'input' => [$x[0], $x[-1]], 'output' => [(undef) x 4], 'fit' => [\@x, \@y]});
2048              
2049             # set 'output' limit
2050 0           $lo = $self->[2][0] + $limit * $span;
2051              
2052             # for each sample
2053 0           for my $i (0 .. $n) {
2054            
2055             # if 'output' value ≥ limit
2056 0 0 0       if (@new || $self->[2][$i] >= $lo) {
2057            
2058             # compute new 'output' value using 'bern' curve
2059 0 0         push(@new, $bern->transform(@{$self->[1]} > 2 ? $self->[1][$i] : $xr[$i]));
  0            
2060            
2061             }
2062            
2063             }
2064              
2065             # splice new values, saving originals in hash
2066 0           $self->[0]{'sh_original'} = [splice(@{$self->[2]}, -@new, @new, @new)];
  0            
2067              
2068             # compute knot derivatives
2069 0           _objderv($self);
2070              
2071             # add segment min/max y-values
2072 0           _minmax($self);
2073              
2074             }
2075              
2076             # set object contents from parameter hash
2077             # parameters: (ref_to_object, ref_to_parameter_hash)
2078             sub _new_from_hash {
2079              
2080             # get parameters
2081 0     0     my ($self, $hash) = @_;
2082              
2083             # local variables
2084 0           my ($in, $out, $type, $damp, $fix, $limit, $pok, $fok);
2085              
2086             # if 'input' is defined
2087 0 0         if (defined($in = $hash->{'input'})) {
2088            
2089             # if 'input' is a vector (2 or more values)
2090 0 0 0       if (ICC::Shared::is_num_vector($in) && 2 <= @{$in}) {
  0 0 0        
      0        
2091            
2092             # set object 'input' array
2093 0           $self->[1] = [@{$in}];
  0            
2094            
2095             # if 'input' is an integer > 0 (2 or more values)
2096             } elsif (Scalar::Util::looks_like_number($in) && $in == int($in) && $in > 0) {
2097            
2098             # make identity curve
2099 0           $self->[1] = [map {$_/$in} (0 .. $in)];
  0            
2100            
2101             } else {
2102            
2103             # error
2104 0           croak("invalid 'input' values");
2105            
2106             }
2107            
2108             } else {
2109            
2110             # use default
2111 0           $self->[1] = [0, 1];
2112            
2113             }
2114              
2115             # if 'output' is defined
2116 0 0         if (defined($out = $hash->{'output'})) {
2117            
2118             # if 'output' is a vector (2 or more values)
2119 0 0 0       if (ICC::Shared::is_num_vector($out) && 2 <= @{$out}) {
  0 0 0        
      0        
2120            
2121             # set object 'output' array
2122 0           $self->[2] = [@{$out}];
  0            
2123            
2124             # if 'output' is an integer > 0 (2 or more values)
2125             } elsif (Scalar::Util::looks_like_number($out) && $out == int($out) && $out > 0) {
2126            
2127             # make identity curve
2128 0           $self->[2] = [map {$_/$out} (0 .. $out)];
  0            
2129            
2130             } else {
2131            
2132             # error
2133 0           croak("invalid 'output' values");
2134            
2135             }
2136            
2137             } else {
2138            
2139             # use default
2140 0           $self->[2] = [0, 1];
2141            
2142             }
2143              
2144             # if 'type' is defined
2145 0 0         if (defined($type = $hash->{'type'})) {
2146            
2147             # verify supported type
2148 0 0         ($type =~ m/^(natural|akima)$/) or croak('unsupported spline type');
2149            
2150             # set object type in hash
2151 0           $self->[0]{'type'} = $type;
2152            
2153             }
2154              
2155             # if 'damp' is defined
2156 0 0         if (defined($damp = $hash->{'damp'})) {
2157            
2158             # verify supported type
2159 0 0 0       (Scalar::Util::looks_like_number($damp) && $damp >= 0) or croak('invalid akima damping value');
2160            
2161             # set object type in hash
2162 0           $self->[0]{'damp'} = $damp;
2163            
2164             }
2165              
2166             # verify number of input values
2167 0 0 0       (@{$self->[1]} == 2 || @{$self->[1]} == @{$self->[2]}) or croak('wrong number of input values');
  0            
  0            
  0            
2168              
2169             # sort input/output values
2170 0           _sort_values($self);
2171              
2172             # compute knot derivatives
2173 0           _objderv($self);
2174              
2175             # add segment min/max y-values
2176 0           _minmax($self);
2177              
2178             # if 'fix_hl' is defined
2179 0 0         if (defined($fix = $hash->{'fix_hl'})) {
2180            
2181             # verify polarity
2182 0 0         ($pok = $self->[2][-1] > $self->[2][0]) or carp("'fix_hl' requires increasing output values");
2183            
2184             # verify fix limit (-1 to 1)
2185 0 0 0       ($fok = (Scalar::Util::looks_like_number($fix) && $fix >= -1 && $fix <= 1)) or carp("invalid 'fix_hl' limit");
2186            
2187             # compute 'output' limit
2188 0           $limit = $self->[2][0] * (1 - $fix) + $self->[2][-1] * $fix;
2189            
2190             # fix the data if polarity and limit are OK, and spline is non-monotonic in highlights
2191 0 0 0       _fix_hl($self, abs($fix)) if ($pok && $fok && ($fix < 0 || grep {$_ <= $limit} monotonic($self, 'output')));
      0        
      0        
2192            
2193             }
2194              
2195             # if 'fix_sh' is defined
2196 0 0         if (defined($fix = $hash->{'fix_sh'})) {
2197            
2198             # verify polarity
2199 0 0         ($pok = $self->[2][-1] > $self->[2][0]) or carp("'fix_sh' requires increasing output values");
2200            
2201             # verify fix limit (-1 to 1)
2202 0 0 0       ($fok = (Scalar::Util::looks_like_number($fix) && $fix >= -1 && $fix <= 1)) or carp("invalid 'fix_sh' limit");
2203            
2204             # compute 'output' limit
2205 0           $limit = $self->[2][0] * (1 - $fix) + $self->[2][-1] * $fix;
2206            
2207             # fix the data if polarity and limit are OK, and spline is non-monotonic in shadows
2208 0 0 0       _fix_sh($self, abs($fix)) if ($pok && $fok && ($fix < 0 || grep {$_ >= $limit} monotonic($self, 'output')));
      0        
      0        
2209            
2210             }
2211            
2212             }
2213              
2214             # write spline object to ICC profile
2215             # note: writes an equivalent curv object
2216             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
2217             sub _writeICCspline {
2218              
2219             # get parameters
2220 0     0     my ($self, $parent, $fh, $tag) = @_;
2221              
2222             # local variables
2223 0           my ($n, $up, @table);
2224              
2225             # get count value (defaults to 4096, the maximum allowed in an 'mft2' tag)
2226 0   0       $n = $self->[0]{'curv_points'} // 4096;
2227              
2228             # validate number of table entries
2229 0 0 0       ($n == int($n) && $n >= 2) or carp('invalid number of table entries');
2230              
2231             # array upper index
2232 0           $up = $n - 1;
2233              
2234             # for each table entry
2235 0           for my $i (0 .. $up) {
2236            
2237             # transform value
2238 0           $table[$i] = transform($self, $i/$up);
2239            
2240             }
2241              
2242             # seek start of tag
2243 0           seek($fh, $tag->[1], 0);
2244              
2245             # write tag type signature and count
2246 0           print $fh pack('a4 x4 N', 'curv', $n);
2247              
2248             # write array
2249 0 0         print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @table);
  0 0          
2250              
2251             }
2252              
2253             1;
2254