File Coverage

blib/lib/ICC/Profile/curf.pm
Criterion Covered Total %
statement 9 130 6.9
branch 0 60 0.0
condition 0 15 0.0
subroutine 3 15 20.0
pod 1 9 11.1
total 13 229 5.6


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