File Coverage

lib/ICC/Support/spline.pm
Criterion Covered Total %
statement 22 690 3.1
branch 2 348 0.5
condition 1 175 0.5
subroutine 7 45 15.5
pod 15 19 78.9
total 47 1277 3.6


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