File Coverage

blib/lib/ICC/Profile/gbd_.pm
Criterion Covered Total %
statement 18 292 6.1
branch 1 120 0.8
condition 0 78 0.0
subroutine 5 20 25.0
pod 1 11 9.0
total 25 521 4.8


line stmt bran cond sub pod time code
1             package ICC::Profile::gbd_;
2              
3 2     2   84195 use strict;
  2         14  
  2         50  
4 2     2   8 use Carp;
  2         3  
  2         118  
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   406 use lib 'lib';
  2         561  
  2         9  
14              
15             # inherit from Shared
16 2     2   626 use parent qw(ICC::Shared);
  2         233  
  2         9  
17              
18             # create new gbd_ object
19             # hash keys are: ('vertex', 'pcs', 'device')
20             # 'vertex', 'pcs' and 'device' values are 2D array references -or- Math::Matrix objects
21             # each 'vertex' row contains an array of 3 indices defining a gamut face
22             # these indices address the 'pcs' and optional 'device' coordinate arrays
23             # parameters: ([ref_to_attribute_hash])
24             # returns: (ref_to_object)
25             sub new {
26              
27             # get object class
28 1     1 0 760 my $class = shift();
29              
30             # create empty gbd_ object
31             # index 4 reserved for cache
32             # index 5 reserved for index
33 1         4 my $self = [
34             {}, # header
35             [], # face vertex IDs
36             [], # pcs coordinates
37             [] # device coordinates
38             ];
39            
40             # local parameter
41 1         2 my ($info);
42              
43             # if there are parameters
44 1 50       4 if (@_) {
45            
46             # if one parameter, a hash reference
47 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
48            
49             # make new gbd_ object from attribute hash
50 0         0 _new_from_hash($self, shift());
51            
52             } else {
53            
54             # error
55 0         0 croak('\'gbd_\' invalid parameter(s)');
56            
57             }
58            
59             }
60              
61             # bless object
62 1         2 bless($self, $class);
63              
64             # return object reference
65 1         2 return($self);
66              
67             }
68              
69             # get/set reference to header hash
70             # parameters: ([ref_to_new_hash])
71             # returns: (ref_to_hash)
72             sub header {
73            
74             # get object reference
75 0     0 0   my $self = shift();
76            
77             # if there are parameters
78 0 0         if (@_) {
79            
80             # if one parameter, a hash reference
81 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
82            
83             # set header to new hash
84 0           $self->[0] = {%{shift()}};
  0            
85            
86             } else {
87            
88             # error
89 0           croak('\'gbd_\' header attribute must be a hash reference');
90            
91             }
92            
93             }
94            
95             # return header reference
96 0           return($self->[0]);
97            
98             }
99              
100             # get/set reference to vertex array
101             # parameters: ([ref_to_new_array])
102             # returns: (ref_to_array)
103             sub vertex {
104              
105             # get object reference
106 0     0 0   my $self = shift();
107              
108             # if there are parameters
109 0 0         if (@_) {
110            
111             # if one parameter, a 2-D array reference
112 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0 0 0        
  0   0        
  0            
113            
114             # set vertex to clone of array
115 0           $self->[1] = bless(Storable::dclone($_[0]), 'Math::Matrix');
116            
117             # if one parameter, a Math::Matrix object
118             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
119            
120             # set vertex to object
121 0           $self->[1] = $_[0];
122            
123             } else {
124            
125             # error
126 0           croak('gbd_ vertex must be a 2-D array reference or Math::Matrix object');
127            
128             }
129            
130             }
131              
132             # return object reference
133 0           return($self->[1]);
134              
135             }
136              
137             # get/set reference to pcs array
138             # parameters: ([ref_to_new_array])
139             # returns: (ref_to_array)
140             sub pcs {
141              
142             # get object reference
143 0     0 0   my $self = shift();
144              
145             # if there are parameters
146 0 0         if (@_) {
147            
148             # if one parameter, a 2-D array reference
149 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0 0 0        
  0   0        
  0            
150            
151             # set pcs to clone of array
152 0           $self->[2] = bless(Storable::dclone($_[0]), 'Math::Matrix');
153            
154             # if one parameter, a Math::Matrix object
155             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
156            
157             # set pcs to object
158 0           $self->[2] = $_[0];
159            
160             } else {
161            
162             # error
163 0           croak('gbd_ pcs must be a 2-D array reference or Math::Matrix object');
164            
165             }
166            
167             }
168              
169             # return object reference
170 0           return($self->[2]);
171              
172             }
173              
174             # get/set reference to device array
175             # parameters: ([ref_to_new_array])
176             # returns: (ref_to_array)
177             sub device {
178              
179             # get object reference
180 0     0 0   my $self = shift();
181              
182             # if there are parameters
183 0 0         if (@_) {
184            
185             # if one parameter, a 2-D array reference
186 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0 0 0        
  0   0        
  0            
187            
188             # set device to clone of array
189 0           $self->[3] = bless(Storable::dclone($_[0]), 'Math::Matrix');
190            
191             # if one parameter, a Math::Matrix object
192             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
193            
194             # set device to object
195 0           $self->[3] = $_[0];
196            
197             } else {
198            
199             # error
200 0           croak('gbd_ device must be a 2-D array reference or Math::Matrix object');
201            
202             }
203            
204             }
205              
206             # return object reference
207 0           return($self->[3]);
208              
209             }
210              
211             # test an array of samples against gamut
212             # the point inside the gamut my be supplied,
213             # otherwise it is computed from the gamut data
214             # result is an array, [[radius, intersect_point, face_ID], [...]]
215             # if radius == 1, sample is on the gamut surface
216             # if radius > 1, sample is inside the gamut
217             # if radius < 1, sample is out-of-gamut
218             # parameters: (sample_array, [point_inside_gamut])
219             # returns: (result_array)
220             sub test {
221              
222             # get parameters
223 0     0 0   my ($self, $samples, $p0) = @_;
224              
225             # local variables
226 0           my ($m, $n, $ps, $i, $j, $faces, $info, $r, $px, $result);
227              
228             # if parameter is undefined
229 0 0 0       if (! defined($p0)) {
    0 0        
230            
231             # if defined in header
232 0 0 0       if (defined($self->[0]{'p0'}) && defined($self->[5])) {
233            
234             # use header value
235 0           $p0 = $self->[0]{'p0'};
236            
237             } else {
238            
239             # use mean value of vertices
240 0           $p0 = ICC::Support::Lapack::mean($self->[2]);
241            
242             }
243            
244             # if parameter is defined, but different from header value
245             } elsif (defined($self->[0]{'p0'}) && ($self->[0]{'p0'}[0] != $p0->[0] || $self->[0]{'p0'}[1] != $p0->[1] || $self->[0]{'p0'}[2] != $p0->[2])) {
246            
247             # undefine spherical index to force re-calculation
248 0           undef($self->[5]);
249            
250             }
251              
252             # if spherical index defined
253 0 0         if (defined($self->[5])) {
254            
255             # get index array size
256 0           $m = @{$self->[5]};
  0            
257 0           $n = @{$self->[5][0]};
  0            
258            
259             } else {
260            
261             # compute index grid size
262 0           $m = $n = int(@{$self->[1]}**(1/3));
  0            
263            
264             # make spherical index
265 0           _make_index($self, $p0, $m, $n) ;
266            
267             }
268              
269             # for each sample
270 0           for my $s (0 .. $#{$samples}) {
  0            
271            
272             # get sample
273 0           $ps = $samples->[$s];
274            
275             # compute spherical indices
276 0           $i = int($m * atan2(sqrt(($ps->[1] - $p0->[1])**2 + ($ps->[2] - $p0->[2])**2), $ps->[0] - $p0->[0])/ICC::Shared::PI);
277 0           $j = int($n * (atan2($ps->[2] - $p0->[2], $ps->[1] - $p0->[1])/ICC::Shared::PI + 1)/2);
278            
279             # limit indices
280 0 0         $i = $i < $m ? $i : $m - 1;
281 0 0         $j = $j < $n ? $j : 0;
282            
283             # get face ID list from spherical index
284 0           $faces = $self->[5][$i][$j];
285            
286             # for each gamut face
287 0           for my $f (@{$faces}) {
  0            
288            
289             # find intersection, if a new face
290 0           ($info, $r, $px) = intersect($self, $f, $p0, $ps);
291            
292             # if intersect found
293 0 0         if ($info == 0) {
294            
295             # save result
296 0           $result->[$s] = [$r, $px, $f];
297            
298             # quit loop
299 0           last;
300            
301             }
302            
303             }
304            
305             }
306              
307             # return
308 0           return($result);
309              
310             }
311              
312             # compute intersection of line segment with face triangle
313             # the radius is 0 at point_0, and 1 at point_1
314             # parameters: (face_ID, point_0, point_1)
315             # returns: (info, radius, point_intersect)
316             sub intersect {
317              
318             # get parameters
319 0     0 0   my ($self, $fid, $p0, $p1) = @_;
320              
321             # local variables
322 0           my ($v0, $v1, $v2, $u, $v, $n, $dir, $w, $w0, $r, $a, $b);
323 0           my ($px, $uu, $uv, $vv, $wu, $wv, $d, $s, $t);
324              
325             # if face values are cached
326 0 0         if (defined($self->[4][$fid])) {
327            
328             # get face vertex
329 0           $v0 = $self->[2][$self->[1][$fid][0]];
330            
331             # get face values
332 0           ($u, $v, $n, $uu, $uv, $vv) = @{$self->[4][$fid]};
  0            
333            
334             } else {
335            
336             # get face vertices
337 0           $v0 = $self->[2][$self->[1][$fid][0]];
338 0           $v1 = $self->[2][$self->[1][$fid][1]];
339 0           $v2 = $self->[2][$self->[1][$fid][2]];
340            
341             # compute triangle edge vectors
342 0           $u = [$v1->[0] - $v0->[0], $v1->[1] - $v0->[1], $v1->[2] - $v0->[2]];
343 0           $v = [$v2->[0] - $v0->[0], $v2->[1] - $v0->[1], $v2->[2] - $v0->[2]];
344            
345             # compute normal vector
346 0           $n = ICC::Shared::crossProduct($u, $v);
347            
348             # compute barycentric dot products
349 0           $uu = ICC::Shared::dotProduct($u, $u);
350 0           $uv = ICC::Shared::dotProduct($u, $v);
351 0           $vv = ICC::Shared::dotProduct($v, $v);
352            
353             # cache face values
354 0           $self->[4][$fid] = [$u, $v, $n, $uu, $uv, $vv];
355            
356             }
357              
358             # check for degenerate triangle
359 0 0 0       return(-1) if ($n->[0] == 0 && $n->[1] == 0 && $n->[2] == 0);
      0        
360              
361             # compute direction vector
362 0           $dir = [$p1->[0] - $p0->[0], $p1->[1] - $p0->[1], $p1->[2] - $p0->[2]];
363              
364             # compute segment to triangle vector
365 0           $w0 = [$p0->[0] - $v0->[0], $p0->[1] - $v0->[1], $p0->[2] - $v0->[2]];
366              
367             # compute dot products
368 0           $a = -ICC::Shared::dotProduct($n, $w0);
369 0           $b = ICC::Shared::dotProduct($n, $dir);
370              
371             # if b is a very small number
372 0 0         if (abs($b) < ICC::Shared::DBL_MIN) {
373            
374             # return (3 - segment lies in plane, 4 - segment disjoint from plane)
375 0 0         return($a ? 3 : 4);
376            
377             }
378              
379             # compute radius
380 0           $r = $a/$b;
381              
382             # check if reverse intersection
383 0 0         return(2, $r) if ($r < 0);
384              
385             # compute the intersection point
386 0           $px = [$p0->[0] + $r * $dir->[0], $p0->[1] + $r * $dir->[1], $p0->[2] + $r * $dir->[2]];
387              
388             # compute barycentric dot products
389 0           $w = [$px->[0] - $v0->[0], $px->[1] - $v0->[1], $px->[2] - $v0->[2]];
390 0           $wu = ICC::Shared::dotProduct($w, $u);
391 0           $wv = ICC::Shared::dotProduct($w, $v);
392              
393             # compute common denominator
394 0           $d = $uv * $uv - $uu * $vv;
395              
396             # compute barycentric coordinate
397 0           $s = ($uv * $wv - $vv * $wu) / $d;
398              
399             # return if intersect outside triangle
400 0 0 0       return(1, $r, $px) if ($s < 0 || $s > 1);
401              
402             # compute barycentric coordinate
403 0           $t = ($uv * $wu - $uu * $wv) / $d;
404              
405             # return if intersect outside triangle
406 0 0 0       return(1, $r, $px) if ($t < 0 || ($s + $t) > 1);
407              
408             # return intersect within triangle
409 0           return(0, $r, $px);
410              
411             }
412              
413             # create gbd_ object from ICC profile
414             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
415             # returns: (ref_to_object)
416             sub new_fh {
417              
418             # get object class
419 0     0 0   my $class = shift();
420              
421             # create empty gbd_ object
422 0           my $self = [
423             {}, # header
424             [], # matrix
425             [] # offset
426             ];
427              
428             # verify 3 parameters
429 0 0         (@_ == 3) or croak('wrong number of parameters');
430              
431             # read gbd_ data from profile
432 0           _readICCgbd_($self, @_);
433              
434             # bless object
435 0           bless($self, $class);
436              
437             # return object reference
438 0           return($self);
439              
440             }
441              
442             # writes gbd_ object to ICC profile
443             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
444             sub write_fh {
445              
446             # verify 4 parameters
447 0 0   0 0   (@_ == 4) or croak('wrong number of parameters');
448              
449             # write gbd_ data to profile
450 0           goto &_writeICCgbd_;
451              
452             }
453              
454             # get tag size (for writing to profile)
455             # returns: (clut_size)
456             sub size {
457              
458             # get parameter
459 0     0 0   my $self = shift();
460              
461             # local variables
462 0           my ($p, $q, $size);
463              
464             # get number of pcs channels
465 0           $p = @{$self->[2][0]};
  0            
466              
467             # get number of device channels
468 0 0         $q = defined($self->[3][0]) ? @{$self->[3][0]} : 0;
  0            
469              
470             # set header size
471 0           $size = 20;
472              
473             # add face vertex IDs
474 0           $size += 12 * @{$self->[1]};
  0            
475              
476             # add vertex pcs values
477 0           $size += 4 * $p * @{$self->[2]};
  0            
478              
479             # add vertex device values (may be 0)
480 0           $size += 4 * $q * @{$self->[3]};
  0            
481              
482             # return size
483 0           return($size);
484              
485             }
486              
487             # print object contents to string
488             # format is an array structure
489             # parameter: ([format])
490             # returns: (string)
491             sub sdump {
492              
493             # get parameters
494 0     0 1   my ($self, $p) = @_;
495              
496             # local variables
497 0           my ($s, $fmt, $f, $v, $e);
498              
499             # resolve parameter to an array reference
500 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
501              
502             # get format string
503 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
504              
505             # set string to object ID
506 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
507              
508             # get stats
509 0           ($f, $v, $e) = _check_faces($self);
510              
511             # append stats
512 0           $s .= "faces: $f vertices: $v edges: $e\n";
513              
514             # return
515 0           return($s);
516              
517             }
518              
519             # check gamut faces
520             # parameters: (ref_to_object)
521             # returns: (faces, vertices, edges)
522             sub _check_faces {
523              
524             # get object reference
525 0     0     my $self = shift();
526              
527             # local variables
528 0           my (%v, %e, $p0, $p1, $p2);
529              
530             # for each face
531 0           for my $i (0 .. $#{$self->[1]}) {
  0            
532            
533             # get indices
534 0           $p0 = $self->[1][$i][0];
535 0           $p1 = $self->[1][$i][1];
536 0           $p2 = $self->[1][$i][2];
537            
538             # add vertices
539 0           $v{$p0}++;
540 0           $v{$p1}++;
541 0           $v{$p2}++;
542            
543             # add edges
544 0 0         $e{$p0 > $p1 ? "$p0:$p1" : "$p1:$p0"}++;
545 0 0         $e{$p1 > $p2 ? "$p1:$p2" : "$p2:$p1"}++;
546 0 0         $e{$p0 > $p2 ? "$p0:$p2" : "$p2:$p0"}++;
547            
548             }
549              
550             # return faces, vertices, edges
551 0           return(scalar(@{$self->[1]}), scalar(keys(%v)), scalar(keys(%e)));
  0            
552              
553             }
554              
555             # make spherical index
556             # parameters: (object_ref, point_inside_gamut, latitude_steps, longitude_steps)
557             sub _make_index {
558              
559             # get parameters
560 0     0     my ($self, $p0, $m, $n) = @_;
561              
562             # local variables
563 0           my ($f, $s, $length, $dc, $dot, $dxy);
564              
565             # for each face
566 0           for my $i (0 .. $#{$self->[1]}) {
  0            
567            
568             # for each coordinate
569 0           for my $j (0 .. 2) {
570            
571             # for each vertex
572 0           for my $k (0 .. 2) {
573            
574             # add value to face centroid
575 0           $f->[$j][$i] += $self->[2][$self->[1][$i][$k]][$j]/3;
576            
577             }
578            
579             # subtract internal point value
580 0           $f->[$j][$i] -= $p0->[$j];
581            
582             }
583            
584             # compute vector length
585 0           $length = sqrt($f->[0][$i]**2 + $f->[1][$i]**2 + $f->[2][$i]**2);
586            
587             # for each coordinate
588 0           for my $j (0 .. 2) {
589            
590             # normalize
591 0           $f->[$j][$i] /= $length;
592            
593             }
594            
595             }
596              
597             # for each x
598 0           for my $i (0 .. $m - 1) {
599            
600             # for each y
601 0           for my $j (0 .. $n - 1) {
602            
603             # compute spherical unit vector for cell[x][y]
604 0           $dc = sin(ICC::Shared::PI * ($i + 0.5)/$m);
605 0           $s->[$n * $i + $j][0] = cos(ICC::Shared::PI * ($i + 0.5)/$m);
606 0           $s->[$n * $i + $j][1] = -$dc * cos(2 * ICC::Shared::PI * (($j + 0.5)/$n));
607 0           $s->[$n * $i + $j][2] = -$dc * sin(2 * ICC::Shared::PI * (($j + 0.5)/$n));
608            
609             }
610            
611             }
612              
613             # compute dot products [s x 3] * [3 x f] = [s x f]
614 0           $dot = ICC::Support::Lapack::mat_xplus($s, $f);
615              
616             # initialize index
617 0           undef($self->[5]);
618              
619             # for each x
620 0           for my $i (0 .. $m - 1) {
621            
622             # for each y
623 0           for my $j (0 .. $n - 1) {
624            
625             # get dot product list for cell[x][y]
626 0           $dxy = $dot->[$n * $i + $j];
627            
628             # compute face ID list, sorted by dot product
629 0           $self->[5][$i][$j] = [map {$_->[0]} sort {$b->[1] <=> $a->[1]} map {[$_, $dxy->[$_]]} (0 .. $#{$self->[1]})];
  0            
  0            
  0            
  0            
630            
631             }
632            
633             }
634              
635             # save internal point in header hash
636 0           $self->[0]{'p0'} = $p0;
637              
638             }
639              
640             # make new gbd_ object from attribute hash
641             # hash keys are: ('vertex', 'pcs', 'device')
642             # object elements not specified in the hash are unchanged
643             # parameters: (ref_to_object, ref_to_attribute_hash)
644             sub _new_from_hash {
645              
646             # get parameters
647 0     0     my ($self, $hash) = @_;
648              
649             # local variables
650 0           my ($value, $f, $v, $e);
651              
652             # if 'vertex' key defined
653 0 0         if (defined($hash->{'vertex'})) {
654            
655             # get value
656 0           $value = $hash->{'vertex'};
657            
658             # if reference to a 2-D array
659 0 0 0       if (ref($value) eq 'ARRAY' && @{$value} == grep {ref() eq 'ARRAY'} @{$value}) {
  0 0          
  0            
  0            
660            
661             # set vertex to clone of array
662 0           $self->[1] = bless(Storable::dclone($value), 'Math::Matrix');
663            
664             # if a reference to a Math::Matrix object
665             } elsif (UNIVERSAL::isa($value, 'Math::Matrix')) {
666            
667             # set vertex to object
668 0           $self->[1] = $value;
669            
670             } else {
671            
672             # wrong data type
673 0           croak('wrong \'vertex\' data type');
674            
675             }
676            
677             # verify number of faces
678 0 0         (@{$self->[1]} >= 4) or croak('number of faces < 4');
  0            
679            
680             # verify number of vertices per face
681 0 0         (@{$self->[1]} == 3) or croak('number of vertices per face <> 3');
  0            
682            
683             # check gamut faces
684 0           ($f, $v, $e) = _check_faces($self);
685            
686             # verify closed shape using Euler's formula
687 0 0         ($f + $v - $e == 2) or carp('not a closed shape');
688            
689             }
690              
691             # if 'pcs' key defined
692 0 0         if (defined($hash->{'pcs'})) {
693            
694             # get value
695 0           $value = $hash->{'pcs'};
696            
697             # if reference to a 2-D array
698 0 0 0       if (ref($value) eq 'ARRAY' && @{$value} == grep {ref() eq 'ARRAY'} @{$value}) {
  0 0          
  0            
  0            
699            
700             # set pcs to clone of array
701 0           $self->[1] = bless(Storable::dclone($value), 'Math::Matrix');
702            
703             # if a reference to a Math::Matrix object
704             } elsif (UNIVERSAL::isa($value, 'Math::Matrix')) {
705            
706             # set pcs to object
707 0           $self->[2] = $value;
708            
709             } else {
710            
711             # wrong data type
712 0           croak('wrong \'pcs\' data type');
713            
714             }
715            
716             # verify number of vertices
717 0 0         (@{$self->[2]} >= 4) or croak('number of vertices < 4');
  0            
718            
719             # verify number of pcs channels
720 0 0         (@{$self->[2][0]} >= 3) or croak('number of pcs channels < 3');
  0            
721            
722             }
723              
724             # if 'device' key defined
725 0 0         if (defined($hash->{'device'})) {
726            
727             # get value
728 0           $value = $hash->{'device'};
729            
730             # if reference to a 2-D array
731 0 0 0       if (ref($value) eq 'ARRAY' && @{$value} == grep {ref() eq 'ARRAY'} @{$value}) {
  0 0          
  0            
  0            
732            
733             # set device to clone of array
734 0           $self->[1] = bless(Storable::dclone($value), 'Math::Matrix');
735            
736             # if a reference to a Math::Matrix object
737             } elsif (UNIVERSAL::isa($value, 'Math::Matrix')) {
738            
739             # set device to object
740 0           $self->[3] = $value;
741            
742             } else {
743            
744             # wrong data type
745 0           croak('wrong \'device\' data type');
746            
747             }
748            
749             # verify number of vertices
750 0 0         (@{$self->[3]} >= 4) or croak('number of vertices < 4');
  0            
751            
752             # verify number of pcs channels
753 0 0 0       (@{$self->[3][0]} >= 1 && @{$self->[3][0]} <= 16) or croak('number of device channels < 1 or > 16');
  0            
  0            
754            
755             }
756            
757             # verify pcs array size
758 0 0 0       (@{$self->[2]} == 0 || @{$self->[2]} == $v) or croak('pcs and face arrays have different number of vertices');
  0            
  0            
759            
760             # if both pcs and device arrays were supplied
761 0 0 0       if (defined($hash->{'pcs'}) && defined($hash->{'device'})) {
762            
763             # verify pcs and device arrays have same number of vertices
764 0 0         (@{$self->[2]} == @{$self->[3]}) or croak('pcs and device arrays are different sizes');
  0            
  0            
765            
766             }
767            
768             }
769              
770             # read gbd_ tag from ICC profile
771             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
772             sub _readICCgbd_ {
773              
774             # get parameters
775 0     0     my ($self, $parent, $fh, $tag) = @_;
776              
777             # local variables
778 0           my ($buf, $p, $q, $v, $f, $bytes);
779              
780             # save tag signature
781 0           $self->[0]{'signature'} = $tag->[0];
782              
783             # seek start of tag
784 0           seek($fh, $tag->[1], 0);
785              
786             # read tag header
787 0           read($fh, $buf, 20);
788              
789             # unpack header
790 0           ($p, $q, $v, $f) = unpack('x8 n2 N2', $buf);
791              
792             # for each face
793 0           for my $i (0 .. $f - 1) {
794            
795             # read vertex IDs
796 0           read($fh, $buf, 12);
797            
798             # unpack the values
799 0           $self->[1][$i] = [unpack('N3', $buf)];
800            
801             }
802              
803             # bless to Math::Matrix object
804 0           bless($self->[1], 'Math::Matrix');
805              
806             # compute the buffer size
807 0           $bytes = 4 * $p;
808              
809             # for each vertex
810 0           for my $i (0 .. $v - 1) {
811            
812             # read vertex PCS values
813 0           read($fh, $buf, $bytes);
814            
815             # unpack the values
816 0           $self->[2][$i] = [unpack('f>*', $buf)];
817            
818             }
819              
820             # bless to Math::Matrix object
821 0           bless($self->[2], 'Math::Matrix');
822              
823             # if there are device values
824 0 0         if ($bytes = 4 * $q) {
825            
826             # for each vertex
827 0           for my $i (0 .. $v - 1) {
828            
829             # read vertex device values
830 0           read($fh, $buf, $bytes);
831            
832             # unpack the values
833 0           $self->[3][$i] = [unpack('f>*', $buf)];
834            
835             }
836            
837             # bless to Math::Matrix object
838 0           bless($self->[3], 'Math::Matrix');
839            
840             }
841            
842             }
843              
844             # write gbd_ tag to ICC profile
845             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
846             sub _writeICCgbd_ {
847              
848             # get parameters
849 0     0     my ($self, $parent, $fh, $tag) = @_;
850              
851             # local variables
852 0           my ($p, $q, $v, $f);
853              
854             # get number of PCS channels
855 0           $p = @{$self->[2][0]};
  0            
856              
857             # get number of device channels
858 0 0         $q = defined($self->[3][0]) ? @{$self->[3][0]} : 0;
  0            
859              
860             # get number of vertices
861 0           $v = @{$self->[2]};
  0            
862              
863             # get number of faces
864 0           $f = @{$self->[1]};
  0            
865              
866             # validate number PCS channels (3 and up)
867 0 0         ($p >= 3) or croak('unsupported number of input channels');
868              
869             # validate number device channels (1 to 15)
870 0 0 0       ($q > 0 && $q < 16) or croak('unsupported number of output channels');
871              
872             # seek start of tag
873 0           seek($fh, $tag->[1], 0);
874              
875             # write tag header
876 0           print $fh pack('a4 x4 n2 N2', 'gbd ', $p, $q, $v, $f);
877              
878             # for each face
879 0           for my $i (0 .. $f - 1) {
880            
881             # write face vertex IDs
882 0           print $fh pack('N3', @{$self->[1][$i]});
  0            
883            
884             }
885              
886             # for each vertex
887 0           for my $i (0 .. $v - 1) {
888            
889             # write vertex PCS values
890 0           print $fh pack('f>*', @{$self->[2][$i]});
  0            
891            
892             }
893              
894             # if there are vertex device values
895 0 0         if ($q) {
896            
897             # for each vertex
898 0           for my $i (0 .. $v - 1) {
899            
900             # write vertex device values
901 0           print $fh pack('f>*', @{$self->[3][$i]});
  0            
902            
903             }
904            
905             }
906            
907             }
908              
909             1;