File Coverage

blib/lib/Image/MetaData/JPEG/access/comments.pl
Criterion Covered Total %
statement 61 61 100.0
branch 22 26 84.6
condition 2 3 66.6
subroutine 12 12 100.0
pod 7 8 87.5
total 104 110 94.5


line stmt bran cond sub pod time code
1             ###########################################################
2             # A Perl package for showing/modifying JPEG (meta)data. #
3             # Copyright (C) 2004,2005,2006 Stefano Bettelli #
4             # See the COPYING and LICENSE files for license terms. #
5             ###########################################################
6             package Image::MetaData::JPEG;
7 14     14   65 use Image::MetaData::JPEG::Segment;
  14         18  
  14         365  
8 14     14   51 no integer;
  14         18  
  14         58  
9 14     14   242 use strict;
  14         22  
  14         323  
10 14     14   54 use warnings;
  14         16  
  14         9400  
11              
12             ###########################################################
13             # This method accepts a string and returns a list whose #
14             # elements are not larger than the length limit imposed #
15             # by a JPEG segment: a segment cannot have a length which #
16             # couldn't be written in a 2-byte unsigned integer, that #
17             # is 2^16 - 1; since the byte count must be written in #
18             # this space, the real comment is limited to 2^16 - 3. #
19             # The length of all but the last element in the list is #
20             # maximal. The input string is not changed. Note that "" #
21             # maps to (""), while an undefined value maps to (). So, #
22             # it is possible to specify an empty comment. #
23             ###########################################################
24             { my $max_length = 2**16 - 3;
25             sub split_comment_string {
26 26 100   26 0 53 return () unless defined $_[0];
27 18         48 map { substr $_[0], $max_length*$_, $max_length }
  24         312  
28             0 .. (-1 + length $_[0]) / $max_length;
29             }
30             }
31              
32             ###########################################################
33             # This method returns the number of comment segments in #
34             # the picture (it should be as fast as possible). #
35             ###########################################################
36             sub get_number_of_comments {
37 10     10 1 34 my ($this) = @_;
38             # return the length of the output of this method
39 10         17 return scalar $this->get_segments('COM');
40             }
41              
42             ###########################################################
43             # This method returns a list, with an element for each #
44             # comment block in the file (the element contains the #
45             # comment string). Note that an empty list can be retur- #
46             # ned (in case there are no comment blocks). #
47             ###########################################################
48             sub get_comments {
49 14     14 1 906 my ($this) = @_;
50             # loop over all segments, and return the appropriate
51             # field of those which are comments.
52 14         31 my @com_segs = $this->get_segments('COM');
53 14         25 return map { $_->search_record_value('Comment') } @com_segs;
  47         97  
54             }
55              
56             ###########################################################
57             # This method adds one or more new comment segments to #
58             # the JPEG file, based on the string passed by the user. #
59             # If there is already at least one comment segment, the #
60             # new segments are created right after the last one. #
61             # Otherwise, the standard position search is applied. #
62             # ------------------------------------------------------- #
63             # In case the passed string is too big (there is a 64KB #
64             # limit in JPEG segments), it is broken down in smaller #
65             # strings and multiple "Comment" segments are inserted in #
66             # the file (they are contiguous). #
67             ###########################################################
68             sub add_comment {
69 13     13 1 991 my ($this, $string) = @_;
70             # create one or more comment blocks, based on the user
71             # string; the string must be split if it is too long.
72 14         43 my @new_comments =
73 13         27 map { new Image::MetaData::JPEG::Segment("COM", \ $_) }
74             split_comment_string($string);
75             # get the list of comment indexes
76 13         35 my @indexes = $this->get_segments('COM', 'INDEXES');
77             # our position is right after the last comment
78 13 100       32 my $position = @indexes ? 1 + $indexes[$#indexes] : undef;
79             # actually insert the comments (we don't need update() here);
80             # if position is undefined, the standard search is used
81 13         31 $this->insert_segments(\ @new_comments, $position);
82             }
83              
84             ###########################################################
85             # This method replaces the $index-th comment segment with #
86             # one or more new segments based on $string (the index of #
87             # the first comment segment is 0). If $string is too big #
88             # (see add_comment), it is broken down and multiple seg- #
89             # ments are created. If $string is undef, the comment #
90             # segment is erased. If $index is out-of-bound, only a #
91             # warning is printed. #
92             ###########################################################
93             sub set_comment {
94 15     15 1 1312 my ($this, $index, $string) = @_;
95             # return immediately if $index is negative or undefined
96 15 50       28 return $this->warn('Undefined $index') unless defined $index;
97 15 100       33 return $this->warn("Negative index ($index)") if $index < 0;
98             # get the list of comment segment indexes
99 14         28 my @indexes = $this->get_segments('COM', 'INDEXES');
100             # if $index is out of bound, warn and return.
101 14 100       38 return $this->warn("Index $index out of bound [0,$#indexes]")
102             if ($#indexes < $index);
103             # otherwise, set an index to the target comment segment
104 13         15 my $position = $indexes[$index];
105             # create one or more comment blocks, based on the user
106             # string; the string must be split if it is too long.
107 10         33 my @new_comments =
108 13         18 map { new Image::MetaData::JPEG::Segment('COM', \ $_) }
109             split_comment_string($string);
110             # replace the target segment with the new segments created
111             # from the user string; @new_comments is the void list if
112             # $string is undefined (this stands for comment deletion).
113             # Since all comments are deleted or added, but not modified,
114             # there is no need to call update here!
115 13         31 $this->insert_segments(\ @new_comments, $position, 1);
116             }
117              
118             ###########################################################
119             # This method eliminates the $index-th comment segment #
120             # (first index is 0). It is only a shortcut for the more #
121             # general set_comment (called with $string = undef). #
122             ###########################################################
123             sub remove_comment {
124 9     9 1 18 my ($this, $index) = @_;
125             # call set_comment with an undefined string
126 9         15 $this->set_comment($index, undef);
127             }
128              
129             ###########################################################
130             # This method (a wrapper around the drop_segments method) #
131             # eliminates all comments currently present in the pic. #
132             ###########################################################
133             sub remove_all_comments {
134 3     3 1 1360 my ($this) = @_;
135             # use this more general method
136 3         13 $this->drop_segments('^COM$');
137             }
138              
139             ###########################################################
140             # This method joins some comments into a single one, with #
141             # the supplied separation string. This utility is neces- #
142             # sary because there are readers out there which do not #
143             # read past the first comment. This method overwrites the #
144             # first comment selected by the arguments and delete the #
145             # others. A warning is issued for each illegal comment #
146             # index (undefined, not a number, out of range). #
147             # The final comment length is checked (<64Kb). #
148             # ------------------------------------------------------- #
149             # If no separation string is provided, it defaults to \n. #
150             # If no index is provided in @selection, it is assumed #
151             # that the method must join all the comments into the #
152             # first one, and delete the others. #
153             ###########################################################
154             sub join_comments {
155 6     6 1 2642 my ($this, $separation, @selection) = @_;
156             # get all the comment indexes
157 6         17 my @indexes = $this->get_segments('COM', 'INDEXES');
158             # get all the comment strings
159 6         12 my @comments = $this->get_comments();
160             # an undefined separation string defaults to "\n"
161 6 50       11 $separation = "\n" unless defined $separation;
162             # an undefined @selection stands for "all the indexes"
163 6 100       14 @selection = 0..$#indexes unless @selection;
164             # discard the elements of @selection which do not make
165             # sense, and leave the others in ascending numerical order
166 11         14 @selection = sort {$a <=> $b} map {
  15         15  
167 6         7 my $error = undef;
168 15 100 66     71 if (! defined $_) { $error = "Undefined comment index"; }
  1 100       2  
    100          
169 2         5 elsif ($_ =~ /[^\d]/) { $error = "'$_' not a whole number"; }
170 1         3 elsif ($_<0 || $_>$#indexes) { $error = "index $_ out of range"; }
171 15 100       32 $this->die("$error: discarding index") if defined $error;
172 11 50       34 defined $error ? () : $_;
173             } @selection;
174             # return immediately if @selection is empty
175 2 50       6 $this->die('No valid comment indexes') unless @selection;
176             # concatenate valid comments in a single string (write a copy
177             # of the separation string between every two comments).
178 2         3 my $joint_comment = join $separation, map { $comments[$_] } @selection;
  8         179  
179             # extract the first comment segment index in the selection list
180             # as the target segment index. Then remove all other comments;
181             # be careful to remove comments starting from higher indexes!
182 2         8 my $target_index = shift @selection;
183 2         5 $this->remove_comment($_) for (sort {$b <=> $a} @selection);
  5         10  
184             # replace the target comment with $joint_comment
185 2         5 $this->set_comment($target_index, $joint_comment);
186             }
187              
188             # successful package load
189             1;