File Coverage

blib/lib/Image/ExifTool/PSP.pm
Criterion Covered Total %
statement 61 67 91.0
branch 19 30 63.3
condition 3 9 33.3
subroutine 6 6 100.0
pod 0 2 0.0
total 89 114 78.0


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: PSP.pm
3             #
4             # Description: Read Paint Shop Pro meta information
5             #
6             # Revisions: 2010/01/23 - P. Harvey Created
7             #
8             # References: 1) http://www.jasc.com/support/kb/articles/pspspec.asp
9             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::PSP;
12              
13 1     1   4343 use strict;
  1         3  
  1         34  
14 1     1   6 use vars qw($VERSION);
  1         2  
  1         38  
15 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         215  
16 1     1   1318 use Image::ExifTool::Exif;
  1         6  
  1         1137  
17              
18             $VERSION = '1.05';
19              
20             sub ProcessExtData($$$);
21              
22             # PSP info
23             %Image::ExifTool::PSP::Main = (
24             GROUPS => { 2 => 'Image' },
25             VARS => { ALPHA_FIRST => 1 },
26             NOTES => q{
27             Tags extracted from Paint Shop Pro images (PSP, PSPIMAGE, PSPFRAME,
28             PSPSHAPE, PSPTUBE and TUB extensions).
29             },
30             # FileVersions:
31             # 3.0 => PSP 5
32             # 4.0 => PSP 6
33             # 5.0 => PSP 7
34             # 6.0 => PSP 8
35             # 7.0 => PSP 9
36             # ? => PSP X
37             # ? => PSP X1 (is this the same as X?)
38             # ? => PSP X2
39             # 10.0 => PSP X3 (= PSP 13)
40             FileVersion => { PrintConv => '$val=~tr/ /./; $val' },
41             0 => [
42             {
43             Condition => '$$self{PSPFileVersion} > 3',
44             Name => 'ImageInfo',
45             SubDirectory => {
46             TagTable => 'Image::ExifTool::PSP::Image',
47             Start => 4,
48             },
49             },
50             {
51             Name => 'ImageInfo',
52             SubDirectory => {
53             TagTable => 'Image::ExifTool::PSP::Image',
54             },
55             },
56             ],
57             1 => {
58             Name => 'CreatorInfo',
59             SubDirectory => { TagTable => 'Image::ExifTool::PSP::Creator' },
60             },
61             10 => {
62             Name => 'ExtendedInfo',
63             SubDirectory => { TagTable => 'Image::ExifTool::PSP::Ext' },
64             },
65             # this is inside the composite image bank block (16), which I don't want to parse...
66             #18 => {
67             # Name => 'PreviewImage',
68             # Groups => { 2 => 'Preview' },
69             # RawConv => '$self->ValidateImage(\$val,$tag)',
70             #},
71             );
72              
73             # the PSP image block
74             %Image::ExifTool::PSP::Image = (
75             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
76             GROUPS => { 2 => 'Image' },
77             0 => { Name => 'ImageWidth', Format => 'int32u' },
78             4 => { Name => 'ImageHeight', Format => 'int32u' },
79             8 => { Name => 'ImageResolution', Format => 'double' },
80             16 => {
81             Name => 'ResolutionUnit',
82             Format => 'int8u',
83             PrintConv => {
84             0 => 'None',
85             1 => 'inches',
86             2 => 'cm',
87             },
88             },
89             17 => {
90             Name => 'Compression',
91             Format => 'int16u',
92             PrintConv => {
93             0 => 'None',
94             1 => 'RLE',
95             2 => 'LZ77',
96             3 => 'JPEG',
97             },
98             },
99             19 => { Name => 'BitsPerSample',Format => 'int16u' },
100             21 => { Name => 'Planes', Format => 'int16u' },
101             23 => { Name => 'NumColors', Format => 'int32u' },
102             );
103              
104             # the PSP creator data block
105             %Image::ExifTool::PSP::Creator = (
106             PROCESS_PROC => \&ProcessExtData,
107             GROUPS => { 2 => 'Image' },
108             PRIORITY => 0, # prefer EXIF if it exists
109             0 => 'Title',
110             1 => {
111             Name => 'CreateDate',
112             Format => 'int32u',
113             Groups => { 2 => 'Time' },
114             ValueConv => 'Image::ExifTool::ConvertUnixTime($val,1)',
115             PrintConv => '$self->ConvertDateTime($val)',
116             },
117             2 => {
118             Name => 'ModifyDate',
119             Format => 'int32u',
120             Groups => { 2 => 'Time' },
121             ValueConv => 'Image::ExifTool::ConvertUnixTime($val,1)',
122             PrintConv => '$self->ConvertDateTime($val)',
123             },
124             3 => {
125             Name => 'Artist',
126             Groups => { 2 => 'Author' },
127             },
128             4 => {
129             Name => 'Copyright',
130             Groups => { 2 => 'Author' },
131             },
132             5 => 'Description',
133             6 => {
134             Name => 'CreatorAppID',
135             Format => 'int32u',
136             PrintConv => {
137             0 => 'Unknown',
138             1 => 'Paint Shop Pro',
139             },
140             },
141             7 => {
142             Name => 'CreatorAppVersion',
143             Format => 'int8u',
144             Count => 4,
145             ValueConv => 'join(" ",reverse split " ", $val)', # low byte first
146             PrintConv => '$val=~tr/ /./; $val',
147             },
148             );
149              
150             # the PSP extended data block
151             %Image::ExifTool::PSP::Ext = (
152             PROCESS_PROC => \&ProcessExtData,
153             GROUPS => { 2 => 'Image' },
154             3 => {
155             Name => 'EXIFInfo', #(don't change this name, it is used in the code)
156             SubDirectory => { TagTable => 'Image::ExifTool::Exif::Main' },
157             },
158             );
159              
160             #------------------------------------------------------------------------------
161             # Extract information from the extended data block
162             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
163             # Returns: 1 on success
164             sub ProcessExtData($$$)
165             {
166 2     2 0 5 my ($et, $dirInfo, $tagTablePtr) = @_;
167 2         5 my $dataPt = $$dirInfo{DataPt};
168 2         5 my $dirLen = $$dirInfo{DirLen};
169 2         3 my $pos = 0;
170             # loop through sub-blocks
171 2         7 while ($pos + 10 < $dirLen) {
172 11 50       28 unless (substr($$dataPt, $pos, 4) eq "~FL\0") {
173 0         0 $et->Warn('Lost synchronization while reading sub blocks');
174 0         0 last;
175             }
176 11         28 my $tag = Get16u($dataPt, $pos + 4);
177 11         30 my $len = Get32u($dataPt, $pos + 6);
178 11         23 $pos += 10 + $len;
179 11 50       21 if ($pos > $dirLen) {
180 0         0 $et->Warn("Truncated sub block ID=$tag len=$len");
181 0         0 last;
182             }
183 11 100       29 next unless $$tagTablePtr{$tag};
184 9 50       24 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag) or next;
185 9         17 my $start = $pos - $len;
186 9 100       21 unless ($$tagInfo{Name} eq 'EXIFInfo') {
187             $et->HandleTag($tagTablePtr, $tag, undef,
188             TagInfo => $tagInfo,
189             DataPt => $dataPt,
190             DataPos => $$dirInfo{DataPos},
191 8         34 DataLen => length $$dataPt,
192             Start => $start,
193             Size => $len,
194             );
195 8         20 next;
196             }
197             # validate EXIF block header and set byte order
198 1 50 33     8 next unless $len > 14 and substr($$dataPt, $pos - $len, 6) eq "Exif\0\0";
199 1 50       4 next unless SetByteOrder(substr($$dataPt, $start + 6, 2));
200             # This is REALLY annoying... They use a standard TIFF offset to point to
201             # the first IFD, but after that the offsets are relative to the start of
202             # the IFD instead of the TIFF base, which means that I must handle it as a
203             # special case. Dumb, dumb...
204 1         3 $start += 14;
205             my %dirInfo = (
206             DirName => 'EXIF',
207             Parent => 'PSP',
208             DataPt => $dataPt,
209             DataPos => -$start, # data position relative to Base
210             DataLen => length $$dataPt,
211             DirStart => $start,
212             Base => $start + $$dirInfo{DataPos}, # absolute base offset
213 1         9 Multi => 0,
214             );
215 1         4 my $exifTable = GetTagTable($$tagInfo{SubDirectory}{TagTable});
216 1         6 Image::ExifTool::Exif::ProcessExif($et, \%dirInfo, $exifTable);
217 1         3 SetByteOrder('II');
218             }
219 2         5 return 1;
220             }
221              
222             #------------------------------------------------------------------------------
223             # Extract information from a PSP file
224             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
225             # Returns: 1 on success, 0 if this wasn't a valid PSP file
226             sub ProcessPSP($$)
227             {
228 1     1 0 5 my ($et, $dirInfo) = @_;
229 1         3 my $raf = $$dirInfo{RAF};
230 1         3 my ($buff, $tag, $len, $err);
231 1 50 33     4 return 0 unless $raf->Read($buff, 32) == 32 and
      33        
232             $buff eq "Paint Shop Pro Image File\x0a\x1a\0\0\0\0\0" and
233             $raf->Read($buff, 4) == 4;
234 1         17 $et->SetFileType();
235 1         20 SetByteOrder('II');
236 1         5 my $tagTablePtr = GetTagTable('Image::ExifTool::PSP::Main');
237 1         16 my @a = unpack('v*', $buff);
238             # figure out block header length for this format PSP file
239 1 50       16 my $hlen = $a[0] > 3 ? 10 : 14;
240 1         7 $$et{PSPFileVersion} = $a[0]; # save for use in Condition
241 1         9 $et->HandleTag($tagTablePtr, FileVersion => "@a");
242             # loop through blocks in file
243 1         3 my $pos = 36;
244 1         2 for (;;) {
245 6 100       21 last unless $raf->Read($buff, $hlen) == $hlen;
246 5 50       32 unless ($buff =~ /^~BK\0/) {
247 0         0 $et->Warn('Lost synchronization while reading main PSP blocks');
248 0         0 last;
249             }
250 5         15 $tag = Get16u(\$buff, 4);
251 5         15 $len = Get32u(\$buff, $hlen - 4);
252 5         10 $pos += $hlen + $len;
253 5 100       18 unless ($$tagTablePtr{$tag}) {
254 2 50       6 $raf->Seek($len, 1) or $err=1, last;
255 2         5 next;
256             }
257 3 50       9 $raf->Read($buff, $len) == $len or $err=1, last;
258 3         15 $et->HandleTag($tagTablePtr, $tag, $buff,
259             DataPt => \$buff,
260             DataPos => $pos - $len,
261             Size => $len,
262             );
263             }
264 1 50       4 $err and $et->Warn("Truncated main block ID=$tag len=$len");
265 1         4 return 1;
266             }
267              
268             1; # end
269              
270             __END__