| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package ICC::Support::bern; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 78649 | use strict; | 
|  | 2 |  |  |  |  | 9 |  | 
|  | 2 |  |  |  |  | 47 |  | 
| 4 | 2 |  |  | 2 |  | 10 | use Carp; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 117 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = 0.41; | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | # revised 2018-09-04 | 
| 9 |  |  |  |  |  |  | # | 
| 10 |  |  |  |  |  |  | # Copyright © 2004-2020 by William B. Birkett | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | # inherit from Shared | 
| 13 | 2 |  |  | 2 |  | 360 | use parent qw(ICC::Shared); | 
|  | 2 |  |  |  |  | 250 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | # enable static variables | 
| 16 | 2 |  |  | 2 |  | 95 | use feature 'state'; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 9925 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | # create new bern object | 
| 19 |  |  |  |  |  |  | # hash keys are: 'input', 'output', 'fit', 'fix_hl', 'fix_sh' | 
| 20 |  |  |  |  |  |  | # default values for 'input' and 'output' are [] | 
| 21 |  |  |  |  |  |  | # parameters: ([ref_to_attribute_hash]) | 
| 22 |  |  |  |  |  |  | # returns: (ref_to_object) | 
| 23 |  |  |  |  |  |  | sub new { | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | # get object class | 
| 26 | 1 |  |  | 1 | 1 | 851 | my $class = shift(); | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # create empty bern object | 
| 29 | 1 |  |  |  |  | 4 | my $self = [ | 
| 30 |  |  |  |  |  |  | {},	# object header | 
| 31 |  |  |  |  |  |  | [],	# parameter array | 
| 32 |  |  |  |  |  |  | [],	# min/max t-values | 
| 33 |  |  |  |  |  |  | []	# min/max i/o-values | 
| 34 |  |  |  |  |  |  | ]; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | # if one parameter, a hash reference | 
| 37 | 1 | 50 | 33 |  |  | 7 | if (@_ == 1 && ref($_[0]) eq 'HASH') { | 
|  |  | 50 |  |  |  |  |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | # set object contents from hash | 
| 40 | 0 |  |  |  |  | 0 | _new_from_hash($self, $_[0]); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | } elsif (@_) { | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | # error | 
| 45 | 0 |  |  |  |  | 0 | croak('invalid parameter(s)'); | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | # return blessed object | 
| 50 | 1 |  |  |  |  | 3 | return(bless($self, $class)); | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | # create inverse object | 
| 55 |  |  |  |  |  |  | # swaps the 'input' and 'output' arrays | 
| 56 |  |  |  |  |  |  | # returns: (ref_to_object) | 
| 57 |  |  |  |  |  |  | sub inv { | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | # clone new object | 
| 60 | 0 |  |  | 0 | 1 |  | my $self = Storable::dclone(shift()); | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | # swap 'input' and 'output' arrays | 
| 63 | 0 |  |  |  |  |  | ($self->[1][0], $self->[1][1]) = ($self->[1][1], $self->[1][0]); | 
| 64 | 0 |  |  |  |  |  | ($self->[2][0], $self->[2][1]) = ($self->[2][1], $self->[2][0]); | 
| 65 | 0 |  |  |  |  |  | ($self->[3][0], $self->[3][1]) = ($self->[3][1], $self->[3][0]); | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | # return inverted object | 
| 68 | 0 |  |  |  |  |  | return($self); | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # write bern object to ICC profile | 
| 73 |  |  |  |  |  |  | # note: writes an equivalent 'curv' object | 
| 74 |  |  |  |  |  |  | # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry) | 
| 75 |  |  |  |  |  |  | sub write_fh { | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # verify 4 parameters | 
| 78 | 0 | 0 |  | 0 | 0 |  | (@_ == 4) or croak('wrong number of parameters'); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | # write bern data to profile | 
| 81 | 0 |  |  |  |  |  | goto &_writeICCbern; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # get tag size (for writing to profile) | 
| 86 |  |  |  |  |  |  | # note: writes an equivalent curv object | 
| 87 |  |  |  |  |  |  | # returns: (tag_size) | 
| 88 |  |  |  |  |  |  | sub size { | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # get parameters | 
| 91 | 0 |  |  | 0 | 0 |  | my ($self) = @_; | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | # get count value (defaults to 4370) | 
| 94 | 0 |  | 0 |  |  |  | my $n = $self->[0]{'curv_points'} // 4370; | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | # return size | 
| 97 | 0 |  |  |  |  |  | return(12 + $n * 2); | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | # compute curve function | 
| 102 |  |  |  |  |  |  | # parameters: (input_value) | 
| 103 |  |  |  |  |  |  | # returns: (output_value) | 
| 104 |  |  |  |  |  |  | sub transform { | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | # get parameters | 
| 107 | 0 |  |  | 0 | 1 |  | my ($self, $in) = @_; | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | # verify input | 
| 110 | 0 | 0 |  |  |  |  | (Scalar::Util::looks_like_number($in)) or croak('invalid transform input'); | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | # compute intermediate value(s) | 
| 113 | 0 |  |  |  |  |  | my @t = _rev($self->[1][0], $self->[2][0], $self->[3][0], $in); | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | # warn if multiple values | 
| 116 | 0 | 0 |  |  |  |  | (@t == 1) or carp('multiple transform values'); | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | # return first output value | 
| 119 | 0 |  |  |  |  |  | return(_fwd($self->[1][1], $t[0])); | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | # compute inverse curve function | 
| 124 |  |  |  |  |  |  | # parameters: (input_value) | 
| 125 |  |  |  |  |  |  | # returns: (output_value) | 
| 126 |  |  |  |  |  |  | sub inverse { | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # get parameters | 
| 129 | 0 |  |  | 0 | 1 |  | my ($self, $in) = @_; | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | # verify input | 
| 132 | 0 | 0 |  |  |  |  | (Scalar::Util::looks_like_number($in)) or croak('invalid transform input'); | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | # compute intermediate value(s) | 
| 135 | 0 |  |  |  |  |  | my @t = _rev($self->[1][1], $self->[2][1], $self->[3][1], $in); | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | # warn if multiple values | 
| 138 | 0 | 0 |  |  |  |  | (@t == 1) or carp('multiple transform values'); | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | # return first output value | 
| 141 | 0 |  |  |  |  |  | return(_fwd($self->[1][0], $t[0])); | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | # compute curve derivative | 
| 146 |  |  |  |  |  |  | # parameters: (input_value) | 
| 147 |  |  |  |  |  |  | # returns: (derivative_value) | 
| 148 |  |  |  |  |  |  | sub derivative { | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # get parameters | 
| 151 | 0 |  |  | 0 | 1 |  | my ($self, $in) = @_; | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | # return forward derivative | 
| 154 | 0 |  |  |  |  |  | return(_derivative($self, 0, $in)); | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | # compute parametric partial derivatives | 
| 159 |  |  |  |  |  |  | # parameters: (input_value) | 
| 160 |  |  |  |  |  |  | # returns: (partial_derivative_array) | 
| 161 |  |  |  |  |  |  | sub parametric { | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | # get parameters | 
| 164 | 0 |  |  | 0 | 1 |  | my ($self, $in) = @_; | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | # return forward parametric partial derivatives | 
| 167 | 0 |  |  |  |  |  | return(_parametric($self, 0, $in)); | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | # get/set header hash reference | 
| 172 |  |  |  |  |  |  | # parameters: ([ref_to_new_hash]) | 
| 173 |  |  |  |  |  |  | # returns: (ref_to_hash) | 
| 174 |  |  |  |  |  |  | sub header { | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | # get object reference | 
| 177 | 0 |  |  | 0 | 1 |  | my $self = shift(); | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | # if there are parameters | 
| 180 | 0 | 0 |  |  |  |  | if (@_) { | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | # if one parameter, a hash reference | 
| 183 | 0 | 0 | 0 |  |  |  | if (@_ == 1 && ref($_[0]) eq 'HASH') { | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | # set header to copy of hash | 
| 186 | 0 |  |  |  |  |  | $self->[0] = Storable::dclone(shift()); | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | } else { | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | # error | 
| 191 | 0 |  |  |  |  |  | croak('parameter must be a hash reference'); | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | # return reference | 
| 198 | 0 |  |  |  |  |  | return($self->[0]); | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # get/set input array reference | 
| 203 |  |  |  |  |  |  | # an array of two values is a range | 
| 204 |  |  |  |  |  |  | # otherwise, it contains input values | 
| 205 |  |  |  |  |  |  | # parameters: ([ref_to_array]) | 
| 206 |  |  |  |  |  |  | # returns: (ref_to_array) | 
| 207 |  |  |  |  |  |  | sub input { | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | # get object reference | 
| 210 | 0 |  |  | 0 | 1 |  | my $self = shift(); | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | # if one parameter supplied | 
| 213 | 0 | 0 |  |  |  |  | if (@_ == 1) { | 
|  |  | 0 |  |  |  |  |  | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | # verify 'input' vector (between 1 and 21 numeric values) | 
| 216 | 0 | 0 | 0 |  |  |  | (ICC::Shared::is_num_vector($_[0]) && 1 <= @{$_[0]} && 21 >= @{$_[0]}) or croak('invalid input values'); | 
|  | 0 |  | 0 |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | # store output values | 
| 219 | 0 |  |  |  |  |  | $self->[1][0] = [@{$_[0]}]; | 
|  | 0 |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # check for min/max values | 
| 222 | 0 |  |  |  |  |  | _minmax($self, 0); | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | } elsif (@_) { | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | # error | 
| 227 | 0 |  |  |  |  |  | carp('too many parameters'); | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | # return array reference | 
| 232 | 0 |  |  |  |  |  | return($self->[1][0]); | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # get/set output array reference | 
| 237 |  |  |  |  |  |  | # parameters: ([ref_to_array]) | 
| 238 |  |  |  |  |  |  | # returns: (ref_to_array) | 
| 239 |  |  |  |  |  |  | sub output { | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | # get object reference | 
| 242 | 0 |  |  | 0 | 1 |  | my $self = shift(); | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | # if one parameter supplied | 
| 245 | 0 | 0 |  |  |  |  | if (@_ == 1) { | 
|  |  | 0 |  |  |  |  |  | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | # verify 'output' vector (between 1 and 21 numeric values) | 
| 248 | 0 | 0 | 0 |  |  |  | (ICC::Shared::is_num_vector($_[0]) && 1 <= @{$_[0]} && 21 >= @{$_[0]}) or croak('invalid input values'); | 
|  | 0 |  | 0 |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | # store output values | 
| 251 | 0 |  |  |  |  |  | $self->[1][1] = [@{$_[0]}]; | 
|  | 0 |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | # check for min/max values | 
| 254 | 0 |  |  |  |  |  | _minmax($self, 1); | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | } elsif (@_) { | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | # error | 
| 259 | 0 |  |  |  |  |  | carp('too many parameters'); | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # return array reference | 
| 264 | 0 |  |  |  |  |  | return($self->[1][1]); | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | # normalize transform | 
| 269 |  |  |  |  |  |  | # adjusts Bernstein coefficients linearly | 
| 270 |  |  |  |  |  |  | # adjusts 'input' and 'output' by default | 
| 271 |  |  |  |  |  |  | # hash keys: 'input', 'output' | 
| 272 |  |  |  |  |  |  | # parameter: ([hash_ref]) | 
| 273 |  |  |  |  |  |  | # returns: (ref_to_object) | 
| 274 |  |  |  |  |  |  | sub normalize { | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | # get parameters | 
| 277 | 0 |  |  | 0 | 1 |  | my ($self, $hash) = @_; | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | # local variables | 
| 280 | 0 |  |  |  |  |  | my ($m, $b, $val, $src, $x0, $x1, $y0, $y1, $f0, $f1, @minmax); | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | # set hash key array | 
| 283 | 0 |  |  |  |  |  | my @key = qw(input output); | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | # for ('input', 'output') | 
| 286 | 0 |  |  |  |  |  | for my $i (0, 1) { | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | # undefine slope | 
| 289 | 0 |  |  |  |  |  | $m = undef; | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | # if no parameter (default) | 
| 292 | 0 | 0 |  |  |  |  | if (! defined($hash)) { | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # if array has 2 or more coefficients | 
| 295 | 0 | 0 | 0 |  |  |  | if (@{$self->[1][$i]} > 1 && ($x0 = $self->[1][$i][0]) != ($x1 = $self->[1][$i][-1])) { | 
|  | 0 |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | # compute adjustment values | 
| 298 | 0 |  |  |  |  |  | $m = abs(1/($x0 - $x1)); | 
| 299 | 0 | 0 |  |  |  |  | $b = -$m * ($x0 < $x1 ? $x0 : $x1); | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | # set endpoints | 
| 302 | 0 | 0 |  |  |  |  | ($y0, $y1) = $x0 < $x1 ? (0, 1) : (1, 0); | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | } else { | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | # if hash contains key | 
| 309 | 0 | 0 |  |  |  |  | if (defined($val = $hash->{$key[$i]})) { | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | # if array has 2 or more coefficients | 
| 312 | 0 | 0 | 0 |  |  |  | if (@{$self->[1][$i]} > 1 && $self->[1][$i][0] != $self->[1][$i][-1]) { | 
|  | 0 |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | # if hash value is an array reference | 
| 315 | 0 | 0 |  |  |  |  | if (ref($val) eq 'ARRAY') { | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | # if two parameters | 
| 318 | 0 | 0 |  |  |  |  | if (@{$val} == 2) { | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | # get parameters | 
| 321 | 0 |  |  |  |  |  | ($y0, $y1) = @{$val}; | 
|  | 0 |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | # get endpoints | 
| 324 | 0 |  |  |  |  |  | $x0 = $self->[1][$i][0]; | 
| 325 | 0 |  |  |  |  |  | $x1 = $self->[1][$i][-1]; | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | # if three parameters | 
| 328 | 0 |  |  |  |  |  | } elsif (@{$val} == 3) { | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | # get parameters | 
| 331 | 0 |  |  |  |  |  | ($src, $y0, $y1) = @{$val}; | 
|  | 0 |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | # if source is 'endpoints' | 
| 334 | 0 | 0 | 0 |  |  |  | if (! ref($src) && $src eq 'endpoints') { | 
|  |  | 0 | 0 |  |  |  |  | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | # get endpoints | 
| 337 | 0 |  |  |  |  |  | $x0 = $self->[1][$i][0]; | 
| 338 | 0 |  |  |  |  |  | $x1 = $self->[1][$i][-1]; | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | # if source is 'minmax' | 
| 341 |  |  |  |  |  |  | } elsif (! ref($src) && $src eq 'minmax') { | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | # get all possible min/max values | 
| 344 | 0 |  |  |  |  |  | @minmax = ($self->[1][$i][0], $self->[1][$i][-1], @{$self->[3][$i]}); | 
|  | 0 |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | # get min/max values | 
| 347 | 0 |  |  |  |  |  | $x0 = List::Util::min(@minmax); | 
| 348 | 0 |  |  |  |  |  | $x1 = List::Util::max(@minmax); | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | # if four parameters | 
| 353 | 0 |  |  |  |  |  | } elsif (@{$val} == 4) { | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | # get parameters | 
| 356 | 0 |  |  |  |  |  | ($x0, $x1, $y0, $y1) = @{$val}; | 
|  | 0 |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | } | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | # if x/y values are numeric | 
| 361 | 0 | 0 | 0 |  |  |  | if ((4 == grep {Scalar::Util::looks_like_number($_)} ($x0, $x1, $y0, $y1)) && ($x0 != $x1)) { | 
|  | 0 |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | # compute slope | 
| 364 | 0 |  |  |  |  |  | $m = ($y0 - $y1)/($x0 - $x1); | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | # compute offset | 
| 367 | 0 |  |  |  |  |  | $b = $y0 - $m * $x0; | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | } else { | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | # warning | 
| 372 | 0 |  |  |  |  |  | carp("invalid '$key[$i]' parameter(s)"); | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | } | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | } else { | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | # warning | 
| 381 | 0 |  |  |  |  |  | carp("can't normalize '$key[$i]' coefficients"); | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | # if slope is defined | 
| 390 | 0 | 0 |  |  |  |  | if (defined($m)) { | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | # set endpoint flags | 
| 393 | 0 |  |  |  |  |  | $f0 = $self->[1][$i][0] == $x0; | 
| 394 | 0 |  |  |  |  |  | $f1 = $self->[1][$i][-1] == $x1; | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | # adjust Bernstein coefficients (y = mx + b) | 
| 397 | 0 |  |  |  |  |  | @{$self->[1][$i]} = map {$m * $_ + $b} @{$self->[1][$i]}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | # set endpoints (to ensure exact values) | 
| 400 | 0 | 0 |  |  |  |  | $self->[1][$i][0] = $y0 if ($f0); | 
| 401 | 0 | 0 |  |  |  |  | $self->[1][$i][-1] = $y1 if ($f1); | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | } | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | # re-calculate min/max values | 
| 408 | 0 |  |  |  |  |  | _minmax($self); | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | # return | 
| 411 | 0 |  |  |  |  |  | return($self); | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | } | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | # check if monotonic | 
| 416 |  |  |  |  |  |  | # returns min/max values | 
| 417 |  |  |  |  |  |  | # returns t-values by default | 
| 418 |  |  |  |  |  |  | # returns 'input' values if format is 'input' | 
| 419 |  |  |  |  |  |  | # returns 'output' values if format is 'output' | 
| 420 |  |  |  |  |  |  | # curve is monotonic if no min/max values | 
| 421 |  |  |  |  |  |  | # parameters: ([format]) | 
| 422 |  |  |  |  |  |  | # returns: (array_of_values) | 
| 423 |  |  |  |  |  |  | sub monotonic { | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | # get object reference | 
| 426 | 0 |  |  | 0 | 1 |  | my ($self, $fmt) = @_; | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | # local variables | 
| 429 | 0 |  |  |  |  |  | my (@s); | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | # compute min/max values | 
| 432 | 0 |  |  |  |  |  | _minmax($self); | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | # if format undefined | 
| 435 | 0 | 0 |  |  |  |  | if (! defined($fmt)) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | # return sorted t-values ('input' and 'output') | 
| 438 | 0 |  |  |  |  |  | return(sort {$a <=> $b} (@{$self->[2][0]}, @{$self->[2][1]})); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | # if format 'input' | 
| 441 |  |  |  |  |  |  | } elsif ($fmt eq 'input') { | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | # return input values | 
| 444 | 0 |  |  |  |  |  | return(map {_fwd($self->[1][0], $_)} sort {$a <=> $b} (@{$self->[2][0]}, @{$self->[2][1]})); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | # if format 'output' | 
| 447 |  |  |  |  |  |  | } elsif ($fmt eq 'output') { | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | # return output values | 
| 450 | 0 |  |  |  |  |  | return(map {_fwd($self->[1][1], $_)} sort {$a <=> $b} (@{$self->[2][0]}, @{$self->[2][1]})); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | } else { | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | # error | 
| 455 | 0 |  |  |  |  |  | croak('unsupported format for min/max values'); | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | } | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | # update internal object elements | 
| 462 |  |  |  |  |  |  | # this method used primarily when optimizing | 
| 463 |  |  |  |  |  |  | # call after changing 'input' or 'output' values | 
| 464 |  |  |  |  |  |  | sub update { | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | # recompute min/max values | 
| 467 | 0 |  |  | 0 | 1 |  | goto &_minmax; | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | # make table for profile objects | 
| 472 |  |  |  |  |  |  | # assumes curve domain/range is (0 - 1) | 
| 473 |  |  |  |  |  |  | # parameters: (number_of_table_entries, [direction]) | 
| 474 |  |  |  |  |  |  | # returns: (ref_to_table_array) | 
| 475 |  |  |  |  |  |  | sub table { | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | # get parameters | 
| 478 | 0 |  |  | 0 | 1 |  | my ($self, $n, $dir) = @_; | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | # local variables | 
| 481 | 0 |  |  |  |  |  | my ($up, $table); | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | # validate number of table entries | 
| 484 | 0 | 0 | 0 |  |  |  | ($n == int($n) && $n >= 2) or carp('invalid number of table entries'); | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | # purify direction flag | 
| 487 | 0 | 0 |  |  |  |  | $dir = ($dir) ? 1 : 0; | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | # array upper index | 
| 490 | 0 |  |  |  |  |  | $up = $n - 1; | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | # for each table entry | 
| 493 | 0 |  |  |  |  |  | for my $i (0 .. $up) { | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | # compute table value | 
| 496 | 0 |  |  |  |  |  | $table->[$i] = _transform($self, $dir, $i/$up); | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | # return table reference | 
| 501 | 0 |  |  |  |  |  | return($table); | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | # make equivalent 'curv' object | 
| 506 |  |  |  |  |  |  | # assumes curve domain/range is (0 - 1) | 
| 507 |  |  |  |  |  |  | # parameters: (number_of_table_entries, [direction]) | 
| 508 |  |  |  |  |  |  | # returns: (ref_to_curv_object) | 
| 509 |  |  |  |  |  |  | sub curv { | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | # return 'curv' object reference | 
| 512 | 0 |  |  | 0 | 1 |  | return(ICC::Profile::curv->new(table(@_))); | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | } | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | # print object contents to string | 
| 517 |  |  |  |  |  |  | # format is an array structure | 
| 518 |  |  |  |  |  |  | # parameter: ([format]) | 
| 519 |  |  |  |  |  |  | # returns: (string) | 
| 520 |  |  |  |  |  |  | sub sdump { | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | # get parameters | 
| 523 | 0 |  |  | 0 | 1 |  | my ($self, $p) = @_; | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | # local variables | 
| 526 | 0 |  |  |  |  |  | my ($s, $fmt, $fmt2); | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | # resolve parameter to an array reference | 
| 529 | 0 | 0 |  |  |  |  | $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : []; | 
|  |  | 0 |  |  |  |  |  | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | # get format string | 
| 532 | 0 | 0 | 0 |  |  |  | $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef'; | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | # set string to object ID | 
| 535 | 0 |  |  |  |  |  | $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self); | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | # if input_parameter_array defined | 
| 538 | 0 | 0 |  |  |  |  | if (defined($self->[1][0])) { | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | # set format | 
| 541 | 0 |  |  |  |  |  | $fmt2 = join(', ', ('%.3f') x @{$self->[1][0]}); | 
|  | 0 |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | # append parameter string | 
| 544 | 0 |  |  |  |  |  | $s .= sprintf("  input parameters  [$fmt2]\n", @{$self->[1][0]}); | 
|  | 0 |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | # if output_parameter_array defined | 
| 549 | 0 | 0 |  |  |  |  | if (defined($self->[1][1])) { | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | # set format | 
| 552 | 0 |  |  |  |  |  | $fmt2 = join(', ', ('%.3f') x @{$self->[1][1]}); | 
|  | 0 |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | # append parameter string | 
| 555 | 0 |  |  |  |  |  | $s .= sprintf("  output parameters [$fmt2]\n", @{$self->[1][1]}); | 
|  | 0 |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | } | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | # return | 
| 560 | 0 |  |  |  |  |  | return($s); | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | # find min/max values within the interval (0 - 1) | 
| 565 |  |  |  |  |  |  | # used by '_rev' to determine solution regions | 
| 566 |  |  |  |  |  |  | # called by the 'update' method | 
| 567 |  |  |  |  |  |  | # i/o_index: 0 - input, 1 - output, undef - both | 
| 568 |  |  |  |  |  |  | # parameters: (ref_to_object, [i/o_index]) | 
| 569 |  |  |  |  |  |  | sub _minmax { | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | # get parameters | 
| 572 | 0 |  |  | 0 |  |  | my ($self, $io) = @_; | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | # local variables | 
| 575 | 0 |  |  |  |  |  | my (@s, $t, $d); | 
| 576 | 0 |  |  |  |  |  | my ($par, $fwd, $fwd2, $drv); | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | # for input and/or output curves | 
| 579 | 0 | 0 |  |  |  |  | for my $i (defined($io) ? ($io ? 1 : 0) : (0, 1)) { | 
|  |  | 0 |  |  |  |  |  | 
| 580 |  |  |  |  |  |  |  | 
| 581 |  |  |  |  |  |  | # initialize s-values array | 
| 582 | 0 |  |  |  |  |  | @s = (); | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | # get ref to Bernstein parameters | 
| 585 | 0 |  |  |  |  |  | $par = $self->[1][$i]; | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | # if number of parameters > 2 | 
| 588 | 0 | 0 |  |  |  |  | if (@{$par} > 2) { | 
|  | 0 |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | # compute forward differences | 
| 591 | 0 |  |  |  |  |  | $fwd = [map {$par->[$_] - $par->[$_ - 1]} (1 .. $#{$par})]; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | # if forward differences have different signs (+/-) | 
| 594 | 0 | 0 |  |  |  |  | if (grep {($fwd->[0] < 0) ^ ($fwd->[$_] < 0)} (1 .. $#{$fwd})) { | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | # compute second forward differences | 
| 597 | 0 |  |  |  |  |  | $fwd2 = [map {$fwd->[$_] - $fwd->[$_ - 1]} (1 .. $#{$fwd})]; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | # compute derivative values over range (0 - 1) | 
| 600 | 0 |  |  |  |  |  | $drv = [map {$#{$par} * _fwd($fwd, $_/(4 * $#{$par}))} (0 .. 4 * $#{$par})]; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | # for each pair of derivatives | 
| 603 | 0 |  |  |  |  |  | for my $j (1 .. $#{$drv}) { | 
|  | 0 |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | # if derivatives have different signs (+/-) | 
| 606 | 0 | 0 |  |  |  |  | if (($drv->[$j - 1] < 0) ^ ($drv->[$j] < 0)) { | 
| 607 |  |  |  |  |  |  |  | 
| 608 |  |  |  |  |  |  | # estimate root from the derivative values | 
| 609 | 0 |  |  |  |  |  | $t = ($j * $drv->[$j - 1] - ($j - 1) * $drv->[$j])/(($drv->[$j - 1] - $drv->[$j]) * 4 * $#{$par}); | 
|  | 0 |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  |  | 
| 611 |  |  |  |  |  |  | # loop until derivative approaches 0 | 
| 612 | 0 |  |  |  |  |  | while (abs($d = $#{$par} * _fwd($fwd, $t)) > 1E-9) { | 
|  | 0 |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | # adjust root using Newton's method | 
| 615 | 0 |  |  |  |  |  | $t -= $d/($#{$fwd} * $#{$par} * _fwd($fwd2, $t)); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | # push root on t-value array | 
| 620 | 0 | 0 | 0 |  |  |  | push(@s, $t) if ($t > 0 && $t < 1); | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | } | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | } | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | } | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | # warn if root values were found | 
| 631 | 0 | 0 | 0 |  |  |  | print "curve $i is not monotonic\n" if (@s && ! defined($self->[2][$i])); | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | # save root t-values | 
| 634 | 0 |  |  |  |  |  | $self->[2][$i] = [@s]; | 
| 635 |  |  |  |  |  |  |  | 
| 636 |  |  |  |  |  |  | # save root i/o-values | 
| 637 | 0 |  |  |  |  |  | $self->[3][$i] = [map {_fwd($par, $_)} @s]; | 
|  | 0 |  |  |  |  |  |  | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | } | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | # combined transform | 
| 644 |  |  |  |  |  |  | # nominal domain (0 - 1) | 
| 645 |  |  |  |  |  |  | # parameters: (ref_to_object, direction, input_value) | 
| 646 |  |  |  |  |  |  | # returns: (output_value) | 
| 647 |  |  |  |  |  |  | sub _transform { | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | # get parameters | 
| 650 | 0 |  |  | 0 |  |  | my ($self, $dir, $in) = @_; | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | # local variables | 
| 653 | 0 |  |  |  |  |  | my ($p0, $p1, $p2, $p3, @t); | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | # verify input | 
| 656 | 0 | 0 |  |  |  |  | (Scalar::Util::looks_like_number($in)) or croak('invalid transform input'); | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | # get parameter arrays | 
| 659 | 0 |  |  |  |  |  | $p0 = $self->[1][$dir]; | 
| 660 | 0 |  |  |  |  |  | $p1 = $self->[2][$dir]; | 
| 661 | 0 |  |  |  |  |  | $p2 = $self->[3][$dir]; | 
| 662 | 0 |  |  |  |  |  | $p3 = $self->[1][1 - $dir]; | 
| 663 |  |  |  |  |  |  |  | 
| 664 |  |  |  |  |  |  | # compute intermediate value(s) | 
| 665 | 0 |  |  |  |  |  | @t = _rev($p0, $p1, $p2, $in); | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | # warn if multiple values | 
| 668 | 0 | 0 |  |  |  |  | (@t == 1) or carp('multiple transform values'); | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | # return first output value | 
| 671 | 0 |  |  |  |  |  | return(_fwd($p3, $t[0])); | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | } | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | # combined derivative | 
| 676 |  |  |  |  |  |  | # nominal domain (0 - 1) | 
| 677 |  |  |  |  |  |  | # parameters: (ref_to_object, direction, input_value) | 
| 678 |  |  |  |  |  |  | # returns: (derivative_value) | 
| 679 |  |  |  |  |  |  | sub _derivative { | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | # get parameters | 
| 682 | 0 |  |  | 0 |  |  | my ($self, $dir, $in) = @_; | 
| 683 |  |  |  |  |  |  |  | 
| 684 |  |  |  |  |  |  | # local variables | 
| 685 | 0 |  |  |  |  |  | my ($p0, $p1, $p2, $p3, @t); | 
| 686 | 0 |  |  |  |  |  | my ($di, $do); | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | # verify input | 
| 689 | 0 | 0 |  |  |  |  | (Scalar::Util::looks_like_number($in)) or croak('invalid transform input'); | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | # get parameter arrays | 
| 692 | 0 |  |  |  |  |  | $p0 = $self->[1][$dir]; | 
| 693 | 0 |  |  |  |  |  | $p1 = $self->[2][$dir]; | 
| 694 | 0 |  |  |  |  |  | $p2 = $self->[3][$dir]; | 
| 695 | 0 |  |  |  |  |  | $p3 = $self->[1][1 - $dir]; | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | # compute intermediate value(s) | 
| 698 | 0 |  |  |  |  |  | @t = _rev($p0, $p1, $p2, $in); | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | # warn if multiple values | 
| 701 | 0 | 0 |  |  |  |  | (@t == 1) || print "multiple transform values\n"; | 
| 702 |  |  |  |  |  |  |  | 
| 703 |  |  |  |  |  |  | # compute input derivative (using first value) | 
| 704 | 0 | 0 |  |  |  |  | ($di = _drv($p0, $t[0])) or croak('infinite derivative value'); | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | # compute output derivative (using first value) | 
| 707 | 0 |  |  |  |  |  | $do = _drv($p3, $t[0]); | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  | # return combined derivative | 
| 710 | 0 |  |  |  |  |  | return($do/$di); | 
| 711 |  |  |  |  |  |  |  | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | # compute parametric partial derivatives | 
| 715 |  |  |  |  |  |  | # direction: 0 - normal, 1 - inverse | 
| 716 |  |  |  |  |  |  | # parameters: (ref_to_object, direction, input_value) | 
| 717 |  |  |  |  |  |  | # returns: (partial_derivative_array) | 
| 718 |  |  |  |  |  |  | sub _parametric { | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | # get parameters | 
| 721 | 0 |  |  | 0 |  |  | my ($self, $dir, $in) = @_; | 
| 722 |  |  |  |  |  |  |  | 
| 723 |  |  |  |  |  |  | # local variables | 
| 724 | 0 |  |  |  |  |  | my ($p0, $p1, $p2, $p3); | 
| 725 | 0 |  |  |  |  |  | my (@t, $t, $di, $do, $bfi, $bfo, @pj); | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | # verify input | 
| 728 | 0 | 0 |  |  |  |  | (Scalar::Util::looks_like_number($in)) or croak('invalid transform input'); | 
| 729 |  |  |  |  |  |  |  | 
| 730 |  |  |  |  |  |  | # get parameter arrays | 
| 731 | 0 |  |  |  |  |  | $p0 = $self->[1][$dir]; | 
| 732 | 0 |  |  |  |  |  | $p1 = $self->[2][$dir]; | 
| 733 | 0 |  |  |  |  |  | $p2 = $self->[3][$dir]; | 
| 734 | 0 |  |  |  |  |  | $p3 = $self->[1][1 - $dir]; | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | # compute intermediate value(s) | 
| 737 | 0 |  |  |  |  |  | @t = _rev($p0, $p1, $p2, $in); | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | # warn if multiple values | 
| 740 | 0 | 0 |  |  |  |  | (@t == 1) || print "multiple transform values\n"; | 
| 741 |  |  |  |  |  |  |  | 
| 742 |  |  |  |  |  |  | # use first value | 
| 743 | 0 |  |  |  |  |  | $t = $t[0]; | 
| 744 |  |  |  |  |  |  |  | 
| 745 |  |  |  |  |  |  | # compute input derivative | 
| 746 | 0 |  |  |  |  |  | $di = _drv($p0, $t); | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | # compute input Bernstein polynomials | 
| 749 | 0 | 0 |  |  |  |  | $bfi = @{$p0} ? _bernstein($#{$p0}, $t) : undef(); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  |  | 
| 751 |  |  |  |  |  |  | # compute output derivative | 
| 752 | 0 |  |  |  |  |  | $do = _drv($p3, $t); | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | # compute output Bernstein polynomials | 
| 755 | 0 | 0 |  |  |  |  | $bfo = @{$p3} ? _bernstein($#{$p3}, $t) : undef(); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  |  | 
| 757 |  |  |  |  |  |  | # initialize array | 
| 758 | 0 |  |  |  |  |  | @pj = (); | 
| 759 |  |  |  |  |  |  |  | 
| 760 |  |  |  |  |  |  | # if there are input parameters | 
| 761 | 0 | 0 |  |  |  |  | if (defined($bfi)) { | 
| 762 |  |  |  |  |  |  |  | 
| 763 |  |  |  |  |  |  | # for each parameter | 
| 764 | 0 |  |  |  |  |  | for my $i (0 .. $#{$bfi}) { | 
|  | 0 |  |  |  |  |  |  | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | # verify non-zero input derivative | 
| 767 | 0 | 0 |  |  |  |  | ($di) or croak('infinite Jacobian element'); | 
| 768 |  |  |  |  |  |  |  | 
| 769 |  |  |  |  |  |  | # compute partial derivative | 
| 770 | 0 |  |  |  |  |  | $pj[$i] = -$bfi->[$i] * $do/$di; | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | } | 
| 773 |  |  |  |  |  |  |  | 
| 774 |  |  |  |  |  |  | } | 
| 775 |  |  |  |  |  |  |  | 
| 776 |  |  |  |  |  |  | # add output parameters (if any) | 
| 777 | 0 | 0 |  |  |  |  | push(@pj, @{$bfo}) if (defined($bfo)); | 
|  | 0 |  |  |  |  |  |  | 
| 778 |  |  |  |  |  |  |  | 
| 779 |  |  |  |  |  |  | # return array of partial derivatives | 
| 780 | 0 |  |  |  |  |  | return(@pj); | 
| 781 |  |  |  |  |  |  |  | 
| 782 |  |  |  |  |  |  | } | 
| 783 |  |  |  |  |  |  |  | 
| 784 |  |  |  |  |  |  | # direct forward transform | 
| 785 |  |  |  |  |  |  | # nominal domain (0 - 1) | 
| 786 |  |  |  |  |  |  | # parameters: (parameter_array_reference, input_value) | 
| 787 |  |  |  |  |  |  | # returns: (output_value) | 
| 788 |  |  |  |  |  |  | sub _fwd { | 
| 789 |  |  |  |  |  |  |  | 
| 790 |  |  |  |  |  |  | # get parameters | 
| 791 | 0 |  |  | 0 |  |  | my ($par, $in) = @_; | 
| 792 |  |  |  |  |  |  |  | 
| 793 |  |  |  |  |  |  | # if no parameters | 
| 794 | 0 | 0 |  |  |  |  | if (! @{$par}) { | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | # return input | 
| 797 | 0 |  |  |  |  |  | return($in); | 
| 798 |  |  |  |  |  |  |  | 
| 799 |  |  |  |  |  |  | # one parameter | 
| 800 | 0 |  |  |  |  |  | } elsif (@{$par} == 1) { | 
| 801 |  |  |  |  |  |  |  | 
| 802 |  |  |  |  |  |  | # return constant | 
| 803 | 0 |  |  |  |  |  | return($par->[0]); | 
| 804 |  |  |  |  |  |  |  | 
| 805 |  |  |  |  |  |  | # two parameters | 
| 806 | 0 |  |  |  |  |  | } elsif (@{$par} == 2) { | 
| 807 |  |  |  |  |  |  |  | 
| 808 |  |  |  |  |  |  | # return linear interpolated value | 
| 809 | 0 |  |  |  |  |  | return((1 - $in) * $par->[0] + $in * $par->[1]); | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | # three or more parameters | 
| 812 |  |  |  |  |  |  | } else { | 
| 813 |  |  |  |  |  |  |  | 
| 814 |  |  |  |  |  |  | # check if ICC::Support::Lapack module is loaded | 
| 815 | 0 |  |  |  |  |  | state $lapack = defined($INC{'ICC/Support/Lapack.pm'}); | 
| 816 |  |  |  |  |  |  |  | 
| 817 |  |  |  |  |  |  | # if input < 0 | 
| 818 | 0 | 0 |  |  |  |  | if ($in < 0) { | 
|  |  | 0 |  |  |  |  |  | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | # return extrapolated value | 
| 821 | 0 |  |  |  |  |  | return($par->[0] + $in * _drv($par, 0)); | 
| 822 |  |  |  |  |  |  |  | 
| 823 |  |  |  |  |  |  | # if input > 1 | 
| 824 |  |  |  |  |  |  | } elsif ($in > 1) { | 
| 825 |  |  |  |  |  |  |  | 
| 826 |  |  |  |  |  |  | # return extrapolated value | 
| 827 | 0 |  |  |  |  |  | return($par->[-1] + ($in - 1) * _drv($par, 1)); | 
| 828 |  |  |  |  |  |  |  | 
| 829 |  |  |  |  |  |  | } else { | 
| 830 |  |  |  |  |  |  |  | 
| 831 |  |  |  |  |  |  | # if Lapack module loaded | 
| 832 | 0 | 0 |  |  |  |  | if ($lapack) { | 
| 833 |  |  |  |  |  |  |  | 
| 834 |  |  |  |  |  |  | # return Bernstein interpolated value | 
| 835 | 0 |  |  |  |  |  | return(ICC::Support::Lapack::bernstein($in, $par)); | 
| 836 |  |  |  |  |  |  |  | 
| 837 |  |  |  |  |  |  | } else { | 
| 838 |  |  |  |  |  |  |  | 
| 839 |  |  |  |  |  |  | # return Bernstein interpolated value | 
| 840 | 0 |  |  |  |  |  | return(ICC::Shared::dotProduct(_bernstein($#{$par}, $in), $par)); | 
|  | 0 |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  |  | 
| 842 |  |  |  |  |  |  | } | 
| 843 |  |  |  |  |  |  |  | 
| 844 |  |  |  |  |  |  | } | 
| 845 |  |  |  |  |  |  |  | 
| 846 |  |  |  |  |  |  | } | 
| 847 |  |  |  |  |  |  |  | 
| 848 |  |  |  |  |  |  | } | 
| 849 |  |  |  |  |  |  |  | 
| 850 |  |  |  |  |  |  | # direct reverse transform | 
| 851 |  |  |  |  |  |  | # nominal range (0 - 1) | 
| 852 |  |  |  |  |  |  | # parameters: (parameter_array_reference, x-min/max_array_reference, y-min/max_array_reference, input_value) | 
| 853 |  |  |  |  |  |  | # returns: (output_value -or- array_of_output_values) | 
| 854 |  |  |  |  |  |  | sub _rev { | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | # get parameters | 
| 857 | 0 |  |  | 0 |  |  | my ($par, $xminmax, $yminmax, $in) = @_; | 
| 858 |  |  |  |  |  |  |  | 
| 859 |  |  |  |  |  |  | # local variables | 
| 860 | 0 |  |  |  |  |  | my (@xs, @ys, $slope, $ext, @sol); | 
| 861 | 0 |  |  |  |  |  | my ($p, $loop, $xlo, $xhi, $xval, $yval); | 
| 862 |  |  |  |  |  |  |  | 
| 863 |  |  |  |  |  |  | # if no parameters | 
| 864 | 0 | 0 |  |  |  |  | if (! @{$par}) { | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 865 |  |  |  |  |  |  |  | 
| 866 |  |  |  |  |  |  | # return input | 
| 867 | 0 |  |  |  |  |  | return($in); | 
| 868 |  |  |  |  |  |  |  | 
| 869 |  |  |  |  |  |  | # one parameter | 
| 870 | 0 |  |  |  |  |  | } elsif (@{$par} == 1) { | 
| 871 |  |  |  |  |  |  |  | 
| 872 |  |  |  |  |  |  | # warn if input not equal to constant | 
| 873 | 0 | 0 |  |  |  |  | ($in == $par->[0]) or carp('curve input not equal to constant parameter'); | 
| 874 |  |  |  |  |  |  |  | 
| 875 |  |  |  |  |  |  | # return input | 
| 876 | 0 |  |  |  |  |  | return($in); | 
| 877 |  |  |  |  |  |  |  | 
| 878 |  |  |  |  |  |  | # two parameters | 
| 879 | 0 |  |  |  |  |  | } elsif (@{$par} == 2) { | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | # return linear interpolated value | 
| 882 | 0 |  |  |  |  |  | return(($in - $par->[0])/($par->[1] - $par->[0])); | 
| 883 |  |  |  |  |  |  |  | 
| 884 |  |  |  |  |  |  | # three or more parameters | 
| 885 |  |  |  |  |  |  | } else { | 
| 886 |  |  |  |  |  |  |  | 
| 887 |  |  |  |  |  |  | # setup segment arrays | 
| 888 | 0 |  |  |  |  |  | @xs = (0, @{$xminmax}, 1); | 
|  | 0 |  |  |  |  |  |  | 
| 889 | 0 |  |  |  |  |  | @ys = ($par->[0], @{$yminmax}, $par->[-1]); | 
|  | 0 |  |  |  |  |  |  | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | # if slope(0) not 0 | 
| 892 | 0 | 0 |  |  |  |  | if ($slope = _drv($par, 0)) { | 
| 893 |  |  |  |  |  |  |  | 
| 894 |  |  |  |  |  |  | # compute extrapolation from 0 | 
| 895 | 0 |  |  |  |  |  | $ext = ($in - $par->[0])/$slope; | 
| 896 |  |  |  |  |  |  |  | 
| 897 |  |  |  |  |  |  | # save if less than 0 | 
| 898 | 0 | 0 |  |  |  |  | push(@sol, $ext) if ($ext < 0); | 
| 899 |  |  |  |  |  |  |  | 
| 900 |  |  |  |  |  |  | } | 
| 901 |  |  |  |  |  |  |  | 
| 902 |  |  |  |  |  |  | # test first y-value | 
| 903 | 0 | 0 |  |  |  |  | push(@sol, $xs[0]) if ($ys[0] == $in); | 
| 904 |  |  |  |  |  |  |  | 
| 905 |  |  |  |  |  |  | # for each array segment | 
| 906 | 0 |  |  |  |  |  | for my $i (1 .. $#xs) { | 
| 907 |  |  |  |  |  |  |  | 
| 908 |  |  |  |  |  |  | # if input value falls within segment | 
| 909 | 0 | 0 | 0 |  |  |  | if (($in > $ys[$i - 1] && $in < $ys[$i]) || ($in < $ys[$i - 1] && $in > $ys[$i])) { | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 910 |  |  |  |  |  |  |  | 
| 911 |  |  |  |  |  |  | # get segment polarity | 
| 912 | 0 |  |  |  |  |  | $p = $ys[$i] > $ys[$i - 1]; | 
| 913 |  |  |  |  |  |  |  | 
| 914 |  |  |  |  |  |  | # initialize limits | 
| 915 | 0 |  |  |  |  |  | $xlo = $xs[$i - 1]; | 
| 916 | 0 |  |  |  |  |  | $xhi = $xs[$i]; | 
| 917 |  |  |  |  |  |  |  | 
| 918 |  |  |  |  |  |  | # initialize loop counter | 
| 919 | 0 |  |  |  |  |  | $loop = 0; | 
| 920 |  |  |  |  |  |  |  | 
| 921 |  |  |  |  |  |  | # bisection method loop | 
| 922 | 0 |  | 0 |  |  |  | while ($xhi - $xlo > 1E-3 && $loop++ < 15) { | 
| 923 |  |  |  |  |  |  |  | 
| 924 |  |  |  |  |  |  | # compute midpoint | 
| 925 | 0 |  |  |  |  |  | $xval = ($xlo + $xhi)/2; | 
| 926 |  |  |  |  |  |  |  | 
| 927 |  |  |  |  |  |  | # compute y-value | 
| 928 | 0 |  |  |  |  |  | $yval = _fwd($par, $xval); | 
| 929 |  |  |  |  |  |  |  | 
| 930 |  |  |  |  |  |  | # if y-value > input -or- y-value < input | 
| 931 | 0 | 0 |  |  |  |  | if ($p ? ($yval > $in) : ($yval < $in)) { | 
|  |  | 0 |  |  |  |  |  | 
| 932 |  |  |  |  |  |  |  | 
| 933 |  |  |  |  |  |  | # set high limit | 
| 934 | 0 |  |  |  |  |  | $xhi = $xval; | 
| 935 |  |  |  |  |  |  |  | 
| 936 |  |  |  |  |  |  | } else { | 
| 937 |  |  |  |  |  |  |  | 
| 938 |  |  |  |  |  |  | # set low limit | 
| 939 | 0 |  |  |  |  |  | $xlo = $xval; | 
| 940 |  |  |  |  |  |  |  | 
| 941 |  |  |  |  |  |  | } | 
| 942 |  |  |  |  |  |  |  | 
| 943 |  |  |  |  |  |  | } | 
| 944 |  |  |  |  |  |  |  | 
| 945 |  |  |  |  |  |  | # initialize loop counter | 
| 946 | 0 |  |  |  |  |  | $loop = 0; | 
| 947 |  |  |  |  |  |  |  | 
| 948 |  |  |  |  |  |  | # Newton's method loop | 
| 949 | 0 |  | 0 |  |  |  | while (abs($in - $yval) > 1E-12 && $loop++ < 5 && ($slope = _drv($par, $xval))) { | 
|  |  |  | 0 |  |  |  |  | 
| 950 |  |  |  |  |  |  |  | 
| 951 |  |  |  |  |  |  | # adjust x-value | 
| 952 | 0 |  |  |  |  |  | $xval += ($in - $yval)/$slope; | 
| 953 |  |  |  |  |  |  |  | 
| 954 |  |  |  |  |  |  | # compute y-value | 
| 955 | 0 |  |  |  |  |  | $yval = _fwd($par, $xval); | 
| 956 |  |  |  |  |  |  |  | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  |  | 
| 959 |  |  |  |  |  |  | # print warning if failed to converge | 
| 960 | 0 | 0 |  |  |  |  | printf("'_rev' function of 'bern' object failed to converge with input of %.2f\n", $in) if (abs($in - $yval) > 1E-12); | 
| 961 |  |  |  |  |  |  |  | 
| 962 |  |  |  |  |  |  | # save result | 
| 963 | 0 |  |  |  |  |  | push(@sol, $xval); | 
| 964 |  |  |  |  |  |  |  | 
| 965 |  |  |  |  |  |  | } | 
| 966 |  |  |  |  |  |  |  | 
| 967 |  |  |  |  |  |  | # add segment y-value, if a solution | 
| 968 | 0 | 0 |  |  |  |  | push(@sol, $xs[$i]) if ($ys[$i] == $in); | 
| 969 |  |  |  |  |  |  |  | 
| 970 |  |  |  |  |  |  | } | 
| 971 |  |  |  |  |  |  |  | 
| 972 |  |  |  |  |  |  | # if slope(1) not 0 | 
| 973 | 0 | 0 |  |  |  |  | if ($slope = _drv($par, 1)) { | 
| 974 |  |  |  |  |  |  |  | 
| 975 |  |  |  |  |  |  | # compute extrapolation from 1 | 
| 976 | 0 |  |  |  |  |  | $ext = ($in - $par->[-1])/$slope + 1; | 
| 977 |  |  |  |  |  |  |  | 
| 978 |  |  |  |  |  |  | # save if greater than 1 | 
| 979 | 0 | 0 |  |  |  |  | push(@sol, $ext) if ($ext > 1); | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | } | 
| 982 |  |  |  |  |  |  |  | 
| 983 |  |  |  |  |  |  | } | 
| 984 |  |  |  |  |  |  |  | 
| 985 |  |  |  |  |  |  | # return result (array or first solution) | 
| 986 | 0 | 0 |  |  |  |  | return(wantarray ? @sol : $sol[0]); | 
| 987 |  |  |  |  |  |  |  | 
| 988 |  |  |  |  |  |  | } | 
| 989 |  |  |  |  |  |  |  | 
| 990 |  |  |  |  |  |  | # forward derivative | 
| 991 |  |  |  |  |  |  | # nominal domain is (0 - 1) | 
| 992 |  |  |  |  |  |  | # parameters: (parameter_array_reference, input_value) | 
| 993 |  |  |  |  |  |  | # returns: (output_value) | 
| 994 |  |  |  |  |  |  | sub _drv { | 
| 995 |  |  |  |  |  |  |  | 
| 996 |  |  |  |  |  |  | # get parameters | 
| 997 | 0 |  |  | 0 |  |  | my ($par, $in) = @_; | 
| 998 |  |  |  |  |  |  |  | 
| 999 |  |  |  |  |  |  | # if no parameters | 
| 1000 | 0 | 0 |  |  |  |  | if (! @{$par}) { | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | # return identity derivative | 
| 1003 | 0 |  |  |  |  |  | return(1); | 
| 1004 |  |  |  |  |  |  |  | 
| 1005 |  |  |  |  |  |  | # one parameter | 
| 1006 | 0 |  |  |  |  |  | } elsif (@{$par} == 1) { | 
| 1007 |  |  |  |  |  |  |  | 
| 1008 |  |  |  |  |  |  | # return constant derivative | 
| 1009 | 0 |  |  |  |  |  | return(0); | 
| 1010 |  |  |  |  |  |  |  | 
| 1011 |  |  |  |  |  |  | # two parameters | 
| 1012 | 0 |  |  |  |  |  | } elsif (@{$par} == 2) { | 
| 1013 |  |  |  |  |  |  |  | 
| 1014 |  |  |  |  |  |  | # return linear derivative | 
| 1015 | 0 |  |  |  |  |  | return($par->[1] - $par->[0]); | 
| 1016 |  |  |  |  |  |  |  | 
| 1017 |  |  |  |  |  |  | # three or more parameters | 
| 1018 |  |  |  |  |  |  | } else { | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | # check if ICC::Support::Lapack module is loaded | 
| 1021 | 0 |  |  |  |  |  | state $lapack = defined($INC{'ICC/Support/Lapack.pm'}); | 
| 1022 |  |  |  |  |  |  |  | 
| 1023 |  |  |  |  |  |  | # limit input value (0 - 1) to force linear extrapolation | 
| 1024 | 0 | 0 |  |  |  |  | $in = $in < 0 ? 0 : ($in > 1 ? 1 : $in); | 
|  |  | 0 |  |  |  |  |  | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 |  |  |  |  |  |  | # compute parameter differences | 
| 1027 | 0 |  |  |  |  |  | my $diff = [map {$par->[$_ + 1] - $par->[$_]} (0 .. $#{$par} - 1)]; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1028 |  |  |  |  |  |  |  | 
| 1029 |  |  |  |  |  |  | # if Lapack module loaded | 
| 1030 | 0 | 0 |  |  |  |  | if ($lapack) { | 
| 1031 |  |  |  |  |  |  |  | 
| 1032 |  |  |  |  |  |  | # return Bernstein interpolated derivative | 
| 1033 | 0 |  |  |  |  |  | return($#{$par} * ICC::Support::Lapack::bernstein($in, $diff)); | 
|  | 0 |  |  |  |  |  |  | 
| 1034 |  |  |  |  |  |  |  | 
| 1035 |  |  |  |  |  |  | } else { | 
| 1036 |  |  |  |  |  |  |  | 
| 1037 |  |  |  |  |  |  | # return Bernstein interpolated derivative | 
| 1038 | 0 |  |  |  |  |  | return($#{$par} * ICC::Shared::dotProduct(_bernstein($#{$diff}, $in), $diff)); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1039 |  |  |  |  |  |  |  | 
| 1040 |  |  |  |  |  |  | } | 
| 1041 |  |  |  |  |  |  |  | 
| 1042 |  |  |  |  |  |  | } | 
| 1043 |  |  |  |  |  |  |  | 
| 1044 |  |  |  |  |  |  | } | 
| 1045 |  |  |  |  |  |  |  | 
| 1046 |  |  |  |  |  |  | # compute Bernstein basis values | 
| 1047 |  |  |  |  |  |  | # parameters: (bernstein_degree, t) | 
| 1048 |  |  |  |  |  |  | # returns: (ref_to_bernstein_array) | 
| 1049 |  |  |  |  |  |  | sub _bernstein { | 
| 1050 |  |  |  |  |  |  |  | 
| 1051 |  |  |  |  |  |  | # get parameters | 
| 1052 | 0 |  |  | 0 |  |  | my ($degree, $t0) = @_; | 
| 1053 |  |  |  |  |  |  |  | 
| 1054 |  |  |  |  |  |  | # local variables | 
| 1055 | 0 |  |  |  |  |  | my ($bern, $t1, $m0, $m1, $n); | 
| 1056 |  |  |  |  |  |  |  | 
| 1057 |  |  |  |  |  |  | # binomial coefficients | 
| 1058 | 0 |  |  |  |  |  | state $pascal = [ | 
| 1059 |  |  |  |  |  |  | [1], | 
| 1060 |  |  |  |  |  |  | [1, 1], | 
| 1061 |  |  |  |  |  |  | [1, 2, 1], | 
| 1062 |  |  |  |  |  |  | [1, 3, 3, 1], | 
| 1063 |  |  |  |  |  |  | [1, 4, 6, 4, 1], | 
| 1064 |  |  |  |  |  |  | [1, 5, 10, 10, 5, 1], | 
| 1065 |  |  |  |  |  |  | [1, 6, 15, 20, 15, 6, 1], | 
| 1066 |  |  |  |  |  |  | [1, 7, 21, 35, 35, 21, 7, 1], | 
| 1067 |  |  |  |  |  |  | [1, 8, 28, 56, 70, 56, 28, 8, 1], | 
| 1068 |  |  |  |  |  |  | [1, 9, 36, 84, 126, 126, 84, 36, 9, 1], | 
| 1069 |  |  |  |  |  |  | [1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1], | 
| 1070 |  |  |  |  |  |  | [1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1], | 
| 1071 |  |  |  |  |  |  | [1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1], | 
| 1072 |  |  |  |  |  |  | [1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1], | 
| 1073 |  |  |  |  |  |  | [1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1], | 
| 1074 |  |  |  |  |  |  | [1, 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, 105, 15, 1], | 
| 1075 |  |  |  |  |  |  | [1, 16, 120, 560, 1820, 4368, 8008, 11440, 12870, 11440, 8008, 4368, 1820, 560, 120, 16, 1], | 
| 1076 |  |  |  |  |  |  | [1, 17, 136, 680, 2380, 6188, 12376, 19448, 24310, 24310, 19448, 12376, 6188, 2380, 680, 136, 17, 1], | 
| 1077 |  |  |  |  |  |  | [1, 18, 153, 816, 3060, 8568, 18564, 31824, 43758, 48620, 43758, 31824, 18564, 8568, 3060, 816, 153, 18, 1], | 
| 1078 |  |  |  |  |  |  | [1, 19, 171, 969, 3876, 11628, 27132, 50388, 75582, 92378, 92378, 75582, 50388, 27132, 11628, 3876, 969, 171, 19, 1], | 
| 1079 |  |  |  |  |  |  | [1, 20, 190, 1140, 4845, 15504, 38760, 77520, 125970, 167960, 184756, 167960, 125970, 77520, 38760, 15504, 4845, 1140, 190, 20, 1] | 
| 1080 |  |  |  |  |  |  | ]; | 
| 1081 |  |  |  |  |  |  |  | 
| 1082 |  |  |  |  |  |  | # copy binomial coefficients for this degree | 
| 1083 | 0 |  |  |  |  |  | $bern = [@{$pascal->[$degree]}]; | 
|  | 0 |  |  |  |  |  |  | 
| 1084 |  |  |  |  |  |  |  | 
| 1085 |  |  |  |  |  |  | # compute input complement | 
| 1086 | 0 |  |  |  |  |  | $t1 = 1 - $t0; | 
| 1087 |  |  |  |  |  |  |  | 
| 1088 |  |  |  |  |  |  | # initialize multipliers | 
| 1089 | 0 |  |  |  |  |  | $m0 = $m1 = 1; | 
| 1090 |  |  |  |  |  |  |  | 
| 1091 |  |  |  |  |  |  | # get polynomial degree | 
| 1092 | 0 |  |  |  |  |  | $n = $#{$bern}; | 
|  | 0 |  |  |  |  |  |  | 
| 1093 |  |  |  |  |  |  |  | 
| 1094 |  |  |  |  |  |  | # for each polynomial | 
| 1095 | 0 |  |  |  |  |  | for (1 .. $n) { | 
| 1096 |  |  |  |  |  |  |  | 
| 1097 |  |  |  |  |  |  | # multiply | 
| 1098 | 0 |  |  |  |  |  | $bern->[$_] *= ($m0 *= $t0); | 
| 1099 | 0 |  |  |  |  |  | $bern->[$n - $_] *= ($m1 *= $t1); | 
| 1100 |  |  |  |  |  |  |  | 
| 1101 |  |  |  |  |  |  | } | 
| 1102 |  |  |  |  |  |  |  | 
| 1103 |  |  |  |  |  |  | # return array reference | 
| 1104 | 0 |  |  |  |  |  | return($bern); | 
| 1105 |  |  |  |  |  |  |  | 
| 1106 |  |  |  |  |  |  | } | 
| 1107 |  |  |  |  |  |  |  | 
| 1108 |  |  |  |  |  |  | # fit bern object to data | 
| 1109 |  |  |  |  |  |  | # uses LAPACK dgels function to perform a least-squares fit | 
| 1110 |  |  |  |  |  |  | # the parameters to be fitted have the value 'undef' and must all be within the | 
| 1111 |  |  |  |  |  |  | # output side of the parameter array, e.g. [[0, 100], [undef, undef, undef, undef]] | 
| 1112 |  |  |  |  |  |  | # fit data is an array containing input and output vectors | 
| 1113 |  |  |  |  |  |  | # parameters: (ref_to_object, fit_data_reference, ref_to_parameter_hash) | 
| 1114 |  |  |  |  |  |  | # returns: (dgels_info_value) | 
| 1115 |  |  |  |  |  |  | sub _fit { | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 |  |  |  |  |  |  | # get parameters | 
| 1118 | 0 |  |  | 0 |  |  | my ($self, $fit, $hash) = @_; | 
| 1119 |  |  |  |  |  |  |  | 
| 1120 |  |  |  |  |  |  | # local variables | 
| 1121 | 0 |  |  |  |  |  | my ($m, $n, $d, $t, $p, $span, @sol); | 
| 1122 | 0 |  |  |  |  |  | my (@so, $outz, $bern, $nrhs, $info); | 
| 1123 | 0 |  |  |  |  |  | my ($outz2, $bern2, $fix_hl, $fix_sh, @c2); | 
| 1124 |  |  |  |  |  |  |  | 
| 1125 |  |  |  |  |  |  | # check if ICC::Support::Lapack module is loaded | 
| 1126 | 0 |  |  |  |  |  | state $lapack = defined($INC{'ICC/Support/Lapack.pm'}); | 
| 1127 |  |  |  |  |  |  |  | 
| 1128 |  |  |  |  |  |  | # verify Lapack module loaded (need to add non-Lapack routine someday) | 
| 1129 | 0 | 0 |  |  |  |  | ($lapack) or croak("'fit' option requires ICC::Support::Lapack module"); | 
| 1130 |  |  |  |  |  |  |  | 
| 1131 |  |  |  |  |  |  | # get degree | 
| 1132 | 0 |  |  |  |  |  | $d = $#{$self->[1][1]}; | 
|  | 0 |  |  |  |  |  |  | 
| 1133 |  |  |  |  |  |  |  | 
| 1134 |  |  |  |  |  |  | # for each output parameter | 
| 1135 | 0 |  |  |  |  |  | for my $i (0 .. $d) { | 
| 1136 |  |  |  |  |  |  |  | 
| 1137 |  |  |  |  |  |  | # if parameter is undefined | 
| 1138 | 0 | 0 |  |  |  |  | if (! defined($self->[1][1][$i])) { | 
| 1139 |  |  |  |  |  |  |  | 
| 1140 |  |  |  |  |  |  | # push index | 
| 1141 | 0 |  |  |  |  |  | push(@so, $i); | 
| 1142 |  |  |  |  |  |  |  | 
| 1143 |  |  |  |  |  |  | # set value to 0 | 
| 1144 | 0 |  |  |  |  |  | $self->[1][1][$i] = 0; | 
| 1145 |  |  |  |  |  |  |  | 
| 1146 |  |  |  |  |  |  | } | 
| 1147 |  |  |  |  |  |  |  | 
| 1148 |  |  |  |  |  |  | } | 
| 1149 |  |  |  |  |  |  |  | 
| 1150 |  |  |  |  |  |  | # if no 'undef' parameters | 
| 1151 | 0 | 0 |  |  |  |  | if (@so == 0) { | 
| 1152 |  |  |  |  |  |  |  | 
| 1153 |  |  |  |  |  |  | # warning | 
| 1154 | 0 |  |  |  |  |  | print "no 'undef' parameters to fit\n"; | 
| 1155 |  |  |  |  |  |  |  | 
| 1156 |  |  |  |  |  |  | # return | 
| 1157 | 0 |  |  |  |  |  | return(0) | 
| 1158 |  |  |  |  |  |  |  | 
| 1159 |  |  |  |  |  |  | } | 
| 1160 |  |  |  |  |  |  |  | 
| 1161 |  |  |  |  |  |  | # check for min/max values | 
| 1162 | 0 |  |  |  |  |  | _minmax($self); | 
| 1163 |  |  |  |  |  |  |  | 
| 1164 |  |  |  |  |  |  | # for each sample | 
| 1165 | 0 |  |  |  |  |  | for my $i (0 .. $#{$fit->[0]}) { | 
|  | 0 |  |  |  |  |  |  | 
| 1166 |  |  |  |  |  |  |  | 
| 1167 |  |  |  |  |  |  | # compute output difference | 
| 1168 | 0 |  |  |  |  |  | $outz->[$i] = [$fit->[1][$i] - transform($self, $fit->[0][$i])]; | 
| 1169 |  |  |  |  |  |  |  | 
| 1170 |  |  |  |  |  |  | # get intermediate value | 
| 1171 | 0 |  |  |  |  |  | $t = _rev($self->[1][0], $self->[2][0], $self->[3][0], $fit->[0][$i]); | 
| 1172 |  |  |  |  |  |  |  | 
| 1173 |  |  |  |  |  |  | # compute Bernstein values for undefined parameters | 
| 1174 | 0 |  |  |  |  |  | $bern->[$i] = [@{_bernstein($d, $t)}[@so]]; | 
|  | 0 |  |  |  |  |  |  | 
| 1175 |  |  |  |  |  |  |  | 
| 1176 |  |  |  |  |  |  | } | 
| 1177 |  |  |  |  |  |  |  | 
| 1178 |  |  |  |  |  |  | # make copies | 
| 1179 | 0 |  |  |  |  |  | $outz2 = Storable::dclone($outz); | 
| 1180 | 0 |  |  |  |  |  | $bern2 = Storable::dclone($bern); | 
| 1181 |  |  |  |  |  |  |  | 
| 1182 |  |  |  |  |  |  | # get array sizes | 
| 1183 | 0 |  |  |  |  |  | $m = @{$bern}; | 
|  | 0 |  |  |  |  |  |  | 
| 1184 | 0 |  |  |  |  |  | $n = @{$bern->[0]}; | 
|  | 0 |  |  |  |  |  |  | 
| 1185 | 0 |  |  |  |  |  | $nrhs = @{$outz->[0]}; | 
|  | 0 |  |  |  |  |  |  | 
| 1186 |  |  |  |  |  |  |  | 
| 1187 |  |  |  |  |  |  | # fit the data, return if failed | 
| 1188 | 0 | 0 |  |  |  |  | ($info = ICC::Support::Lapack::dgels('N', $m, $n, $nrhs, $bern, $m, $outz, $m)) && return($info); | 
| 1189 |  |  |  |  |  |  |  | 
| 1190 |  |  |  |  |  |  | # get curve polarity (0 - increasing, 1 - decreasing) | 
| 1191 | 0 | 0 |  |  |  |  | $p = ($span = ($so[-1] == $d ? $outz->[$n - 1][0] : $self->[1][1][-1]) - ($so[0] == 0 ? $outz->[0][0] : $self->[1][1][0])) < 0; | 
|  |  | 0 |  |  |  |  |  | 
| 1192 |  |  |  |  |  |  |  | 
| 1193 |  |  |  |  |  |  | # get flags (indicating HL or SH contrast needs to be fixed) | 
| 1194 | 0 |  | 0 |  |  |  | $fix_hl = $hash->{'fix_hl'} && $so[0] == 0 && $so[1] == 1 && (($outz->[0][0] > $outz->[1][0]) ^ $p); | 
| 1195 | 0 |  | 0 |  |  |  | $fix_sh = $hash->{'fix_sh'} && $so[-1] == $d && $so[-2] == $d - 1 && (($outz->[$n - 1][0] < $outz->[$n - 2][0]) ^ $p); | 
| 1196 |  |  |  |  |  |  |  | 
| 1197 |  |  |  |  |  |  | # if degree >= 4 and HL or SH needs fixing | 
| 1198 | 0 | 0 | 0 |  |  |  | if ($d >= 4 && ($fix_hl || $fix_sh)) { | 
|  |  |  | 0 |  |  |  |  | 
| 1199 |  |  |  |  |  |  |  | 
| 1200 |  |  |  |  |  |  | # for each sample | 
| 1201 | 0 |  |  |  |  |  | for my $i (0 .. $#{$bern2}) { | 
|  | 0 |  |  |  |  |  |  | 
| 1202 |  |  |  |  |  |  |  | 
| 1203 |  |  |  |  |  |  | # if highlight fix | 
| 1204 | 0 | 0 |  |  |  |  | if ($fix_hl) { | 
| 1205 |  |  |  |  |  |  |  | 
| 1206 |  |  |  |  |  |  | # get first 2 Bernstein values | 
| 1207 | 0 |  |  |  |  |  | @c2 = splice(@{$bern2->[$i]}, 0, 2); | 
|  | 0 |  |  |  |  |  |  | 
| 1208 |  |  |  |  |  |  |  | 
| 1209 |  |  |  |  |  |  | # combine to single value | 
| 1210 | 0 |  |  |  |  |  | unshift(@{$bern2->[$i]}, ($c2[0] + $c2[1])); | 
|  | 0 |  |  |  |  |  |  | 
| 1211 |  |  |  |  |  |  |  | 
| 1212 |  |  |  |  |  |  | } | 
| 1213 |  |  |  |  |  |  |  | 
| 1214 |  |  |  |  |  |  | # if shadow fix | 
| 1215 | 0 | 0 |  |  |  |  | if ($fix_sh) { | 
| 1216 |  |  |  |  |  |  |  | 
| 1217 |  |  |  |  |  |  | # get last 2 Bernstein values | 
| 1218 | 0 |  |  |  |  |  | @c2 = splice(@{$bern2->[$i]}, -2); | 
|  | 0 |  |  |  |  |  |  | 
| 1219 |  |  |  |  |  |  |  | 
| 1220 |  |  |  |  |  |  | # combine to single value | 
| 1221 | 0 |  |  |  |  |  | push(@{$bern2->[$i]}, ($c2[0] + $c2[1])); | 
|  | 0 |  |  |  |  |  |  | 
| 1222 |  |  |  |  |  |  |  | 
| 1223 |  |  |  |  |  |  | } | 
| 1224 |  |  |  |  |  |  |  | 
| 1225 |  |  |  |  |  |  | } | 
| 1226 |  |  |  |  |  |  |  | 
| 1227 |  |  |  |  |  |  | # get array sizes | 
| 1228 | 0 |  |  |  |  |  | $m = @{$bern2}; | 
|  | 0 |  |  |  |  |  |  | 
| 1229 | 0 |  |  |  |  |  | $n = @{$bern2->[0]}; | 
|  | 0 |  |  |  |  |  |  | 
| 1230 | 0 |  |  |  |  |  | $nrhs = @{$outz2->[0]}; | 
|  | 0 |  |  |  |  |  |  | 
| 1231 |  |  |  |  |  |  |  | 
| 1232 |  |  |  |  |  |  | # fit the data, return if failed | 
| 1233 | 0 | 0 |  |  |  |  | ($info = ICC::Support::Lapack::dgels('N', $m, $n, $nrhs, $bern2, $m, $outz2, $m)) && return($info); | 
| 1234 |  |  |  |  |  |  |  | 
| 1235 |  |  |  |  |  |  | # extract solution | 
| 1236 | 0 |  |  |  |  |  | @sol = map {$outz2->[$_][0]} (0 .. $n - 1); | 
|  | 0 |  |  |  |  |  |  | 
| 1237 |  |  |  |  |  |  |  | 
| 1238 |  |  |  |  |  |  | # replace combined coefficient(s) | 
| 1239 | 0 | 0 |  |  |  |  | splice(@sol, 1, 0, $sol[0] + $span * 0.001) if ($fix_hl); | 
| 1240 | 0 | 0 |  |  |  |  | splice(@sol, -1, 0, $sol[-1] - $span * 0.001) if ($fix_sh); | 
| 1241 |  |  |  |  |  |  |  | 
| 1242 |  |  |  |  |  |  | } else { | 
| 1243 |  |  |  |  |  |  |  | 
| 1244 |  |  |  |  |  |  | # extract solution | 
| 1245 | 0 |  |  |  |  |  | @sol = map {$outz->[$_][0]} (0 .. $n - 1); | 
|  | 0 |  |  |  |  |  |  | 
| 1246 |  |  |  |  |  |  |  | 
| 1247 |  |  |  |  |  |  | } | 
| 1248 |  |  |  |  |  |  |  | 
| 1249 |  |  |  |  |  |  | # copy solution to output array | 
| 1250 | 0 |  |  |  |  |  | @{$self->[1][1]}[@so] = @sol; | 
|  | 0 |  |  |  |  |  |  | 
| 1251 |  |  |  |  |  |  |  | 
| 1252 |  |  |  |  |  |  | # return | 
| 1253 | 0 |  |  |  |  |  | return(0); | 
| 1254 |  |  |  |  |  |  |  | 
| 1255 |  |  |  |  |  |  | } | 
| 1256 |  |  |  |  |  |  |  | 
| 1257 |  |  |  |  |  |  | # set object contents from parameter hash | 
| 1258 |  |  |  |  |  |  | # parameters: (ref_to_object, ref_to_parameter_hash) | 
| 1259 |  |  |  |  |  |  | sub _new_from_hash { | 
| 1260 |  |  |  |  |  |  |  | 
| 1261 |  |  |  |  |  |  | # get parameters | 
| 1262 | 0 |  |  | 0 |  |  | my ($self, $hash) = @_; | 
| 1263 |  |  |  |  |  |  |  | 
| 1264 |  |  |  |  |  |  | # local variables | 
| 1265 | 0 |  |  |  |  |  | my ($in, $out, $fit); | 
| 1266 |  |  |  |  |  |  |  | 
| 1267 |  |  |  |  |  |  | # if 'input' is defined | 
| 1268 | 0 | 0 |  |  |  |  | if (defined($in = $hash->{'input'})) { | 
| 1269 |  |  |  |  |  |  |  | 
| 1270 |  |  |  |  |  |  | # if 'input' is a vector (21 values or less, some may be 'undef') | 
| 1271 | 0 | 0 | 0 |  |  |  | if (ICC::Shared::is_vector($in) && 21 >= @{$in}) { | 
|  | 0 | 0 | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1272 |  |  |  |  |  |  |  | 
| 1273 |  |  |  |  |  |  | # set object 'input' array | 
| 1274 | 0 |  |  |  |  |  | $self->[1][0] = [@{$in}]; | 
|  | 0 |  |  |  |  |  |  | 
| 1275 |  |  |  |  |  |  |  | 
| 1276 |  |  |  |  |  |  | # if 'input' is an integer between 1 and 20 | 
| 1277 |  |  |  |  |  |  | } elsif (Scalar::Util::looks_like_number($in) && $in == int($in) && $in > 0 && $in < 21) { | 
| 1278 |  |  |  |  |  |  |  | 
| 1279 |  |  |  |  |  |  | # make identity curve | 
| 1280 | 0 |  |  |  |  |  | $self->[1][0] = [map {$_/$in} (0 .. $in)]; | 
|  | 0 |  |  |  |  |  |  | 
| 1281 |  |  |  |  |  |  |  | 
| 1282 |  |  |  |  |  |  | } else { | 
| 1283 |  |  |  |  |  |  |  | 
| 1284 |  |  |  |  |  |  | # error | 
| 1285 | 0 |  |  |  |  |  | croak("invalid 'input' values"); | 
| 1286 |  |  |  |  |  |  |  | 
| 1287 |  |  |  |  |  |  | } | 
| 1288 |  |  |  |  |  |  |  | 
| 1289 |  |  |  |  |  |  | } else { | 
| 1290 |  |  |  |  |  |  |  | 
| 1291 |  |  |  |  |  |  | # disable input | 
| 1292 | 0 |  |  |  |  |  | $self->[1][0] = []; | 
| 1293 |  |  |  |  |  |  |  | 
| 1294 |  |  |  |  |  |  | } | 
| 1295 |  |  |  |  |  |  |  | 
| 1296 |  |  |  |  |  |  | # if 'output' is defined | 
| 1297 | 0 | 0 |  |  |  |  | if (defined($out = $hash->{'output'})) { | 
| 1298 |  |  |  |  |  |  |  | 
| 1299 |  |  |  |  |  |  | # if 'output' is a vector (21 values or less, some may be 'undef') | 
| 1300 | 0 | 0 | 0 |  |  |  | if (ICC::Shared::is_vector($out) && 21 >= @{$out}) { | 
|  | 0 | 0 | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1301 |  |  |  |  |  |  |  | 
| 1302 |  |  |  |  |  |  | # set object 'output' array | 
| 1303 | 0 |  |  |  |  |  | $self->[1][1] = [@{$out}]; | 
|  | 0 |  |  |  |  |  |  | 
| 1304 |  |  |  |  |  |  |  | 
| 1305 |  |  |  |  |  |  | # if 'output' is an integer between 1 and 20 | 
| 1306 |  |  |  |  |  |  | } elsif (Scalar::Util::looks_like_number($out) && $out == int($out) && $out > 0 && $out < 21) { | 
| 1307 |  |  |  |  |  |  |  | 
| 1308 |  |  |  |  |  |  | # make identity curve | 
| 1309 | 0 |  |  |  |  |  | $self->[1][1] = [map {$_/$out} (0 .. $out)]; | 
|  | 0 |  |  |  |  |  |  | 
| 1310 |  |  |  |  |  |  |  | 
| 1311 |  |  |  |  |  |  | } else { | 
| 1312 |  |  |  |  |  |  |  | 
| 1313 |  |  |  |  |  |  | # error | 
| 1314 | 0 |  |  |  |  |  | croak("invalid 'output' values"); | 
| 1315 |  |  |  |  |  |  |  | 
| 1316 |  |  |  |  |  |  | } | 
| 1317 |  |  |  |  |  |  |  | 
| 1318 |  |  |  |  |  |  | } else { | 
| 1319 |  |  |  |  |  |  |  | 
| 1320 |  |  |  |  |  |  | # disable output | 
| 1321 | 0 |  |  |  |  |  | $self->[1][1] = []; | 
| 1322 |  |  |  |  |  |  |  | 
| 1323 |  |  |  |  |  |  | } | 
| 1324 |  |  |  |  |  |  |  | 
| 1325 |  |  |  |  |  |  | # if 'fit' is defined and not empty | 
| 1326 | 0 | 0 | 0 |  |  |  | if (defined($fit = $hash->{'fit'}) && @{$fit}) { | 
|  | 0 |  |  |  |  |  |  | 
| 1327 |  |  |  |  |  |  |  | 
| 1328 |  |  |  |  |  |  | # verify 'fit' vectors | 
| 1329 | 0 | 0 | 0 |  |  |  | (ICC::Shared::is_num_vector($fit->[0]) && 1 <= @{$fit->[0]}) or croak("invalid 'fit' input values"); | 
|  | 0 |  |  |  |  |  |  | 
| 1330 | 0 | 0 | 0 |  |  |  | (ICC::Shared::is_num_vector($fit->[1]) && 1 <= @{$fit->[1]}) or croak("invalid 'fit' output values"); | 
|  | 0 |  |  |  |  |  |  | 
| 1331 | 0 | 0 |  |  |  |  | (@{$fit->[0]} == @{$fit->[1]}) or croak("'fit' input and output vectors are different size"); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1332 |  |  |  |  |  |  |  | 
| 1333 |  |  |  |  |  |  | # fit object to data | 
| 1334 | 0 | 0 |  |  |  |  | (_fit($self, $fit, $hash)) && croak("'fit' operation failed"); | 
| 1335 |  |  |  |  |  |  |  | 
| 1336 |  |  |  |  |  |  | } else { | 
| 1337 |  |  |  |  |  |  |  | 
| 1338 |  |  |  |  |  |  | # verify object parameters are all defined | 
| 1339 | 0 | 0 |  |  |  |  | (@{$self->[1][0]} == grep {defined($_)} @{$self->[1][0]}) or croak("some 'input' values are 'undef'"); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1340 | 0 | 0 |  |  |  |  | (@{$self->[1][1]} == grep {defined($_)} @{$self->[1][1]}) or croak("some 'output' values are 'undef'"); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 |  |  |  |  |  |  | } | 
| 1343 |  |  |  |  |  |  |  | 
| 1344 |  |  |  |  |  |  | # add segment min/max y-values | 
| 1345 | 0 |  |  |  |  |  | _minmax($self); | 
| 1346 |  |  |  |  |  |  |  | 
| 1347 |  |  |  |  |  |  | } | 
| 1348 |  |  |  |  |  |  |  | 
| 1349 |  |  |  |  |  |  | # write bern object to ICC profile | 
| 1350 |  |  |  |  |  |  | # note: writes an equivalent curv object | 
| 1351 |  |  |  |  |  |  | # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry) | 
| 1352 |  |  |  |  |  |  | sub _writeICCbern { | 
| 1353 |  |  |  |  |  |  |  | 
| 1354 |  |  |  |  |  |  | # get parameters | 
| 1355 | 0 |  |  | 0 |  |  | my ($self, $parent, $fh, $tag) = @_; | 
| 1356 |  |  |  |  |  |  |  | 
| 1357 |  |  |  |  |  |  | # local variables | 
| 1358 | 0 |  |  |  |  |  | my ($n, $up, @table); | 
| 1359 |  |  |  |  |  |  |  | 
| 1360 |  |  |  |  |  |  | # get count value (defaults to 4096, the maximum allowed in an 'mft2' tag) | 
| 1361 | 0 |  | 0 |  |  |  | $n = $self->[0]{'curv_points'} // 4096; | 
| 1362 |  |  |  |  |  |  |  | 
| 1363 |  |  |  |  |  |  | # validate number of table entries | 
| 1364 | 0 | 0 | 0 |  |  |  | ($n == int($n) && $n >= 2) or carp('invalid number of table entries'); | 
| 1365 |  |  |  |  |  |  |  | 
| 1366 |  |  |  |  |  |  | # array upper index | 
| 1367 | 0 |  |  |  |  |  | $up = $n - 1; | 
| 1368 |  |  |  |  |  |  |  | 
| 1369 |  |  |  |  |  |  | # for each table entry | 
| 1370 | 0 |  |  |  |  |  | for my $i (0 .. $up) { | 
| 1371 |  |  |  |  |  |  |  | 
| 1372 |  |  |  |  |  |  | # transform value | 
| 1373 | 0 |  |  |  |  |  | $table[$i] = transform($self, $i/$up); | 
| 1374 |  |  |  |  |  |  |  | 
| 1375 |  |  |  |  |  |  | } | 
| 1376 |  |  |  |  |  |  |  | 
| 1377 |  |  |  |  |  |  | # seek start of tag | 
| 1378 | 0 |  |  |  |  |  | seek($fh, $tag->[1], 0); | 
| 1379 |  |  |  |  |  |  |  | 
| 1380 |  |  |  |  |  |  | # write tag type signature and count | 
| 1381 | 0 |  |  |  |  |  | print $fh pack('a4 x4 N', 'curv', $n); | 
| 1382 |  |  |  |  |  |  |  | 
| 1383 |  |  |  |  |  |  | # write array | 
| 1384 | 0 | 0 |  |  |  |  | print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @table); | 
|  | 0 | 0 |  |  |  |  |  | 
| 1385 |  |  |  |  |  |  |  | 
| 1386 |  |  |  |  |  |  | } | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 |  |  |  |  |  |  | 1; |