File Coverage

blib/lib/ICC/Profile/vcgt.pm
Criterion Covered Total %
statement 17 164 10.3
branch 1 80 1.2
condition 0 45 0.0
subroutine 5 16 31.2
pod 1 8 12.5
total 24 313 7.6


line stmt bran cond sub pod time code
1             package ICC::Profile::vcgt;
2              
3 2     2   82567 use strict;
  2         12  
  2         47  
4 2     2   8 use Carp;
  2         4  
  2         109  
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 2     2   399 use lib 'lib';
  2         569  
  2         9  
14              
15             # inherit from Shared
16 2     2   543 use parent qw(ICC::Shared);
  2         243  
  2         8  
17              
18             # create new vcgt tag object
19             # parameters: ()
20             # parameters: (ref_to_array_of_curv_objects)
21             # parameters: (ref_to_3x3_parameter_array)
22             # returns: (ref_to_object)
23             sub new {
24              
25             # get object class
26 1     1 0 723 my $class = shift();
27              
28             # create empty vcgt object
29 1         4 my $self = [
30             {}, # object header
31             [[]] # array
32             ];
33              
34             # if parameter supplied
35 1 50       4 if (@_) {
36            
37             # if one parameter, a reference to an array of 'curv' objects
38 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {UNIVERSAL::isa($_, 'ICC::Profile::curv')} @{$_[0]}) {
  0 0 0     0  
  0   0     0  
  0   0     0  
39            
40             # verify one or three objects
41 0 0 0     0 (@{$_[0]} == 1 || @{$_[0]} == 3) or croak('array must contain one or three \'curv\' objects');
  0         0  
  0         0  
42            
43             # copy array
44 0         0 $self->[1] = [@{shift()}];
  0         0  
45            
46             # if one parameter, a reference to a 2-D array
47 0         0 } elsif (@_ == 1 && (ref($_[0]) eq 'ARRAY') && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0         0  
  0         0  
48            
49             # verify 3x3 scalar array
50 0 0 0     0 (3 == @{$_[0]} && 3 == @{$_[0][0]} && 3 == grep {! ref()} @{$_[0][0]}) or croak('array must contain 3x3 scalar array');
  0   0     0  
  0         0  
  0         0  
  0         0  
51            
52             # copy array
53 0         0 $self->[1] = Storable::dclone(shift());
54            
55             } else {
56            
57             # error
58 0         0 croak('invalid parameter(s) for new \'vcgt\' object');
59            
60             }
61            
62             }
63              
64             # bless object
65 1         2 bless($self, $class);
66              
67             # return object reference
68 1         2 return($self);
69              
70             }
71              
72             # create vcgt tag object from ICC profile
73             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
74             # returns: (ref_to_object)
75             sub new_fh {
76              
77             # get object class
78 0     0 0   my $class = shift();
79              
80 0           my $self = [
81             {}, # object header
82             [[]] # array
83             ];
84              
85             # verify 3 parameters
86 0 0         (@_ == 3) or croak('wrong number of parameters');
87              
88             # read vcgt data from profile
89 0           _readICCvcgt($self, @_);
90              
91             # bless object
92 0           bless($self, $class);
93              
94             # return object reference
95 0           return($self);
96              
97             }
98              
99             # writes vcgt tag object to ICC profile
100             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
101             sub write_fh {
102              
103             # verify 4 parameters
104 0 0   0 0   (@_ == 4) or croak('wrong number of parameters');
105              
106             # write vcgt data to profile
107 0           goto &_writeICCvcgt;
108              
109             }
110              
111             # get tag size (for writing to profile)
112             # returns: (tag_size)
113             sub size {
114              
115             # get parameters
116 0     0 0   my ($self) = @_;
117              
118             # local variables
119 0           my ($channels, $count, $size);
120              
121             # if table
122 0 0 0       if (UNIVERSAL::isa($self->[1][0], 'ICC::Profile::curv') && @{$self->[1][0]->array()} > 1) {
  0            
123            
124             # get channels
125 0           $channels = @{$self->[1]};
  0            
126            
127             # get entryCount
128 0           $count = @{$self->[1][0]->array()};
  0            
129            
130             # get entrySize
131 0 0         $size = defined($self->[0]{'entrySize'}) ? $self->[0]{'entrySize'} : 2;
132            
133             # return size
134 0           return(18 + $channels * $count * $size);
135            
136             # if function
137             } else {
138            
139             # return size
140 0           return(48);
141            
142             }
143            
144             }
145              
146             # get/set array reference
147             # parameters: ([ref_to_array])
148             # returns: (ref_to_array)
149             sub array {
150              
151             # get object reference
152 0     0 0   my $self = shift();
153              
154             # if parameter
155 0 0         if (@_) {
156            
157             # verify array reference
158 0 0         (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
159            
160             # set array reference
161 0           $self->[1] = shift();
162            
163             }
164            
165             # return array reference
166 0           return($self->[1]);
167              
168             }
169              
170             # compute curve function
171             # parameters: (array_input_values -or- ref_to_input_array)
172             # returns: (array_output_values -or- ref_to_output-array)
173             sub transform {
174            
175             # get object reference
176 0     0 0   my ($self) = shift();
177            
178             # if one input parameter, an array reference
179 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY') {
    0 0        
180            
181             # transform the array structure
182 0           _crawl($self, 0, $_[0], my $out = []);
183            
184             # return output reference
185 0           return($out);
186            
187             # if one input parameter, a Math::Matrix object
188             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
189            
190             # transform the array structure
191 0           _crawl($self, 0, $_[0], my $out = bless([], 'Math::Matrix'));
192            
193             # return output reference
194 0           return($out);
195            
196             } else {
197            
198             # if caller expects array
199 0 0         if (wantarray) {
200            
201             # return array
202 0           return(_transform($self, 0, @_));
203            
204             } else {
205            
206             # transform array
207 0           my @out = _transform($self, 0, @_);
208            
209             # return scalar
210 0           return($out[0]);
211            
212             }
213            
214             }
215              
216             }
217              
218             # compute inverse curve function
219             # parameters: (array_input_values -or- ref_to_input_array)
220             # returns: (array_output_values -or- ref_to_output-array)
221             sub inverse {
222            
223             # get object reference
224 0     0 0   my ($self) = shift();
225            
226             # if one input parameter, an array reference
227 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY') {
    0 0        
228            
229             # transform the array structure
230 0           _crawl($self, 1, $_[0], my $out = []);
231            
232             # return output reference
233 0           return($out);
234            
235             # if one input parameter, a Math::Matrix object
236             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
237            
238             # transform the array structure
239 0           _crawl($self, 1, $_[0], my $out = bless([], 'Math::Matrix'));
240            
241             # return output reference
242 0           return($out);
243            
244             } else {
245            
246             # if caller expects array
247 0 0         if (wantarray) {
248            
249             # return array
250 0           return(_transform($self, 1, @_));
251            
252             } else {
253            
254             # transform array
255 0           my @out = _transform($self, 1, @_);
256            
257             # return scalar
258 0           return($out[0]);
259            
260             }
261            
262             }
263            
264             }
265              
266             # print object contents to string
267             # format is an array structure
268             # parameter: ([format])
269             # returns: (string)
270             sub sdump {
271              
272             # get parameters
273 0     0 1   my ($self, $p) = @_;
274              
275             # local variables
276 0           my ($s, $fmt);
277              
278             # resolve parameter to an array reference
279 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
280              
281             # get format string
282 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
283              
284             # set string to object ID
285 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
286              
287             # return
288 0           return($s);
289              
290             }
291              
292             # recursive transform
293             # array structure is traversed until scalar arrays are found and transformed
294             # parameters: (object_reference, direction, input_array_reference, output_array_reference)
295             sub _crawl {
296            
297             # get parameters
298 0     0     my ($self, $dir, $in, $out) = @_;
299            
300             # if input is a vector (reference to a scalar array)
301 0 0         if (@{$in} == grep {! ref()} @{$in}) {
  0            
  0            
  0            
302            
303             # transform input vector and copy to output
304 0           @{$out} = _transform($self, $dir, @{$in});
  0            
  0            
305            
306             } else {
307            
308             # for each input element
309 0           for my $i (0 .. $#{$in}) {
  0            
310            
311             # if an array reference
312 0 0         if (ref($in->[$i]) eq 'ARRAY') {
313            
314             # transform next level
315 0           _crawl($self, $dir, $in->[$i], $out->[$i] = []);
316            
317             } else {
318            
319             # error
320 0           croak('invalid transform input');
321            
322             }
323            
324             }
325            
326             }
327            
328             }
329              
330             # transform input value array (vector)
331             # direction: 0 - normal, 1 - inverse
332             # parameters: (object_reference, direction, input_value_array)
333             # returns: (output_value_array)
334             sub _transform {
335              
336             # get parameters
337 0     0     my ($self, $dir, @in) = @_;
338              
339             # local variables
340 0           my (@out);
341              
342             # verify inputs are all scalars
343 0 0         (@in == grep {! ref()} @in) or croak('invalid transform input');
  0            
344              
345             # verify number of inputs equals number of channels
346 0 0         (@in == @{$self->[1]}) or croak('wrong number of input values');
  0            
347              
348             # for each channel
349 0           for my $i (0 .. $#{$self->[1]}) {
  0            
350            
351             # if 'curv' object
352 0 0         if (UNIVERSAL::isa($self->[1][$i], 'ICC::Profile::curv')) {
353            
354             # transform using 'curv' method
355 0           $out[$i] = $self->[1][$i]->_transform($dir, $in[$i]);
356            
357             } else {
358            
359             # if normal direction
360 0 0         if ($dir == 0) {
361            
362             # forward transform using formula (out = min + (max - min) * input^gamma)
363 0           $out[$i] = $self->[1][$i][1] + ($self->[1][$i][2] - $self->[1][$i][1]) * $in[$i]**$self->[1][$i][0];
364            
365             } else {
366            
367             # reverse transform using formula (out = ((input - min)/(max - min))^(1/gamma))
368 0           $out[$i] = (($in[$i] - $self->[1][$i][1])/($self->[1][$i][2] - $self->[1][$i][1]))**(1/$self->[1][$i][0]);
369            
370             }
371            
372             }
373            
374             }
375              
376             # return output array
377 0           return(@out);
378              
379             }
380              
381             # read vcgt tag from ICC profile
382             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
383             sub _readICCvcgt {
384              
385             # get parameters
386 0     0     my ($self, $parent, $fh, $tag) = @_;
387              
388             # local variables
389 0           my ($tagType, $buf, $channels, $count, $size, @table);
390              
391             # save tag signature
392 0           $self->[0]{'signature'} = $tag->[0];
393              
394             # seek start of tag
395 0           seek($fh, $tag->[1], 0);
396              
397             # read first 12 bytes
398 0           read($fh, $buf, 12);
399              
400             # unpack tagType (0 = cmVideoCardGammaTableType, 1 = cmVideoCardGammaFormulaType)
401 0           $tagType = unpack('x8 N', $buf);
402              
403             # if table
404 0 0         if ($tagType == 0) {
    0          
405            
406             # read 6 bytes
407 0           read($fh, $buf, 6);
408            
409             # unpack channels, entryCount, entrySize
410 0           ($channels, $count, $size) = unpack('n3', $buf);
411            
412             # save entrySize in header hash
413 0           $self->[0]{'entrySize'} = $size;
414            
415             # for each channel (gray or RGB)
416 0           for my $i (0 .. $channels - 1) {
417            
418             # read table data
419 0           read($fh, $buf, $count * $size);
420            
421             # if 8-bit
422 0 0         if ($size == 1) {
423            
424             # unpack table
425 0           @table = unpack('C*', $buf);
426            
427             # save as 'curv' object
428 0           $self->[1][$i] = ICC::Profile::curv->new([map {$_/255} @table]);
  0            
429            
430             # else 16-bit
431             } else {
432            
433             # unpack table
434 0           @table = unpack('n*', $buf);
435            
436             # save as 'curv' object
437 0           $self->[1][$i] = ICC::Profile::curv->new([map {$_/65535} @table]);
  0            
438            
439             }
440            
441             }
442            
443             # if formula
444             } elsif ($tagType == 1) {
445            
446             # for each RGB
447 0           for my $i (0 .. 2) {
448            
449             # read 12 bytes
450 0           read($fh, $buf, 12);
451            
452             # unpack gamma, min, max (s15Fixed16Number values)
453 0           $self->[1][$i] = [ICC::Shared::s15f162v(unpack('N3', $buf))];
454            
455             }
456            
457             } else {
458            
459             # error
460 0           croak('invalid \'vcgt\' tagType');
461            
462             }
463            
464             }
465              
466             # write vcgt tag to ICC profile
467             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
468             sub _writeICCvcgt {
469              
470             # get parameters
471 0     0     my ($self, $parent, $fh, $tag) = @_;
472              
473             # local variables
474 0           my ($tagType, $channels, $count, $size, @table, $gamma);
475              
476             # seek start of tag
477 0           seek($fh, $tag->[1], 0);
478              
479             # determine tagType (0 = cmVideoCardGammaTableType, 1 = cmVideoCardGammaFormulaType)
480 0 0 0       $tagType = (UNIVERSAL::isa($self->[1][0], 'ICC::Profile::curv') && @{$self->[1][0]->array()} > 1) ? 0 : 1;
481              
482             # write signature and tagType
483 0           print $fh pack('a4 x4 N', 'vcgt', $tagType);
484              
485             # if table
486 0 0         if ($tagType == 0) {
487            
488             # get channels
489 0           $channels = @{$self->[1]};
  0            
490            
491             # get entryCount
492 0           $count = @{$self->[1][0]->array()};
  0            
493            
494             # if entrySize is 8-bit
495 0 0 0       $size = (defined($self->[0]{'entrySize'}) && $self->[0]{'entrySize'} == 1) ? 1 : 2;
496            
497             # write channels, entryCount, entrySize
498 0           print $fh pack('n3', $channels, $count, $size);
499            
500             # for each channel (gray or RGB)
501 0           for my $i (0 .. $channels - 1) {
502            
503             # if 8-bit
504 0 0         if ($size == 1) {
505            
506             # write table limiting values, converting to 8-bit, adding 0.5 to round
507 0 0         print $fh pack('C*', map {$_ < 0 ? 0 : ($_ > 1 ? 255 : $_ * 255 + 0.5)} @{$self->[1][$i]->array()});
  0 0          
  0            
508            
509             # else 16-bit
510             } else {
511            
512             # write table limiting values, converting to 16-bit, adding 0.5 to round
513 0 0         print $fh pack('n*', map {$_ < 0 ? 0 : ($_ > 1 ? 65535 : $_ * 65535 + 0.5)} @{$self->[1][$i]->array()});
  0 0          
  0            
514            
515             }
516            
517             }
518            
519             # if formula
520             } else {
521            
522             # if gamma type 'curv' objects
523 0 0         if (UNIVERSAL::isa($self->[1][0], 'ICC::Profile::curv')) {
524            
525             # for each RGB
526 0           for my $i (0 .. 2) {
527            
528             # get 'curv' object index (could be just one 'curv')
529 0 0         my $j = defined($self->[1][$i]) ? $i : 0;
530            
531             # get gamma (use 1.0 if undefined)
532 0 0         $gamma = defined($self->[1][$j]->array->[0]) ? $self->[1][$j]->array->[0] : 1;
533            
534             # write gamma, min, max (s15Fixed16Number values)
535 0           print $fh pack('N3', ICC::Shared::v2s15f16($gamma, 0, 1));
536            
537             }
538            
539             # if numeric array
540             } else {
541            
542             # for each RGB
543 0           for my $i (0 .. 2) {
544            
545             # write gamma, min, max (s15Fixed16Number values)
546 0           print $fh pack('N3', ICC::Shared::v2s15f16(@{$self->[1][$i]}));
  0            
547            
548             }
549            
550             }
551            
552             }
553            
554             }
555              
556             1;