| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 2 |  |  |  |  |  |  | # File:         MIFF.pm | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # Description:  Read Magick Image File Format meta information | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Revisions:    06/10/2005 - P. Harvey Created | 
| 7 |  |  |  |  |  |  | # | 
| 8 |  |  |  |  |  |  | # References:   1) http://www.imagemagick.org/script/miff.php | 
| 9 |  |  |  |  |  |  | #               2) http://www.cs.uni.edu/Help/ImageMagick/www/miff.html | 
| 10 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | package Image::ExifTool::MIFF; | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 1 |  |  | 1 |  | 4224 | use strict; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 32 |  | 
| 15 | 1 |  |  | 1 |  | 5 | use vars qw($VERSION); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 16 | 1 |  |  | 1 |  | 9 | use Image::ExifTool qw(:DataAccess :Utils); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 1212 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | $VERSION = '1.07'; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | # MIFF chunks | 
| 21 |  |  |  |  |  |  | %Image::ExifTool::MIFF::Main = ( | 
| 22 |  |  |  |  |  |  | GROUPS => { 2 => 'Image' }, | 
| 23 |  |  |  |  |  |  | NOTES => q{ | 
| 24 |  |  |  |  |  |  | The MIFF (Magick Image File Format) format allows aribrary tag names to be | 
| 25 |  |  |  |  |  |  | used.  Only the standard tag names are listed below, however ExifTool will | 
| 26 |  |  |  |  |  |  | decode any tags found in the image. | 
| 27 |  |  |  |  |  |  | }, | 
| 28 |  |  |  |  |  |  | 'background-color' => 'BackgroundColor', | 
| 29 |  |  |  |  |  |  | 'blue-primary' => 'BluePrimary', | 
| 30 |  |  |  |  |  |  | 'border-color' => 'BorderColor', | 
| 31 |  |  |  |  |  |  | 'matt-color' => 'MattColor', | 
| 32 |  |  |  |  |  |  | class => 'Class', | 
| 33 |  |  |  |  |  |  | colors => 'Colors', | 
| 34 |  |  |  |  |  |  | colorspace => 'ColorSpace', | 
| 35 |  |  |  |  |  |  | columns => 'ImageWidth', | 
| 36 |  |  |  |  |  |  | compression => 'Compression', | 
| 37 |  |  |  |  |  |  | delay => 'Delay', | 
| 38 |  |  |  |  |  |  | depth => 'Depth', | 
| 39 |  |  |  |  |  |  | dispose => 'Dispose', | 
| 40 |  |  |  |  |  |  | gamma => 'Gamma', | 
| 41 |  |  |  |  |  |  | 'green-primary' => 'GreenPrimary', | 
| 42 |  |  |  |  |  |  | id => 'ID', | 
| 43 |  |  |  |  |  |  | iterations => 'Iterations', | 
| 44 |  |  |  |  |  |  | label => 'Label', | 
| 45 |  |  |  |  |  |  | matte => 'Matte', | 
| 46 |  |  |  |  |  |  | montage => 'Montage', | 
| 47 |  |  |  |  |  |  | packets => 'Packets', | 
| 48 |  |  |  |  |  |  | page => 'Page', | 
| 49 |  |  |  |  |  |  | # profile tags.  Note the SubDirectory is not used by ProcessMIFF(), | 
| 50 |  |  |  |  |  |  | # but is inserted for documentation purposes only | 
| 51 |  |  |  |  |  |  | 'profile-APP1' => [ | 
| 52 |  |  |  |  |  |  | # [this list is just for the sake of the documentation] | 
| 53 |  |  |  |  |  |  | { | 
| 54 |  |  |  |  |  |  | Name => 'APP1_Profile', | 
| 55 |  |  |  |  |  |  | SubDirectory => { | 
| 56 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::Exif::Main', | 
| 57 |  |  |  |  |  |  | }, | 
| 58 |  |  |  |  |  |  | }, | 
| 59 |  |  |  |  |  |  | { | 
| 60 |  |  |  |  |  |  | Name => 'APP1_Profile', | 
| 61 |  |  |  |  |  |  | SubDirectory => { | 
| 62 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::XMP::Main', | 
| 63 |  |  |  |  |  |  | }, | 
| 64 |  |  |  |  |  |  | }, | 
| 65 |  |  |  |  |  |  | ], | 
| 66 |  |  |  |  |  |  | 'profile-exif' => { # haven't seen this, but it would make sense - PH | 
| 67 |  |  |  |  |  |  | Name => 'EXIF_Profile', | 
| 68 |  |  |  |  |  |  | SubDirectory => { | 
| 69 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::Exif::Main', | 
| 70 |  |  |  |  |  |  | }, | 
| 71 |  |  |  |  |  |  | }, | 
| 72 |  |  |  |  |  |  | 'profile-icc' => { | 
| 73 |  |  |  |  |  |  | Name => 'ICC_Profile', | 
| 74 |  |  |  |  |  |  | SubDirectory => { | 
| 75 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::ICC_Profile::Main', | 
| 76 |  |  |  |  |  |  | }, | 
| 77 |  |  |  |  |  |  | }, | 
| 78 |  |  |  |  |  |  | 'profile-iptc' => { | 
| 79 |  |  |  |  |  |  | Name => 'IPTC_Profile', | 
| 80 |  |  |  |  |  |  | SubDirectory => { | 
| 81 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::Photoshop::Main', | 
| 82 |  |  |  |  |  |  | }, | 
| 83 |  |  |  |  |  |  | }, | 
| 84 |  |  |  |  |  |  | 'profile-xmp' => { # haven't seen this, but it would make sense - PH | 
| 85 |  |  |  |  |  |  | Name => 'XMP_Profile', | 
| 86 |  |  |  |  |  |  | SubDirectory => { | 
| 87 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::XMP::Main', | 
| 88 |  |  |  |  |  |  | }, | 
| 89 |  |  |  |  |  |  | }, | 
| 90 |  |  |  |  |  |  | 'red-primary' => 'RedPrimary', | 
| 91 |  |  |  |  |  |  | 'rendering-intent' => 'RenderingIntent', | 
| 92 |  |  |  |  |  |  | resolution => 'Resolution', | 
| 93 |  |  |  |  |  |  | rows => 'ImageHeight', | 
| 94 |  |  |  |  |  |  | scene => 'Scene', | 
| 95 |  |  |  |  |  |  | signature => 'Signature', | 
| 96 |  |  |  |  |  |  | units => 'Units', | 
| 97 |  |  |  |  |  |  | 'white-point' => 'WhitePoint', | 
| 98 |  |  |  |  |  |  | ); | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 101 |  |  |  |  |  |  | # Extract meta information from a MIFF image | 
| 102 |  |  |  |  |  |  | # Inputs: 0) ExifTool object reference, 1) dirInfo reference | 
| 103 |  |  |  |  |  |  | # Returns: 1 on success, 0 if this wasn't a valid MIFF image | 
| 104 |  |  |  |  |  |  | sub ProcessMIFF($$) | 
| 105 |  |  |  |  |  |  | { | 
| 106 | 1 |  |  | 1 | 0 | 4 | my ($et, $dirInfo) = @_; | 
| 107 | 1 |  |  |  |  | 10 | my $raf = $$dirInfo{RAF}; | 
| 108 | 1 |  |  |  |  | 3 | my $verbose = $$et{OPTIONS}{Verbose}; | 
| 109 | 1 |  |  |  |  | 3 | my ($hdr, $buff); | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | # validate the MIFF file (note: MIFF files _may_ begin with other | 
| 112 |  |  |  |  |  |  | # characters, but this starting sequence is strongly suggested.) | 
| 113 | 1 | 50 |  |  |  | 4 | return 0 unless $raf->Read($hdr, 14) == 14; | 
| 114 | 1 | 50 |  |  |  | 4 | return 0 unless $hdr eq 'id=ImageMagick'; | 
| 115 | 1 |  |  |  |  | 6 | $et->SetFileType();   # set the FileType tag | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | # set end-of-line character sequence to read to end of the TEXT | 
| 118 |  |  |  |  |  |  | # section for new-type MIFF files (text ends with Colon+Ctrl-Z) | 
| 119 |  |  |  |  |  |  | # Old MIFF files end with Colon+Linefeed, so this will likely | 
| 120 |  |  |  |  |  |  | # slurp those entire files, which will be slower, but will work | 
| 121 |  |  |  |  |  |  | # OK except that the profile information won't be decoded | 
| 122 | 1 |  |  |  |  | 7 | local $/ = ":\x1a"; | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 1 |  |  |  |  | 2 | my $mode = ''; | 
| 125 | 1 |  |  |  |  | 3 | my @profiles; | 
| 126 | 1 | 50 |  |  |  | 5 | if ($raf->ReadLine($buff)) { | 
| 127 | 1 |  |  |  |  | 3 | chomp $buff;    # remove end-of-line chars | 
| 128 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::MIFF::Main'); | 
| 129 | 1 |  |  |  |  | 20 | my @entries = split ' ', $buff; | 
| 130 | 1 |  |  |  |  | 5 | unshift @entries, $hdr; # put the ID back in | 
| 131 | 1 |  |  |  |  | 3 | my ($tag, $val); | 
| 132 | 1 |  |  |  |  | 3 | foreach (@entries) { | 
| 133 | 11 | 50 |  |  |  | 31 | if ($mode eq 'com') { | 
|  |  | 50 |  |  |  |  |  | 
| 134 | 0 | 0 |  |  |  | 0 | $mode = '' if /\}$/; | 
| 135 | 0 |  |  |  |  | 0 | next; | 
| 136 |  |  |  |  |  |  | } elsif (/^\{/) { | 
| 137 | 0 |  |  |  |  | 0 | $mode = 'com';  # read to the end of the comment | 
| 138 | 0 |  |  |  |  | 0 | next; | 
| 139 |  |  |  |  |  |  | } | 
| 140 | 11 | 50 |  |  |  | 49 | if ($mode eq 'val') { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 141 | 0 |  |  |  |  | 0 | $val .= " $_";  # join back together with a space | 
| 142 | 0 | 0 |  |  |  | 0 | next unless /\}$/; | 
| 143 | 0 |  |  |  |  | 0 | $mode = ''; | 
| 144 | 0 |  |  |  |  | 0 | $val =~ s/(^\{|\}$)//g; # remove braces | 
| 145 |  |  |  |  |  |  | } elsif (/(.+)=(.+)/) { | 
| 146 | 11 |  |  |  |  | 36 | ($tag, $val) = ($1, $2); | 
| 147 | 11 | 50 |  |  |  | 22 | if ($val =~ /^\{/) { | 
| 148 | 0 |  |  |  |  | 0 | $mode = 'val';      # read to the end of the value data | 
| 149 | 0 |  |  |  |  | 0 | next; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | } elsif (/^:/) { | 
| 152 |  |  |  |  |  |  | # this could be the end of an old-style MIFF file | 
| 153 | 0 |  |  |  |  | 0 | last; | 
| 154 |  |  |  |  |  |  | } else { | 
| 155 |  |  |  |  |  |  | # something we don't recognize -- stop parsing here | 
| 156 | 0 |  |  |  |  | 0 | $et->Warn('Unrecognized MIFF data'); | 
| 157 | 0 |  |  |  |  | 0 | last; | 
| 158 |  |  |  |  |  |  | } | 
| 159 | 11 |  |  |  |  | 28 | my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); | 
| 160 | 11 | 100 |  |  |  | 22 | unless ($tagInfo) { | 
| 161 | 1 |  |  |  |  | 3 | $tagInfo = { Name => $tag }; | 
| 162 | 1 |  |  |  |  | 5 | AddTagToTable($tagTablePtr, $tag, $tagInfo); | 
| 163 |  |  |  |  |  |  | } | 
| 164 | 11 | 50 |  |  |  | 17 | $verbose and $et->VerboseInfo($tag, $tagInfo, | 
| 165 |  |  |  |  |  |  | Table  => $tagTablePtr, | 
| 166 |  |  |  |  |  |  | DataPt => \$val, | 
| 167 |  |  |  |  |  |  | ); | 
| 168 |  |  |  |  |  |  | # handle profile tags specially | 
| 169 | 11 | 100 |  |  |  | 26 | if ($tag =~ /^profile-(.*)/) { | 
| 170 | 3 |  |  |  |  | 14 | push @profiles, [$1, $val]; | 
| 171 |  |  |  |  |  |  | } else { | 
| 172 | 8 |  |  |  |  | 17 | $et->FoundTag($tagInfo, $val); | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | # process profile information | 
| 178 | 1 |  |  |  |  | 3 | foreach (@profiles) { | 
| 179 | 3 |  |  |  |  | 5 | my ($type, $len) = @{$_}; | 
|  | 3 |  |  |  |  | 9 |  | 
| 180 | 3 | 50 |  |  |  | 18 | unless ($len =~ /^\d+$/) { | 
| 181 | 0 |  |  |  |  | 0 | $et->Warn("Invalid length for $type profile"); | 
| 182 | 0 |  |  |  |  | 0 | last;   # don't try to read the rest | 
| 183 |  |  |  |  |  |  | } | 
| 184 | 3 | 50 |  |  |  | 11 | unless ($raf->Read($buff, $len) == $len) { | 
| 185 | 0 |  |  |  |  | 0 | $et->Warn("Error reading $type profile ($len bytes)"); | 
| 186 | 0 |  |  |  |  | 0 | next; | 
| 187 |  |  |  |  |  |  | } | 
| 188 | 3 |  |  |  |  | 7 | my $processed = 0; | 
| 189 | 3 |  |  |  |  | 11 | my %dirInfo = ( | 
| 190 |  |  |  |  |  |  | Parent   => 'PNG', | 
| 191 |  |  |  |  |  |  | DataPt   => \$buff, | 
| 192 |  |  |  |  |  |  | DataPos  => $raf->Tell() - $len, | 
| 193 |  |  |  |  |  |  | DataLen  => $len, | 
| 194 |  |  |  |  |  |  | DirStart => 0, | 
| 195 |  |  |  |  |  |  | DirLen   => $len, | 
| 196 |  |  |  |  |  |  | ); | 
| 197 | 3 | 50 | 33 |  |  | 21 | if ($type eq 'icc') { | 
|  |  | 100 | 33 |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | # ICC Profile information | 
| 199 | 0 |  |  |  |  | 0 | my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); | 
| 200 | 0 |  |  |  |  | 0 | $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); | 
| 201 |  |  |  |  |  |  | } elsif ($type eq 'iptc') { | 
| 202 | 1 | 50 |  |  |  | 6 | if ($buff =~ /^8BIM/) { | 
| 203 |  |  |  |  |  |  | # Photoshop information | 
| 204 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); | 
| 205 | 1 |  |  |  |  | 14 | $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | # I haven't seen 'exif' or 'xmp' profile types yet, but I have seen them | 
| 208 |  |  |  |  |  |  | # in newer PNG files so presumably they are possible here as well - PH | 
| 209 |  |  |  |  |  |  | } elsif ($type eq 'APP1' or $type eq 'exif' or $type eq 'xmp') { | 
| 210 | 2 | 100 |  |  |  | 38 | if ($buff =~ /^$Image::ExifTool::exifAPP1hdr/) { | 
|  |  | 50 |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # APP1 EXIF | 
| 212 | 1 |  |  |  |  | 3 | my $hdrLen = length($Image::ExifTool::exifAPP1hdr); | 
| 213 | 1 |  |  |  |  | 3 | $dirInfo{DirStart} += $hdrLen; | 
| 214 | 1 |  |  |  |  | 2 | $dirInfo{DirLen} -= $hdrLen; | 
| 215 |  |  |  |  |  |  | # use the usual position for EXIF data: 12 bytes from start of file | 
| 216 |  |  |  |  |  |  | # (this may be wrong, but I can't see where the PNG stores this information) | 
| 217 | 1 |  |  |  |  | 2 | $dirInfo{Base} = 12; # this is the usual value | 
| 218 | 1 |  |  |  |  | 5 | $processed = $et->ProcessTIFF(\%dirInfo); | 
| 219 |  |  |  |  |  |  | } elsif ($buff =~ /^$Image::ExifTool::xmpAPP1hdr/) { | 
| 220 |  |  |  |  |  |  | # APP1 XMP | 
| 221 | 1 |  |  |  |  | 5 | my $hdrLen = length($Image::ExifTool::xmpAPP1hdr); | 
| 222 | 1 |  |  |  |  | 3 | my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); | 
| 223 | 1 |  |  |  |  | 4 | $dirInfo{DirStart} += $hdrLen; | 
| 224 | 1 |  |  |  |  | 2 | $dirInfo{DirLen} -= $hdrLen; | 
| 225 | 1 |  |  |  |  | 5 | $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  | } | 
| 228 | 3 | 50 |  |  |  | 14 | unless ($processed) { | 
| 229 | 0 |  |  |  |  | 0 | $et->Warn("Unknown MIFF $type profile data"); | 
| 230 | 0 | 0 |  |  |  | 0 | if ($verbose) { | 
| 231 | 0 |  |  |  |  | 0 | $et->VerboseDir($type, 0, $len); | 
| 232 | 0 |  |  |  |  | 0 | $et->VerboseDump(\$buff); | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  | } | 
| 236 | 1 |  |  |  |  | 8 | return 1; | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | 1;  # end | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | __END__ |