File Coverage

blib/lib/Image/ExifTool/GIMP.pm
Criterion Covered Total %
statement 50 60 83.3
branch 14 28 50.0
condition 5 11 45.4
subroutine 5 5 100.0
pod 0 2 0.0
total 74 106 69.8


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: GIMP.pm
3             #
4             # Description: Read meta information from GIMP XCF images
5             #
6             # Revisions: 2010/10/05 - P. Harvey Created
7             # 2018/08/21 - PH Updated to current XCF specification (v013)
8             #
9             # References: 1) GIMP source code
10             # 2) https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/xcf.txt
11             #------------------------------------------------------------------------------
12              
13             package Image::ExifTool::GIMP;
14              
15 1     1   4419 use strict;
  1         3  
  1         35  
16 1     1   7 use vars qw($VERSION);
  1         3  
  1         39  
17 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         3  
  1         1192  
18              
19             $VERSION = '1.03';
20              
21             sub ProcessParasites($$$);
22              
23             # GIMP XCF properties (ref 2)
24             %Image::ExifTool::GIMP::Main = (
25             GROUPS => { 2 => 'Image' },
26             VARS => { ALPHA_FIRST => 1 },
27             NOTES => q{
28             The GNU Image Manipulation Program (GIMP) writes these tags in its native
29             XCF (eXperimental Computing Facilty) images.
30             },
31             header => { SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Header' } },
32             # recognized properties
33             # 1 - ColorMap
34             # 17 - SamplePoints? (doc says 17 is also "PROP_SAMPLE_POINTS"??)
35             17 => {
36             Name => 'Compression',
37             Format => 'int8u',
38             PrintConv => {
39             0 => 'None',
40             1 => 'RLE Encoding',
41             2 => 'Zlib',
42             3 => 'Fractal',
43             },
44             },
45             # 18 - Guides
46             19 => {
47             Name => 'Resolution',
48             SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Resolution' },
49             },
50             20 => {
51             Name => 'Tattoo',
52             Format => 'int32u',
53             },
54             21 => {
55             Name => 'Parasites',
56             SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Parasite' },
57             },
58             22 => {
59             Name => 'Units',
60             Format => 'int32u',
61             PrintConv => {
62             1 => 'Inches',
63             2 => 'mm',
64             3 => 'Points',
65             4 => 'Picas',
66             },
67             },
68             # 23 Paths
69             # 24 UserUnit
70             # 25 Vectors
71             );
72              
73             # information extracted from the XCF file header (ref 2)
74             %Image::ExifTool::GIMP::Header = (
75             GROUPS => { 2 => 'Image' },
76             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
77             9 => {
78             Name => 'XCFVersion',
79             Format => 'string[5]',
80             DataMember => 'XCFVersion',
81             RawConv => '$$self{XCFVersion} = $val',
82             PrintConv => {
83             'file' => '0',
84             'v001' => '1',
85             'v002' => '2',
86             OTHER => sub { my $val = shift; $val =~ s/^v0*//; return $val },
87             },
88             },
89             14 => { Name => 'ImageWidth', Format => 'int32u' },
90             18 => { Name => 'ImageHeight', Format => 'int32u' },
91             22 => {
92             Name => 'ColorMode',
93             Format => 'int32u',
94             PrintConv => {
95             0 => 'RGB Color',
96             1 => 'Grayscale',
97             2 => 'Indexed Color',
98             },
99             },
100             # 26 - [XCF 4 or later] Precision
101             );
102              
103             # XCF resolution data (property type 19) (ref 2)
104             %Image::ExifTool::GIMP::Resolution = (
105             GROUPS => { 2 => 'Image' },
106             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
107             FORMAT => 'float',
108             0 => 'XResolution',
109             1 => 'YResolution',
110             );
111              
112             # XCF "Parasite" data (property type 21) (ref 1/PH)
113             %Image::ExifTool::GIMP::Parasite = (
114             GROUPS => { 2 => 'Image' },
115             PROCESS_PROC => \&ProcessParasites,
116             'gimp-comment' => {
117             Name => 'Comment',
118             Format => 'string',
119             },
120             'exif-data' => {
121             Name => 'ExifData',
122             SubDirectory => {
123             TagTable => 'Image::ExifTool::Exif::Main',
124             ProcessProc => \&Image::ExifTool::ProcessTIFF,
125             Start => 6, # starts after "Exif\0\0" header
126             },
127             },
128             'jpeg-exif-data' => { # (deprecated, untested)
129             Name => 'JPEGExifData',
130             SubDirectory => {
131             TagTable => 'Image::ExifTool::Exif::Main',
132             ProcessProc => \&Image::ExifTool::ProcessTIFF,
133             Start => 6,
134             },
135             },
136             'iptc-data' => { # (untested)
137             Name => 'IPTCData',
138             SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' },
139             },
140             'icc-profile' => {
141             Name => 'ICC_Profile',
142             SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
143             },
144             'icc-profile-name' => {
145             Name => 'ICCProfileName',
146             Format => 'string',
147             },
148             'gimp-metadata' => {
149             Name => 'XMP',
150             SubDirectory => {
151             TagTable => 'Image::ExifTool::XMP::Main',
152             Start => 10, # starts after "GIMP_XMP_1" header
153             },
154             },
155             'gimp-image-metadata' => {
156             Name => 'XML',
157             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
158             },
159             # Seen, but not yet decoded:
160             # gimp-image-grid
161             # jpeg-settings
162             );
163              
164             #------------------------------------------------------------------------------
165             # Read information in a GIMP XCF parasite data (ref PH)
166             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
167             # Returns: 1 on success
168             sub ProcessParasites($$$)
169             {
170 1     1 0 3 my ($et, $dirInfo, $tagTablePtr) = @_;
171 1   33     5 my $unknown = $et->Options('Unknown') || $et->Options('Verbose');
172 1         3 my $dataPt = $$dirInfo{DataPt};
173 1   50     938 my $pos = $$dirInfo{DirStart} || 0;
174 1         5 my $end = length $$dataPt;
175 1         6 $et->VerboseDir('Parasites', undef, $end);
176 1         2 for (;;) {
177 5 100       15 last if $pos + 4 > $end;
178 4         13 my $size = Get32u($dataPt, $pos); # length of tag string
179 4         11 $pos += 4;
180 4 50       12 last if $pos + $size + 8 > $end;
181 4         10 my $tag = substr($$dataPt, $pos, $size);
182 4         7 $pos += $size;
183 4         29 $tag =~ s/\0.*//s; # trim at null terminator
184             # my $flags = Get32u($dataPt, $pos); (ignore flags)
185 4         12 $size = Get32u($dataPt, $pos + 4); # length of data
186 4         9 $pos += 8;
187 4 50       14 last if $pos + $size > $end;
188 4 50 66     43 if (not $$tagTablePtr{$tag} and $unknown) {
189 0         0 my $name = $tag;
190 0         0 $name =~ tr/-_A-Za-z0-9//dc;
191 0         0 $name =~ s/^gimp-//;
192 0 0       0 next unless length $name;
193 0         0 $name = ucfirst $name;
194 0         0 $name =~ s/([a-z])-([a-z])/$1\u$2/g;
195 0 0       0 $name = "GIMP-$name" unless length($name) > 1;
196 0         0 AddTagToTable($tagTablePtr, $tag, { Name => $name, Unknown => 1 });
197             }
198 4         27 $et->HandleTag($tagTablePtr, $tag, undef,
199             DataPt => $dataPt,
200             Start => $pos,
201             Size => $size,
202             );
203 4         9 $pos += $size;
204             }
205 1         3 return 1;
206             }
207              
208             #------------------------------------------------------------------------------
209             # Read information in a GIMP XCF document
210             # Inputs: 0) ExifTool ref, 1) dirInfo ref
211             # Returns: 1 on success, 0 if this wasn't a valid XCF file
212             sub ProcessXCF($$)
213             {
214 1     1 0 5 my ($et, $dirInfo) = @_;
215 1         3 my $raf = $$dirInfo{RAF};
216 1         1 my $buff;
217              
218 1 50       5 return 0 unless $raf->Read($buff, 26) == 26;
219 1 50       6 return 0 unless $buff =~ /^gimp xcf /;
220              
221 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::GIMP::Main');
222 1         5 my $verbose = $et->Options('Verbose');
223 1         6 $et->SetFileType();
224 1         6 SetByteOrder('MM');
225              
226             # process the XCF header
227 1         4 $et->HandleTag($tagTablePtr, 'header', $buff);
228              
229             # skip over precision for XCV version 4 or later
230 1 50 33     5 $raf->Seek(4, 1) if $$et{XCFVersion} =~ /^v0*(\d+)/ and $1 >= 4;
231              
232             # loop through image properties
233 1         2 for (;;) {
234 6 50       26 $raf->Read($buff, 8) == 8 or last;
235 6 100       19 my $tag = Get32u(\$buff, 0) or last;
236 5         18 my $size = Get32u(\$buff, 4);
237 5 50       13 $verbose and $et->VPrint(0, "XCF property $tag ($size bytes):\n");
238 5 50       15 unless ($$tagTablePtr{$tag}) {
239 0         0 $raf->Seek($size, 1);
240 0         0 next;
241             }
242 5 50       15 $raf->Read($buff, $size) == $size or last;
243 5         15 $et->HandleTag($tagTablePtr, $tag, undef,
244             DataPt => \$buff,
245             DataPos => $raf->Tell() - $size,
246             Size => $size,
247             );
248             }
249 1         4 return 1;
250             }
251              
252             1; # end
253              
254             __END__