File Coverage

lib/ICC/Profile/curf.pm
Criterion Covered Total %
statement 12 133 9.0
branch 0 60 0.0
condition 0 15 0.0
subroutine 4 16 25.0
pod 1 9 11.1
total 17 233 7.3


line stmt bran cond sub pod time code
1             package ICC::Profile::curf;
2              
3 1     1   6 use strict;
  1         1  
  1         36  
4 1     1   4 use Carp;
  1         1  
  1         64  
5              
6             our $VERSION = 0.12;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 1     1   5 use lib 'lib';
  1         1  
  1         6  
14              
15             # inherit from Shared
16 1     1   122 use parent qw(ICC::Shared);
  1         1  
  1         6  
17              
18             # create new curf tag object
19             # hash may contain pointers to segments or breakpoints
20             # segments are an array of 'parf' or 'samf' objects
21             # hash keys are: ('segment', 'breakpoint')
22             # parameters: ([ref_to_attribute_hash])
23             # returns: (ref_to_object)
24             sub new {
25              
26             # get object class
27 0     0 0   my $class = shift();
28              
29             # create empty curf object
30 0           my $self = [
31             {}, # object header
32             [], # segment object array
33             [] # breakpoint array
34             ];
35              
36             # if there are parameters
37 0 0         if (@_) {
38            
39             # if one parameter, a hash reference
40 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
41            
42             # make new curf object from attribute hash
43 0           _new_from_hash($self, shift());
44            
45             } else {
46            
47             # error
48 0           croak('\'curf\' parameter must be a hash reference');
49            
50             }
51            
52             }
53              
54             # bless object
55 0           bless($self, $class);
56              
57             # return object reference
58 0           return($self);
59              
60             }
61              
62             # create curf tag object from ICC profile
63             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
64             # returns: (ref_to_object)
65             sub new_fh {
66              
67             # get object class
68 0     0 0   my $class = shift();
69              
70             # create empty curf object
71 0           my $self = [
72             {}, # object header
73             [], # segment object array
74             [] # breakpoint array
75             ];
76              
77             # verify 3 parameters
78 0 0         (@_ == 3) or croak('wrong number of parameters');
79              
80             # read curf data from profile
81 0           _readICCcurf($self, @_);
82              
83             # bless object
84 0           bless($self, $class);
85              
86             # return object reference
87 0           return($self);
88              
89             }
90              
91             # writes curf tag object to ICC profile
92             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
93             sub write_fh {
94              
95             # verify 4 parameters
96 0 0   0 0   (@_ == 4) or croak('wrong number of parameters');
97              
98             # write curf data to profile
99 0           goto &_writeICCcurf;
100              
101             }
102              
103             # get tag size (for writing to profile)
104             # returns: (tag_size)
105             sub size {
106              
107             # get parameters
108 0     0 0   my ($self) = @_;
109              
110             # set size of header and breakpoints
111 0           my $size = 12 + 4 * @{$self->[2]};
  0            
112              
113             # for each curve segment
114 0           for my $seg (@{$self->[1]}) {
  0            
115            
116             # add size
117 0           $size += $seg->size();
118            
119             }
120              
121             # return size
122 0           return($size);
123              
124             }
125              
126             # compute curve derivative function
127             # parameters: (input_value)
128             # returns: (output_value)
129             sub derivative {
130              
131             # get parameters
132 0     0 0   my ($self, $in) = @_;
133              
134             # local variables
135 0           my ($ix);
136              
137             # return transformed value, if just one segment
138 0 0         return($self->[1][0]->derivative($in)) if ($#{$self->[1]} == 0);
  0            
139              
140             # initialize index
141 0           $ix = 0;
142              
143             # for each breakpoint
144 0           for my $bp (@{$self->[2]}) {
  0            
145            
146             # last if breakpoint >= input value
147 0 0         last if ($bp >= $in);
148            
149             # increment index
150 0           $ix++;
151            
152             }
153              
154             # if segment is a 'parf' object
155 0 0         if (UNIVERSAL::isa($self->[1][$ix], 'ICC::Profile::parf')) {
    0          
156            
157             # return transformed value
158 0           return($self->[1][$ix]->derivative($in));
159            
160             # if segment is a 'samf' object
161             } elsif (UNIVERSAL::isa($self->[1][$ix], 'ICC::Profile::samf')) {
162            
163             # return transformed value
164 0           return($self->[1][$ix]->derivative($in, $self->[2][$ix - 1], $self->[2][$ix], $self->[1][$ix - 1]));
165            
166             } else {
167            
168             # error
169 0           croak('unsupported object class for \'curf\' segment');
170            
171             }
172            
173             }
174              
175             # compute curve function
176             # parameters: (input_value)
177             # returns: (output_value)
178             sub transform {
179              
180             # get parameters
181 0     0 0   my ($self, $in) = @_;
182              
183             # local variables
184 0           my ($ix);
185              
186             # return transformed value, if just one segment
187 0 0         return($self->[1][0]->transform($in)) if ($#{$self->[1]} == 0);
  0            
188              
189             # initialize index
190 0           $ix = 0;
191              
192             # for each breakpoint
193 0           for my $bp (@{$self->[2]}) {
  0            
194            
195             # last if breakpoint >= input value
196 0 0         last if ($bp >= $in);
197            
198             # increment index
199 0           $ix++;
200            
201             }
202              
203             # if segment is a 'parf' object
204 0 0         if (UNIVERSAL::isa($self->[1][$ix], 'ICC::Profile::parf')) {
    0          
205            
206             # return transformed value
207 0           return($self->[1][$ix]->transform($in));
208            
209             # if segment is a 'samf' object
210             } elsif (UNIVERSAL::isa($self->[1][$ix], 'ICC::Profile::samf')) {
211            
212             # return transformed value
213 0           return($self->[1][$ix]->transform($in, $self->[2][$ix - 1], $self->[2][$ix], $self->[1][$ix - 1]));
214            
215             } else {
216            
217             # error
218 0           croak('unsupported object class for \'curf\' segment');
219            
220             }
221            
222             }
223              
224             # get/set segment array reference
225             # parameters: ([ref_to_array])
226             # returns: (ref_to_array)
227             sub segment {
228              
229             # get object reference
230 0     0 0   my $self = shift();
231              
232             # if parameter
233 0 0         if (@_) {
234            
235             # verify array reference
236 0 0         (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
237            
238             # set array reference
239 0           $self->[1] = [@{shift()}];
  0            
240            
241             }
242              
243             # return array reference
244 0           return($self->[1]);
245              
246             }
247              
248             # get/set breakpoint array reference
249             # parameters: ([ref_to_array])
250             # returns: (ref_to_array)
251             sub breakpoint {
252              
253             # get object reference
254 0     0 0   my $self = shift();
255              
256             # if parameter
257 0 0         if (@_) {
258            
259             # verify array reference
260 0 0         (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
261            
262             # set array reference
263 0           $self->[2] = [@{shift()}];
  0            
264            
265             }
266              
267             # return array reference
268 0           return($self->[2]);
269              
270             }
271              
272             # print object contents to string
273             # format is an array structure
274             # parameter: ([format])
275             # returns: (string)
276             sub sdump {
277              
278             # get parameters
279 0     0 1   my ($self, $p) = @_;
280              
281             # local variables
282 0           my ($s, $fmt);
283              
284             # resolve parameter to an array reference
285 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
286              
287             # get format string
288 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
289              
290             # set string to object ID
291 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
292              
293             # return
294 0           return($s);
295              
296             }
297              
298             # make new curf object from attribute hash
299             # hash may contain pointers to segments, or breakpoints
300             # hash keys are: ('segment', 'breakpoint')
301             # object elements not specified in the hash are unchanged
302             # parameters: (ref_to_object, ref_to_attribute_hash)
303             sub _new_from_hash {
304              
305             # get parameters
306 0     0     my ($self, $hash) = @_;
307              
308             # for each attribute
309 0           for my $attr (keys(%{$hash})) {
  0            
310            
311             # if 'segment'
312 0 0         if ($attr eq 'segment') {
    0          
313            
314             # if reference to an array of 'parf' or 'samf' objects
315 0 0 0       if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {UNIVERSAL::isa($_, 'ICC::Profile::parf') || UNIVERSAL::isa($_, 'ICC::Profile::samf')} @{$hash->{$attr}}) {
  0 0          
  0            
  0            
316            
317             # set object element
318 0           $self->[1] = [@{$hash->{$attr}}];
  0            
319            
320             } else {
321            
322             # wrong data type
323 0           croak('\'curf\' segment attribute must be a reference to an array of \'parf\' or \'samf\' objects');
324            
325             }
326            
327             # if 'breakpoint'
328             } elsif ($attr eq 'breakpoint') {
329            
330             # if reference to an array of scalars
331 0 0 0       if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {! ref()} @{$hash->{$attr}}) {
  0            
  0            
  0            
332            
333             # set object element
334 0           $self->[2] = [@{$hash->{$attr}}];
  0            
335            
336             } else {
337            
338             # wrong data type
339 0           croak('\'curf\' breakpoint attribute must be a reference to an array of scalars');
340            
341             }
342            
343             }
344            
345             }
346            
347             }
348              
349             # read curf tag from ICC profile
350             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
351             sub _readICCcurf {
352              
353             # get parameters
354 0     0     my ($self, $parent, $fh, $tag) = @_;
355              
356             # local variables
357 0           my ($buf, $segs, $mark, $class);
358              
359             # save tag signature
360 0           $self->[0]{'signature'} = $tag->[0];
361              
362             # seek start of tag
363 0           seek($fh, $tag->[1], 0);
364              
365             # read tag header
366 0           read($fh, $buf, 12);
367              
368             # unpack number of segments
369 0           $segs = unpack('x8 n x2', $buf);
370              
371             # if one segment
372 0 0         if ($segs == 1) {
    0          
373            
374             # mark file offset
375 0           $mark = tell($fh);
376            
377             # read segment type signature
378 0           read($fh, $buf, 4);
379            
380             # if type is 'parf'
381 0 0         if ($buf eq 'parf') {
382            
383             # create object
384 0           $self->[1][0] = ICC::Profile::parf->new_fh($self, $fh, ['curf', $mark]);
385            
386             } else {
387            
388             # error
389 0           croak('wrong segment type in \'curf\' tag');
390            
391             }
392            
393             # if more than one segment
394             } elsif ($segs > 1) {
395            
396             # read breakpoint values
397 0           read($fh, $buf, 4 * ($segs - 1));
398            
399             # unpack breakpoint values
400 0           $self->[2] = [unpack('f>*', $buf)];
401            
402             # for each segment
403 0           for my $i (0 .. $segs - 1) {
404            
405             # mark file offset
406 0           $mark = tell($fh);
407            
408             # read segment type signature
409 0           read($fh, $buf, 4);
410            
411             # if type is 'parf' or 'samf'
412 0 0 0       if ($buf eq 'parf' || $buf eq 'samf') {
413            
414             # form class specifier
415 0           $class = "ICC::Profile::$buf";
416            
417             # create specific tag object
418 0           $self->[1][$i] = $class->new_fh($self, $fh, ['curf', $mark]);
419            
420             } else {
421            
422             # error
423 0           croak('unsupported segment type in \'curf\' tag');
424            
425             }
426            
427             }
428            
429             } else {
430            
431             # error
432 0           croak('\'curf\' tag has no segments');
433            
434             }
435            
436             }
437              
438             # write curf tag to ICC profile
439             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
440             sub _writeICCcurf {
441              
442             # get parameters
443 0     0     my ($self, $parent, $fh, $tag) = @_;
444              
445             # verify segments
446 0 0         (@{$self->[1]} > 0) or carp('\'curf\' object must contain at least one segment');
  0            
447              
448             # verify breakpoints
449 0 0         (@{$self->[1]} == @{$self->[2]} + 1) or carp('\'curf\' object must contain a breakpoint between each segment');
  0            
  0            
450              
451             # seek start of tag
452 0           seek($fh, $tag->[1], 0);
453              
454             # write tag type signature and segment count
455 0           print $fh pack('a4 x4 n x2', 'curf', scalar(@{$self->[1]}));
  0            
456              
457             # write breakpoints
458 0           print $fh pack('f>*', @{$self->[2]});
  0            
459              
460             # for each segment
461 0           for my $seg (@{$self->[1]}) {
  0            
462            
463             # write segment data
464 0           $seg->write_fh($self, $fh, ['curf', tell($fh)]);
465            
466             }
467            
468             }
469              
470             1;