File Coverage

blib/lib/ICC/Profile/curv.pm
Criterion Covered Total %
statement 57 153 37.2
branch 14 80 17.5
condition 0 3 0.0
subroutine 12 20 60.0
pod 1 10 10.0
total 84 266 31.5


line stmt bran cond sub pod time code
1             package ICC::Profile::curv;
2              
3 7     7   124960 use strict;
  7         23  
  7         205  
4 7     7   34 use Carp;
  7         10  
  7         488  
5              
6             our $VERSION = 2.11;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 7     7   552 use lib 'lib';
  7         660  
  7         42  
14              
15             # inherit from Shared
16 7     7   924 use parent qw(ICC::Shared);
  7         11  
  7         36  
17              
18             # use POSIX math
19 7     7   370 use POSIX ();
  7         13  
  7         10806  
20              
21             # create new 'curv' tag object
22             # with no parameters, 'curv' has identity response
23             # if array has one value, 'curv' has gamma response (256 = gamma 1)
24             # if array has multiple values, 'curv' is a linear piecewise function (range 0 - 1)
25             # parameters: ([ref_to_array])
26             # returns: (ref_to_object)
27             sub new {
28              
29             # get object class
30 12     12 0 916 my $class = shift();
31              
32             # create empty curv object
33 12         29 my $self = [
34             {}, # object header
35             [] # curve array
36             ];
37              
38             # if parameter supplied
39 12 100       39 if (@_) {
40            
41             # verify array reference
42 11 50       45 (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
43            
44             # copy array
45 11         14 $self->[1] = [@{shift()}];
  11         2127  
46            
47             }
48              
49             # bless object
50 12         40 bless($self, $class);
51            
52             # return object reference
53 12         1575 return($self);
54              
55             }
56              
57             # create inverse 'curv' object
58             # returns: (ref_to_object)
59             sub inv {
60              
61             # get object
62 0     0 0 0 my $self = shift();
63              
64             # local variable
65 0         0 my ($array);
66              
67             # if identity curve
68 0 0       0 if (@{$self->array()} == 0) {
  0 0       0  
69            
70             # return identity curve
71 0         0 return(ICC::Profile::curv->new());
72            
73             # if gamma curve
74 0         0 } elsif (@{$self->array()} == 1) {
75            
76             # verify gamma > 0
77 0 0       0 ($self->array->[0] > 0) or croak('gamma must be > 0');
78            
79             # return inverse gamma curve
80 0         0 return(ICC::Profile::curv->new([65536/$self->array->[0]]));
81            
82             # if LUT curve
83             } else {
84            
85             # for each point
86 0         0 for my $i (0 .. 4095) {
87            
88             # compute inverse curve value
89 0         0 $array->[$i] = $self->inverse($i/4095);
90            
91             }
92            
93             # return inverse curve
94 0         0 return(ICC::Profile::curv->new($array));
95            
96             }
97            
98             }
99              
100             # get/set array reference
101             # parameters: ([ref_to_array])
102             # returns: (ref_to_array)
103             sub array {
104              
105             # get object reference
106 32     32 0 47 my $self = shift();
107              
108             # if parameter
109 32 50       53 if (@_) {
110            
111             # verify array reference
112 0 0       0 (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
113            
114             # set array reference
115 0         0 $self->[1] = shift();
116            
117             }
118              
119             # return array reference
120 32         185 return($self->[1]);
121              
122             }
123              
124             # compute curve function
125             # domain/range is (0 - 1)
126             # parameters: (input_value)
127             # returns: (output_value)
128             sub transform {
129              
130             # get parameters
131 0     0 0 0 my ($self, $in) = @_;
132              
133             # local variables
134 0         0 my ($array, $upper, $ix, $ir);
135              
136             # get array reference
137 0         0 $array = $self->[1];
138              
139             # get array upper subscript
140 0         0 $upper = $#{$array};
  0         0  
141              
142             # if array size == 0 (identity)
143 0 0       0 if (@{$array} == 0) {
  0 0       0  
144            
145             # return input value
146 0         0 return($in);
147            
148             # if array size == 1 (gamma function)
149 0         0 } elsif (@{$array} == 1) {
150            
151             # if gamma == 1
152 0 0       0 if ($array->[0] == 256) {
153            
154             # return input value
155 0         0 return($in);
156            
157             } else {
158            
159             # return x^gamma
160 0 0       0 return($in > 0 ? $in**($array->[0]/256) : 0);
161            
162             }
163            
164             } else {
165            
166             # compute lower bound index
167 0         0 $ix = POSIX::floor($in * $upper);
168            
169             # limit lower bound index
170 0 0       0 $ix = $ix < 0 ? 0 : ($ix > ($upper - 1) ? $upper - 1 : $ix);
    0          
171            
172             # compute interpolation ratio
173 0         0 $ir = $in * $upper - $ix;
174            
175             # return value (linear interpolation)
176 0         0 return(((1 - $ir) * $array->[$ix] + $ir * $array->[$ix + 1]));
177            
178             }
179            
180             }
181              
182             # compute inverse curve function
183             # domain/range is (0 - 1)
184             # parameters: (input_value)
185             # returns: (output_value)
186             sub inverse {
187              
188             # get parameters
189 0     0 0 0 my ($self, $in) = @_;
190              
191             # local variables
192 0         0 my ($array, $upper, $ix, $ir);
193              
194             # get array reference
195 0         0 $array = $self->[1];
196              
197             # get array upper subscript
198 0         0 $upper = $#{$array};
  0         0  
199              
200             # if array size == 0 (identity)
201 0 0       0 if (@{$array} == 0) {
  0 0       0  
202            
203             # return input value
204 0         0 return($in);
205            
206             # if array size = 1 (gamma function)
207 0         0 } elsif (@{$array} == 1) {
208            
209             # if gamma = 1
210 0 0       0 if ($array->[0] == 256) {
211            
212             # return input value
213 0         0 return($in);
214            
215             } else {
216            
217             # return y^(1/gamma)
218 0 0       0 return($in > 0 ? $in**(256/$array->[0]) : 0);
219            
220             }
221            
222             } else {
223            
224             # find array interval containing input value
225 0         0 $ix = _binsearch($array, $in);
226            
227             # compute array interval ratio
228 0         0 $ir = ($in - $array->[$ix])/($array->[$ix + 1] - $array->[$ix]);
229            
230             # return value
231 0         0 return(($ix + $ir)/$upper);
232            
233             }
234            
235             }
236              
237             # compute curve derivative
238             # domain is (0 - 1)
239             # parameters: (input_value)
240             # returns: (derivative_value)
241             sub derivative {
242              
243             # get parameters
244 0     0 0 0 my ($self, $in) = @_;
245              
246             # local variables
247 0         0 my ($array, $upper, $ix, $ir);
248              
249             # get array reference
250 0         0 $array = $self->[1];
251              
252             # get array upper subscript
253 0         0 $upper = $#{$array};
  0         0  
254              
255             # if array size == 0 (identity)
256 0 0       0 if (@{$array} == 0) {
  0 0       0  
257            
258             # return value
259 0         0 return(1);
260            
261             # if array size == 1 (gamma curve)
262 0         0 } elsif (@{$array} == 1) {
263            
264             # if gamma == 1
265 0 0       0 if ($array->[0] == 256) {
266            
267             # return 1
268 0         0 return(1);
269            
270             } else {
271            
272             # return gamma * x^(gamma - 1)
273 0 0       0 return($in > 0 ? ($array->[0]/256) * $in**($array->[0]/256 - 1) : 0);
274            
275             }
276            
277             } else {
278            
279             # compute lower bound index
280 0         0 $ix = POSIX::floor($in * $upper);
281            
282             # limit lower bound index
283 0 0       0 $ix = $ix < 0 ? 0 : ($ix > ($upper - 1) ? $upper - 1 : $ix);
    0          
284            
285             # return value
286 0         0 return(($array->[$ix + 1] - $array->[$ix]) * $upper);
287            
288             }
289            
290             }
291              
292             # create curv tag object from ICC profile
293             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
294             # returns: (ref_to_object)
295             sub new_fh {
296              
297             # get object class
298 4     4 0 655 my $class = shift();
299              
300             # create empty curv object
301 4         10 my $self = [
302             {}, # object header
303             [] # curve array
304             ];
305              
306             # verify 3 parameters
307 4 50       13 (@_ == 3) or croak('wrong number of parameters');
308              
309             # read curv data from profile
310 4         13 _readICCcurv($self, @_);
311              
312             # bless object
313 4         36 bless($self, $class);
314              
315             # return object reference
316 4         18 return($self);
317              
318             }
319              
320             # writes curv tag object to ICC profile
321             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
322             sub write_fh {
323              
324             # verify 4 parameters
325 4 50   4 0 1128 (@_ == 4) or croak('wrong number of parameters');
326              
327             # write curv data to profile
328 4         23 goto &_writeICCcurv;
329              
330             }
331              
332             # get tag size (for writing to profile)
333             # returns: (tag_size)
334             sub size {
335              
336             # get parameters
337 12     12 0 556 my ($self) = @_;
338              
339             # return size
340 12         17 return(12 + @{$self->[1]} * 2);
  12         63  
341              
342             }
343              
344             # print object contents to string
345             # format is an array structure
346             # parameter: ([format])
347             # returns: (string)
348             sub sdump {
349              
350             # get parameters
351 0     0 1 0 my ($self, $p) = @_;
352              
353             # local variables
354 0         0 my ($s, $fmt);
355              
356             # resolve parameter to an array reference
357 0 0       0 $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
358              
359             # get format string
360 0 0 0     0 $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
361              
362             # set string to object ID
363 0         0 $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
364              
365             # return
366 0         0 return($s);
367              
368             }
369              
370             # directional derivative
371             # nominal domain (0 - 1)
372             # direction: 0 - normal, 1 - inverse
373             # parameters: (object_reference, direction, input_value)
374             # returns: (derivative_value)
375             sub _derivative {
376              
377             # get parameters
378 0     0   0 my ($self, $dir, $in) = @_;
379              
380             # if inverse transform
381 0 0       0 if ($dir) {
382            
383             # compute derivative
384 0         0 my $d = derivative($self, $in);
385            
386             # if non-zero
387 0 0       0 if ($d) {
388            
389             # return inverse
390 0         0 return(1/$d);
391            
392             } else {
393            
394             # error
395 0         0 croak('infinite derivative');
396            
397             }
398            
399             } else {
400            
401             # return derivative
402 0         0 return(derivative($self, $in));
403            
404             }
405            
406             }
407              
408             # directional transform
409             # nominal domain (0 - 1)
410             # direction: 0 - normal, 1 - inverse
411             # parameters: (object_reference, direction, input_value)
412             # returns: (output_value)
413             sub _transform {
414              
415             # get parameters
416 0     0   0 my ($self, $dir, $in) = @_;
417              
418             # if inverse transform
419 0 0       0 if ($dir) {
420            
421             # return inverse
422 0         0 return(inverse($self, $in));
423            
424             } else {
425            
426             # return transform
427 0         0 return(transform($self, $in));
428            
429             }
430            
431             }
432              
433             # binary search
434             # finds array interval containing value
435             # assumes values are monotonic
436             # parameters: (ref_to_array, value)
437             # returns: (lower_index)
438             sub _binsearch {
439              
440             # get parameters
441 0     0   0 my ($xref, $v) = @_;
442              
443             # local variables
444 0         0 my ($k, $klo, $khi);
445              
446             # set low and high indices
447 0         0 $klo = 0;
448 0         0 $khi = $#{$xref};
  0         0  
449              
450             # if values are increasing
451 0 0       0 if ($xref->[-1] > $xref->[0]) {
452            
453             # repeat until interval is found
454 0         0 while (($khi - $klo) > 1) {
455            
456             # compute the midpoint
457 0         0 $k = int(($khi + $klo)/2);
458            
459             # if midpoint value > value
460 0 0       0 if ($xref->[$k] > $v) {
461            
462             # set high index to midpoint
463 0         0 $khi = $k;
464            
465             } else {
466            
467             # set low index to midpoint
468 0         0 $klo = $k;
469            
470             }
471            
472             }
473            
474             # if values are decreasing
475             } else {
476            
477             # repeat until interval is found
478 0         0 while (($khi - $klo) > 1) {
479            
480             # compute the midpoint
481 0         0 $k = int(($khi + $klo)/2);
482            
483             # if midpoint value < value
484 0 0       0 if ($xref->[$k] < $v) {
485            
486             # set high index to midpoint
487 0         0 $khi = $k;
488            
489             } else {
490            
491             # set low index to midpoint
492 0         0 $klo = $k;
493            
494             }
495            
496             }
497            
498             }
499              
500             # return low index
501 0         0 return ($klo);
502              
503             }
504              
505             # read curv tag from ICC profile
506             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
507             sub _readICCcurv {
508              
509             # get parameters
510 4     4   12 my ($self, $parent, $fh, $tag) = @_;
511              
512             # local variables
513 4         6 my ($buf, $cnt);
514              
515             # save tag signature
516 4         9 $self->[0]{'signature'} = $tag->[0];
517              
518             # seek start of tag
519 4         42 seek($fh, $tag->[1], 0);
520              
521             # read tag type signature and count
522 4         34 read($fh, $buf, 12);
523              
524             # unpack count
525 4         16 $cnt = unpack('x8 N', $buf);
526              
527             # if count == 1 (gamma)
528 4 50       20 if ($cnt == 1) {
    100          
529            
530             # read gamma
531 0         0 read($fh, $buf, 2);
532            
533             # unpack gamma
534 0         0 $self->[1] = [unpack('n', $buf)];
535            
536             # if count > 1
537             } elsif ($cnt > 1) {
538            
539             # read array values
540 1         6 read($fh, $buf, 2 * $cnt);
541            
542             # unpack array values
543 1         27 $self->[1] = [map {$_/65535} unpack('n*', $buf)];
  700         1093  
544            
545             }
546            
547             }
548              
549             # write curv tag to ICC profile
550             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
551             sub _writeICCcurv {
552              
553             # get parameters
554 4     4   12 my ($self, $parent, $fh, $tag) = @_;
555              
556             # seek start of tag
557 4         30 seek($fh, $tag->[1], 0);
558              
559             # write tag type signature and count
560 4         13 print $fh pack('a4 x4 N', 'curv', scalar(@{$self->[1]}));
  4         31  
561              
562             # if count == 1 (gamma)
563 4 50       8 if (@{$self->[1]} == 1) {
  4 100       12  
564            
565             # write gamma
566 0         0 print $fh pack('n*', $self->[1][0]);
567            
568             # if count > 1
569 4         14 } elsif (@{$self->[1]} > 1){
570            
571             # write array, limiting values and adding 0.5 to round
572 1 50       4 print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @{$self->[1]});
  700 50       1469  
  1         4  
573            
574             }
575            
576             }
577              
578             1;