1<?php 2 3/************************* 4 Coppermine Photo Gallery 5 ************************ 6 Copyright (c) 2003-2016 Coppermine Dev Team 7 v1.0 originally written by Gregory Demar 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License version 3 11 as published by the Free Software Foundation. 12 13 ******************************************** 14 Coppermine version: 1.6.03 15 $HeadURL$ 16**********************************************/ 17/* 18 Exifer 1.6 19 Extracts EXIF information from digital photos. 20 21 Originally created by: 22 Copyright © 2005 Jake Olefsky 23 http:// www.offsky.com/software/exif/index.php 24 jake@olefsky.com 25 26 This program is free software; you can redistribute it and/or modify it under the terms of 27 the GNU General Public License as published by the Free Software Foundation; either version 2 28 of the License, or (at your option) any later version. 29 30 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 31 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 32 See the GNU General Public License for more details. http:// www.gnu.org/copyleft/gpl.html 33 34 SUMMARY: 35 This script will correctly parse all of the EXIF data included in images taken 36 with digital cameras. It will read the IDF0, IDF1, SubIDF and InteroperabilityIFD 37 fields as well as parsing some of the MakerNote fields that vary depending on 38 camera make and model. This script parses more tags than the internal PHP exif 39 implementation and it will correctly identify and decode what all the values mean. 40 41 This version will correctly parse the MakerNote field for Nikon, Olympus, and Canon 42 digital cameras. Others will follow. 43 44 TESTED WITH: 45 Nikon CoolPix 700 46 Nikon CoolPix E3200 47 Nikon CoolPix 4500 48 Nikon CoolPix 950 49 Nikon Coolpix 5700 50 Canon PowerShot S200 51 Canon PowerShot S110 52 Olympus C2040Z 53 Olympus C960 54 Olumpus E-300 55 Olympus E-410 56 Olympus E-500 57 Olympus E-510 58 Olympus E-3 59 Canon Ixus 60 Canon EOS 300D 61 Canon Digital Rebel 62 Canon EOS 10D 63 Canon PowerShot G2 64 FujiFilm DX 10 65 FujiFilm MX-1200 66 FujiFilm FinePix2400 67 FujiFilm FinePix2600 68 FujiFilm FinePix S602 69 FujiFilm FinePix40i 70 Sony D700 71 Sony Cybershot 72 Kodak DC210 73 Kodak DC240 74 Kodak DC4800 75 Kodak DX3215 76 Ricoh RDC-5300 77 Sanyo VPC-G250 78 Sanyo VPC-SX550 79 Epson 3100z 80 81 82 VERSION HISTORY: 83 84 1.0 September 23, 2002 85 86 + First Public Release 87 88 1.1 January 25, 2003 89 90 + Gracefully handled the error case where you pass an empty string to this library 91 + Fixed an inconsistency in the Olympus Camera parsing module 92 + Added support for parsing the MakerNote of Canon images. 93 + Modified how the imagefile is opened so it works for windows machines. 94 + Correctly parses the FocalPlaneResolutionUnit and PhotometricInterpretation fields 95 + Negative rational numbers are properly displayed 96 + Strange old cameras that use Motorola endineness are now properly supported 97 + Tested with several more cameras 98 99 Potential Problem: Negative Shorts and Negative Longs may not be correctly displayed, but I 100 have not yet found an example of negative shorts or longs being used. 101 102 1.2 March 30, 2003 103 104 + Fixed an error that was displayed if you edited your image with WinXP's image viewer 105 + Fixed a bug that caused some images saved from 3rd party software to not parse correctly 106 + Changed the ExposureTime tag to display in fractional seconds rather than decimal 107 + Updated the ShutterSpeedValue tag to have the units of 'sec' 108 + Added support for parsing the MakeNote of FujiFilm images 109 + Added support for parsing the MakeNote of Sanyo images 110 + Fixed a bug with parsing some Olympus MakerNote tags 111 + Tested with several more cameras 112 113 1.3 June 15, 2003 114 115 + Fixed Canon MakerNote support for some models 116 (Canon has very difficult and inconsistent MakerNote syntax) 117 + Negative signed shorts and negative signed longs are properly displayed 118 + Several more tags are defined 119 + More information in my comments about what each tag is 120 + Parses and Displays GPS information if available 121 + Tested with several more cameras 122 123 1.4 September 14, 2003 124 125 + This software is now licensed under the GNU General Public License 126 + Exposure time is now correctly displayed when the numerator is 10 127 + Fixed the calculation and display of ShutterSpeedValue, ApertureValue and MaxApertureValue 128 + Fixed a bug with the GPS code 129 + Tested with several more cameras 130 131 1.5 February 18, 2005 132 133 + It now gracefully deals with a passed in file that cannot be found. 134 + Fixed a GPS bug for the parsing of Altitude and other signed rational numbers 135 + Defined more values for Canon cameras. 136 + Added 'bulb' detection for ShutterSpeed 137 + Made script loading a little faster and less memory intensive. 138 + Bug fixes 139 + Better error reporting 140 + Graceful failure for files with corrupt exif info. 141 + QuickTime (including iPhoto) messes up the Makernote tag for certain photos (no workaround yet) 142 + Now reads exif information when the jpeg markers are out of order 143 + Gives raw data output for IPTC, COM and APP2 fields which are sometimes set by other applications 144 + Improvements to Nikon Makernote parsing 145 146 1.6 March 25th, 2007 [Zenphoto] 147 148 + Adopted into the Zenphoto gallery project, at http://www.zenphoto.org 149 + Fixed a bug where strings had trailing null bytes. 150 + Formatted selected strings better. 151 + Added calculation of 35mm-equivalent focal length when possible. 152 + Cleaned up code for readability and efficiency. 153 154 1.7 April 11th, 2008 [Zenphoto] 155 156 + Fixed bug with newer Olympus cameras where number of fields was miscalculated leading to bad performance. 157 + More logical fraction calculation for shutter speed. 158 1592009: For all further changes, see the Zenphoto change logs. 160 161*/ 162 163 164 165 166 167 168//================================================================================================ 169// Converts from Intel to Motorola endien. Just reverses the bytes (assumes hex is passed in) 170//================================================================================================ 171 172function intel2Moto($intel) { 173 static $cache = array(); 174 if (isset($cache[$intel])) { 175 return $cache[$intel]; 176 } 177 178 $cache[$intel] = ''; 179 $len = strlen($intel); 180 if ($len > 1000) { // an unreasonable length, override it. 181 $len = 1000; 182 } 183 for($i = 0; $i <= $len; $i += 2) { 184 $cache[$intel] .= substr($intel, $len-$i, 2); 185 } 186 return $cache[$intel]; 187} 188 189 190//================================================================================================ 191// Looks up the name of the tag 192//================================================================================================ 193function lookup_tag($tag) { 194 switch($tag) { 195 // used by IFD0 'Camera Tags' 196 case '000b': $tag = 'ACDComment'; break; // text string up to 999 bytes long 197 case '00fe': $tag = 'ImageType'; break; // integer -2147483648 to 2147483647 198 case '0106': $tag = 'PhotometricInterpret'; break; // ?? Please send sample image with this tag 199 case '010e': $tag = 'ImageDescription'; break; // text string up to 999 bytes long 200 case '010f': $tag = 'Make'; break; // text string up to 999 bytes long 201 case '0110': $tag = 'Model'; break; // text string up to 999 bytes long 202 case '0112': $tag = 'Orientation'; break; // integer values 1-9 203 case '0115': $tag = 'SamplePerPixel'; break; // integer 0-65535 204 case '011a': $tag = 'xResolution'; break; // positive rational number 205 case '011b': $tag = 'yResolution'; break; // positive rational number 206 case '011c': $tag = 'PlanarConfig'; break; // integer values 1-2 207 case '0128': $tag = 'ResolutionUnit'; break; // integer values 1-3 208 case '0131': $tag = 'Software'; break; // text string up to 999 bytes long 209 case '0132': $tag = 'DateTime'; break; // YYYY:MM:DD HH:MM:SS 210 case '013b': $tag = 'Artist'; break; // text string up to 999 bytes long 211 case '013c': $tag = 'HostComputer'; break; // text string 212 case '013e': $tag = 'WhitePoint'; break; // two positive rational numbers 213 case '013f': $tag = 'PrimaryChromaticities'; break; // six positive rational numbers 214 case '0211': $tag = 'YCbCrCoefficients'; break; // three positive rational numbers 215 case '0213': $tag = 'YCbCrPositioning'; break; // integer values 1-2 216 case '0214': $tag = 'ReferenceBlackWhite'; break; // six positive rational numbers 217 case '8298': $tag = 'Copyright'; break; // text string up to 999 bytes long 218 case '8649': $tag = 'PhotoshopSettings'; break; // ?? 219 case '8769': $tag = 'ExifOffset'; break; // positive integer 220 case '8825': $tag = 'GPSInfoOffset'; break; 221 case '9286': $tag = 'UserCommentOld'; break; // ?? 222 // used by Exif SubIFD 'Image Tags' 223 case '829a': $tag = 'ExposureTime'; break; // seconds or fraction of seconds 1/x 224 case '829d': $tag = 'FNumber'; break; // positive rational number 225 case '8822': $tag = 'ExposureProgram'; break; // integer value 1-9 226 case '8824': $tag = 'SpectralSensitivity'; break; // ?? 227 case '8827': $tag = 'ISOSpeedRatings'; break; // integer 0-65535 228 case '9000': $tag = 'ExifVersion'; break; // ?? 229 case '9003': $tag = 'DateTimeOriginal'; break; // YYYY:MM:DD HH:MM:SS 230 case '9004': $tag = 'DateTimeDigitized'; break; // YYYY:MM:DD HH:MM:SS 231 case '9101': $tag = 'ComponentsConfiguration'; break; // ?? 232 case '9102': $tag = 'CompressedBitsPerPixel'; break; // positive rational number 233 case '9201': $tag = 'ShutterSpeedValue'; break; // seconds or fraction of seconds 1/x 234 case '9202': $tag = 'ApertureValue'; break; // positive rational number 235 case '9203': $tag = 'BrightnessValue'; break; // positive rational number 236 case '9204': $tag = 'ExposureBiasValue'; break; // positive rational number (EV) 237 case '9205': $tag = 'MaxApertureValue'; break; // positive rational number 238 case '9206': $tag = 'SubjectDistance'; break; // positive rational number (meters) 239 case '9207': $tag = 'MeteringMode'; break; // integer 1-6 and 255 240 case '9208': $tag = 'LightSource'; break; // integer 1-255 241 case '9209': $tag = 'Flash'; break; // integer 1-255 242 case '920a': $tag = 'FocalLength'; break; // positive rational number (mm) 243 case '9213': $tag = 'ImageHistory'; break; // text string up to 999 bytes long 244 case '927c': $tag = 'MakerNote'; break; // a bunch of data 245 case '9286': $tag = 'UserComment'; break; // text string 246 case '9290': $tag = 'SubsecTime'; break; // text string up to 999 bytes long 247 case '9291': $tag = 'SubsecTimeOriginal'; break; // text string up to 999 bytes long 248 case '9292': $tag = 'SubsecTimeDigitized'; break; // text string up to 999 bytes long 249 case 'a000': $tag = 'FlashPixVersion'; break; // ?? 250 case 'a001': $tag = 'ColorSpace'; break; // values 1 or 65535 251 case 'a002': $tag = 'ExifImageWidth'; break; // ingeter 1-65535 252 case 'a003': $tag = 'ExifImageHeight'; break; // ingeter 1-65535 253 case 'a004': $tag = 'RelatedSoundFile'; break; // text string 12 bytes long 254 case 'a005': $tag = 'ExifInteroperabilityOffset'; break; // positive integer 255 case 'a20c': $tag = 'SpacialFreqResponse'; break; // ?? 256 case 'a20b': $tag = 'FlashEnergy'; break; // positive rational number 257 case 'a20e': $tag = 'FocalPlaneXResolution'; break; // positive rational number 258 case 'a20f': $tag = 'FocalPlaneYResolution'; break; // positive rational number 259 case 'a210': $tag = 'FocalPlaneResolutionUnit'; break; // values 1-3 260 case 'a214': $tag = 'SubjectLocation'; break; // two integers 0-65535 261 case 'a215': $tag = 'ExposureIndex'; break; // positive rational number 262 case 'a217': $tag = 'SensingMethod'; break; // values 1-8 263 case 'a300': $tag = 'FileSource'; break; // integer 264 case 'a301': $tag = 'SceneType'; break; // integer 265 case 'a302': $tag = 'CFAPattern'; break; // undefined data type 266 case 'a401': $tag = 'CustomerRender'; break; // values 0 or 1 267 case 'a402': $tag = 'ExposureMode'; break; // values 0-2 268 case 'a403': $tag = 'WhiteBalance'; break; // values 0 or 1 269 case 'a404': $tag = 'DigitalZoomRatio'; break; // positive rational number 270 case 'a405': $tag = 'FocalLengthIn35mmFilm'; break; 271 case 'a406': $tag = 'SceneCaptureMode'; break; // values 0-3 272 case 'a407': $tag = 'GainControl'; break; // values 0-4 273 case 'a408': $tag = 'Contrast'; break; // values 0-2 274 case 'a409': $tag = 'Saturation'; break; // values 0-2 275 case 'a40a': $tag = 'Sharpness'; break; // values 0-2 276 case 'a434': $tag = 'LensInfo'; break; 277 278 // used by Interoperability IFD 279 case '0001': $tag = 'InteroperabilityIndex'; break; // text string 3 bytes long 280 case '0002': $tag = 'InteroperabilityVersion'; break; // datatype undefined 281 case '1000': $tag = 'RelatedImageFileFormat'; break; // text string up to 999 bytes long 282 case '1001': $tag = 'RelatedImageWidth'; break; // integer in range 0-65535 283 case '1002': $tag = 'RelatedImageLength'; break; // integer in range 0-65535 284 285 // used by IFD1 'Thumbnail' 286 case '0100': $tag = 'ImageWidth'; break; // integer in range 0-65535 287 case '0101': $tag = 'ImageLength'; break; // integer in range 0-65535 288 case '0102': $tag = 'BitsPerSample'; break; // integers in range 0-65535 289 case '0103': $tag = 'Compression'; break; // values 1 or 6 290 case '0106': $tag = 'PhotometricInterpretation'; break;// values 0-4 291 case '010e': $tag = 'ThumbnailDescription'; break; // text string up to 999 bytes long 292 case '010f': $tag = 'ThumbnailMake'; break; // text string up to 999 bytes long 293 case '0110': $tag = 'ThumbnailModel'; break; // text string up to 999 bytes long 294 case '0111': $tag = 'StripOffsets'; break; // ?? 295 case '0112': $tag = 'ThumbnailOrientation'; break; // integer 1-9 296 case '0115': $tag = 'SamplesPerPixel'; break; // ?? 297 case '0116': $tag = 'RowsPerStrip'; break; // ?? 298 case '0117': $tag = 'StripByteCounts'; break; // ?? 299 case '011a': $tag = 'ThumbnailXResolution'; break; // positive rational number 300 case '011b': $tag = 'ThumbnailYResolution'; break; // positive rational number 301 case '011c': $tag = 'PlanarConfiguration'; break; // values 1 or 2 302 case '0128': $tag = 'ThumbnailResolutionUnit'; break; // values 1-3 303 case '0201': $tag = 'JpegIFOffset'; break; 304 case '0202': $tag = 'JpegIFByteCount'; break; 305 case '0212': $tag = 'YCbCrSubSampling'; break; 306 307 // misc 308 case '00ff': $tag = 'SubfileType'; break; 309 case '012d': $tag = 'TransferFunction'; break; 310 case '013d': $tag = 'Predictor'; break; 311 case '0142': $tag = 'TileWidth'; break; 312 case '0143': $tag = 'TileLength'; break; 313 case '0144': $tag = 'TileOffsets'; break; 314 case '0145': $tag = 'TileByteCounts'; break; 315 case '014a': $tag = 'SubIFDs'; break; 316 case '015b': $tag = 'JPEGTables'; break; 317 case '828d': $tag = 'CFARepeatPatternDim'; break; 318 case '828e': $tag = 'CFAPattern'; break; 319 case '828f': $tag = 'BatteryLevel'; break; 320 case '83bb': $tag = 'IPTC/NAA'; break; 321 case '8773': $tag = 'InterColorProfile'; break; 322 323 case '8828': $tag = 'OECF'; break; 324 case '8829': $tag = 'Interlace'; break; 325 case '882a': $tag = 'TimeZoneOffset'; break; 326 case '882b': $tag = 'SelfTimerMode'; break; 327 case '920b': $tag = 'FlashEnergy'; break; 328 case '920c': $tag = 'SpatialFrequencyResponse'; break; 329 case '920d': $tag = 'Noise'; break; 330 case '9211': $tag = 'ImageNumber'; break; 331 case '9212': $tag = 'SecurityClassification'; break; 332 case '9214': $tag = 'SubjectLocation'; break; 333 case '9215': $tag = 'ExposureIndex'; break; 334 case '9216': $tag = 'TIFF/EPStandardID'; break; 335 case 'a20b': $tag = 'FlashEnergy'; break; 336 337 default: $tag = 'unknown:'.$tag; break; 338 } 339 return $tag; 340 341} 342 343 344//================================================================================================ 345// Looks up the datatype 346//================================================================================================ 347function lookup_type(&$type,&$size) { 348 switch($type) { 349 case '0001': $type = 'UBYTE'; $size=1; break; 350 case '0002': $type = 'ASCII'; $size=1; break; 351 case '0003': $type = 'USHORT'; $size=2; break; 352 case '0004': $type = 'ULONG'; $size=4; break; 353 case '0005': $type = 'URATIONAL'; $size=8; break; 354 case '0006': $type = 'SBYTE'; $size=1; break; 355 case '0007': $type = 'UNDEFINED'; $size=1; break; 356 case '0008': $type = 'SSHORT'; $size=2; break; 357 case '0009': $type = 'SLONG'; $size=4; break; 358 case '000a': $type = 'SRATIONAL'; $size=8; break; 359 case '000b': $type = 'FLOAT'; $size=4; break; 360 case '000c': $type = 'DOUBLE'; $size=8; break; 361 default: $type = 'error:'.$type; $size=0; break; 362 } 363 return $type; 364} 365 366//================================================================================================ 367// processes a irrational number 368//================================================================================================ 369function unRational($data, $type, $intel) { 370 $data = bin2hex($data); 371 if ($intel == 1) { 372 $data = intel2Moto($data); 373 $top = hexdec(substr($data,8,8)); // intel stores them bottom-top 374 $bottom = hexdec(substr($data,0,8)); // intel stores them bottom-top 375 } else { 376 $top = hexdec(substr($data,0,8)); // motorola stores them top-bottom 377 $bottom = hexdec(substr($data,8,8)); // motorola stores them top-bottom 378 } 379 if ($type == 'SRATIONAL' && $top > 2147483647) $top = $top - 4294967296; // this makes the number signed instead of unsigned 380 if ($bottom != 0) 381 $data=$top/$bottom; 382 else 383 if ($top == 0) 384 $data = 0; 385 else 386 $data = $top.'/'.$bottom; 387 return $data; 388} 389 390//================================================================================================ 391// processes a rational number 392//================================================================================================ 393function rational($data,$type,$intel) { 394 if (($type == 'USHORT' || $type == 'SSHORT')) { 395 $data = substr($data,0,2); 396 } 397 $data = bin2hex($data); 398 if ($intel == 1) { 399 $data = intel2Moto($data); 400 } 401 $data = hexdec($data); 402 if ($type == 'SSHORT' && $data > 32767) $data = $data - 65536; // this makes the number signed instead of unsigned 403 if ($type == 'SLONG' && $data > 2147483647) $data = $data - 4294967296; // this makes the number signed instead of unsigned 404 return $data; 405} 406 407//================================================================================================ 408// Formats Data for the data type 409//================================================================================================ 410function formatData($type,$tag,$intel,$data) { 411 switch ($type) { 412 case 'ASCII': 413 if (($pos = strpos($data, chr(0))) !== false) { // Search for a null byte and stop there. 414 $data = substr($data, 0, $pos); 415 } 416 if ($tag == '010f') $data = ucwords(strtolower(trim($data))); // Format certain kinds of strings nicely (Camera make etc.) 417 break; 418 case 'URATIONAL': 419 case 'SRATIONAL': 420 switch ($tag) { 421 case '011a': // XResolution 422 case '011b': // YResolution 423 $data = round(unRational($data,$type,$intel)).' dots per ResolutionUnit'; 424 break; 425 case '829a': // Exposure Time 426 $data = formatExposure(unRational($data,$type,$intel)); 427 break; 428 case '829d': // FNumber 429 $data = 'f/'.unRational($data,$type,$intel); 430 break; 431 case '9204': // ExposureBiasValue 432 $data = round(unRational($data,$type,$intel), 2) . ' EV'; 433 break; 434 case '9205': // ApertureValue 435 case '9202': // MaxApertureValue 436 // ApertureValue is given in the APEX Mode. Many thanks to Matthieu Froment for this code 437 // The formula is : Aperture = 2*log2(FNumber) <=> FNumber = e((Aperture.ln(2))/2) 438 $datum = exp((unRational($data,$type,$intel)*log(2))/2); 439 $data = 'f/'.round($datum, 1);// Focal is given with a precision of 1 digit. 440 break; 441 case '920a': // FocalLength 442 $data = unRational($data,$type,$intel).' mm'; 443 break; 444 case '9201': // ShutterSpeedValue 445 // The ShutterSpeedValue is given in the APEX mode. Many thanks to Matthieu Froment for this code 446 // The formula is : Shutter = - log2(exposureTime) (Appendix C of EXIF spec.) 447 // Where shutter is in APEX, log2(exposure) = ln(exposure)/ln(2) 448 // So final formula is : exposure = exp(-ln(2).shutter) 449 // The formula can be developed : exposure = 1/(exp(ln(2).shutter)) 450 $datum = exp(unRational($data,$type,$intel) * log(2)); 451 if ($datum != 0) $datum = 1/$datum; 452 $data = formatExposure($datum); 453 break; 454 default: 455 $data = unRational($data,$type,$intel); 456 break; 457 } 458 break; 459 case 'USHORT': 460 case 'SSHORT': 461 case 'ULONG': 462 case 'SLONG': 463 case 'FLOAT': 464 case 'DOUBLE': 465 $data = rational($data,$type,$intel); 466 switch ($tag) { 467 case '0112': // Orientation 468 // Example of how all of these tag formatters should be... 469 switch ($data) { 470 case 0 : // not set, presume normal 471 case 1 : $data = gettext('1: Normal (0 deg)'); break; 472 case 2 : $data = gettext('2: Mirrored'); break; 473 case 3 : $data = gettext('3: Upside-down'); break; 474 case 4 : $data = gettext('4: Upside-down Mirrored'); break; 475 case 5 : $data = gettext('5: 90 deg CW Mirrored'); break; 476 case 6 : $data = gettext('6: 90 deg CCW'); break; 477 case 7 : $data = gettext('7: 90 deg CCW Mirrored'); break; 478 case 8 : $data = gettext('8: 90 deg CW'); break; 479 default : $data = sprintf(gettext('%d: Unknown'),$data); break; 480 } 481 break; 482 case '0128': // ResolutionUnit 483 case 'a210': // FocalPlaneResolutionUnit 484 case '0128': // ThumbnailResolutionUnit 485 switch ($data) { 486 case 1: $data = gettext('No Unit'); break; 487 case 2: $data = gettext('Inch'); break; 488 case 3: $data = gettext('Centimeter'); break; 489 } 490 break; 491 case '0213': // YCbCrPositioning 492 switch ($data) { 493 case 1: $data = gettext('Center of Pixel Array'); break; 494 case 2: $data = gettext('Datum Point'); break; 495 } 496 break; 497 case '8822': // ExposureProgram 498 switch ($data) { 499 case 1: $data = gettext('Manual'); break; 500 case 2: $data = gettext('Program'); break; 501 case 3: $data = gettext('Aperture Priority'); break; 502 case 4: $data = gettext('Shutter Priority'); break; 503 case 5: $data = gettext('Program Creative'); break; 504 case 6: $data = gettext('Program Action'); break; 505 case 7: $data = gettext('Portrait'); break; 506 case 8: $data = gettext('Landscape'); break; 507 default: $data = gettext('Unknown').': '.$data; break; 508 } 509 break; 510 case '9207': // MeteringMode 511 switch ($data) { 512 case 1: $data = gettext('Average'); break; 513 case 2: $data = gettext('Center Weighted Average'); break; 514 case 3: $data = gettext('Spot'); break; 515 case 4: $data = gettext('Multi-Spot'); break; 516 case 5: $data = gettext('Pattern'); break; 517 case 6: $data = gettext('Partial'); break; 518 case 255: $data = gettext('Other'); break; 519 default: $data = gettext('Unknown').': '.$data; break; 520 } 521 break; 522 case '9208': // LightSource 523 switch ($data) { 524 case 1: $data = gettext('Daylight'); break; 525 case 2: $data = gettext('Fluorescent'); break; 526 case 3: $data = gettext('Tungsten'); break; // 3 Tungsten (Incandescent light) 527 // 4 Flash 528 // 9 Fine Weather 529 case 10: $data = gettext('Flash'); break; // 10 Cloudy Weather 530 // 11 Shade 531 // 12 Daylight Fluorescent (D 5700 - 7100K) 532 // 13 Day White Fluorescent (N 4600 - 5400K) 533 // 14 Cool White Fluorescent (W 3900 -4500K) 534 // 15 White Fluorescent (WW 3200 - 3700K) 535 // 10 Flash 536 case 17: $data = gettext('Standard Light A'); break; 537 case 18: $data = gettext('Standard Light B'); break; 538 case 19: $data = gettext('Standard Light C'); break; 539 case 20: $data = gettext('D55'); break; 540 case 21: $data = gettext('D65'); break; 541 case 22: $data = gettext('D75'); break; 542 case 23: $data = gettext('D50'); break; 543 case 24: $data = gettext('ISO Studio Tungsten'); break; 544 case 255: $data = gettext('Other'); break; 545 default: $data = gettext('Unknown').': '.$data; break; 546 } 547 break; 548 case '9209': // Flash 549 switch ($data) { 550 case 0: $data = gettext('No Flash'); break; 551 case 1: $data = gettext('Flash'); break; 552 case 5: $data = gettext('Flash, strobe return light not detected'); break; 553 case 7: $data = gettext('Flash, strobe return light detected'); break; 554 case 9: $data = gettext('Compulsory Flash'); break; 555 case 13: $data = gettext('Compulsory Flash, Return light not detected'); break; 556 case 15: $data = gettext('Compulsory Flash, Return light detected'); break; 557 case 16: $data = gettext('No Flash'); break; 558 case 24: $data = gettext('No Flash'); break; 559 case 25: $data = gettext('Flash, Auto-Mode'); break; 560 case 29: $data = gettext('Flash, Auto-Mode, Return light not detected'); break; 561 case 31: $data = gettext('Flash, Auto-Mode, Return light detected'); break; 562 case 32: $data = gettext('No Flash'); break; 563 case 65: $data = gettext('Red Eye'); break; 564 case 69: $data = gettext('Red Eye, Return light not detected'); break; 565 case 71: $data = gettext('Red Eye, Return light detected'); break; 566 case 73: $data = gettext('Red Eye, Compulsory Flash'); break; 567 case 77: $data = gettext('Red Eye, Compulsory Flash, Return light not detected'); break; 568 case 79: $data = gettext('Red Eye, Compulsory Flash, Return light detected'); break; 569 case 89: $data = gettext('Red Eye, Auto-Mode'); break; 570 case 93: $data = gettext('Red Eye, Auto-Mode, Return light not detected'); break; 571 case 95: $data = gettext('Red Eye, Auto-Mode, Return light detected'); break; 572 default: $data = gettext('Unknown').': '.$data; break; 573 } 574 break; 575 case 'a001': // ColorSpace 576 if ($data == 1) $data = gettext('sRGB'); 577 else $data = gettext('Uncalibrated'); 578 break; 579 case 'a002': // ExifImageWidth 580 case 'a003': // ExifImageHeight 581 $data = $data. ' '.gettext('pixels'); 582 break; 583 case '0103': // Compression 584 switch ($data) { 585 case 1: $data = gettext('No Compression'); break; 586 case 6: $data = gettext('Jpeg Compression'); break; 587 default: $data = gettext('Unknown').': '.$data; break; 588 } 589 break; 590 case 'a217': // SensingMethod 591 switch ($data) { 592 case 1: $data = gettext('Not defined'); break; 593 case 2: $data = gettext('One Chip Color Area Sensor'); break; 594 case 3: $data = gettext('Two Chip Color Area Sensor'); break; 595 case 4: $data = gettext('Three Chip Color Area Sensor'); break; 596 case 5: $data = gettext('Color Sequential Area Sensor'); break; 597 case 7: $data = gettext('Trilinear Sensor'); break; 598 case 8: $data = gettext('Color Sequential Linear Sensor'); break; 599 default: $data = gettext('Unknown').': '.$data; break; 600 } 601 break; 602 case '0106': // PhotometricInterpretation 603 switch ($data) { 604 case 1: $data = gettext('Monochrome'); break; 605 case 2: $data = gettext('RGB'); break; 606 case 6: $data = gettext('YCbCr'); break; 607 default: $data = gettext('Unknown').': '.$data; break; 608 } 609 break; 610 //case "a408": // Contrast 611 //case "a40a": //Sharpness 612 // switch($data) { 613 // case 0: $data="Normal"; break; 614 // case 1: $data="Soft"; break; 615 // case 2: $data="Hard"; break; 616 // default: $data="Unknown"; break; 617 // } 618 // break; 619 //case "a409": // Saturation 620 // switch($data) { 621 // case 0: $data="Normal"; break; 622 // case 1: $data="Low saturation"; break; 623 // case 2: $data="High saturation"; break; 624 // default: $data="Unknown"; break; 625 // } 626 // break; 627 //case "a402": // Exposure Mode 628 // switch($data) { 629 // case 0: $data="Auto exposure"; break; 630 // case 1: $data="Manual exposure"; break; 631 // case 2: $data="Auto bracket"; break; 632 // default: $data="Unknown"; break; 633 // } 634 // break; 635 } 636 break; 637 case 'UNDEFINED': 638 switch ($tag) { 639 case '9000': // ExifVersion 640 case 'a000': // FlashPixVersion 641 case '0002': // InteroperabilityVersion 642 $data=gettext('version').' '.$data/100; 643 break; 644 case 'a300': // FileSource 645 $data = bin2hex($data); 646 $data = str_replace('00','',$data); 647 $data = str_replace('03',gettext('Digital Still Camera'),$data); 648 break; 649 case 'a301': // SceneType 650 $data = bin2hex($data); 651 $data = str_replace('00','',$data); 652 $data = str_replace('01',gettext('Directly Photographed'),$data); 653 break; 654 case '9101': // ComponentsConfiguration 655 $data = bin2hex($data); 656 $data = str_replace('01','Y',$data); 657 $data = str_replace('02','Cb',$data); 658 $data = str_replace('03','Cr',$data); 659 $data = str_replace('04','R',$data); 660 $data = str_replace('05','G',$data); 661 $data = str_replace('06','B',$data); 662 $data = str_replace('00','',$data); 663 break; 664 //case "9286": //UserComment 665 // $encoding = rtrim(substr($data, 0, 8)); 666 // $data = rtrim(substr($data, 8)); 667 // break; 668 } 669 break; 670 default: 671 $data = bin2hex($data); 672 if ($intel == 1) $data = intel2Moto($data); 673 break; 674 } 675 return $data; 676} 677 678function formatExposure($data) { 679 if (strpos($data,'/')===false) { 680 if ($data >= 1) { 681 return round($data, 2).' '.gettext('sec'); 682 } else { 683 $n=0; $d=0; 684 ConvertToFraction($data, $n, $d); 685 return $n.'/'.$d.' '.gettext('sec'); 686 } 687 } else { 688 return gettext('Bulb'); 689 } 690} 691 692//================================================================================================ 693// Reads one standard IFD entry 694//================================================================================================ 695function read_entry(&$result,$in,$seek,$intel,$ifd_name,$globalOffset) { 696 697 if (feof($in)) { // test to make sure we can still read. 698 $result['Errors'] = $result['Errors']+1; 699 return; 700 } 701 702 // 2 byte tag 703 $tag = bin2hex(fread($in, 2)); 704 if ($intel == 1) $tag = intel2Moto($tag); 705 $tag_name = lookup_tag($tag); 706 707 // 2 byte datatype 708 $type = bin2hex(fread($in, 2)); 709 if ($intel == 1) $type = intel2Moto($type); 710 lookup_type($type, $size); 711 712 if (strpos($tag_name, 'unknown:') !== false && strpos($type, 'error:') !== false) { // we have an error 713 $result['Errors'] = $result['Errors']+1; 714 return; 715 } 716 717 // 4 byte number of elements 718 $count = bin2hex(fread($in, 4)); 719 if ($intel == 1) $count = intel2Moto($count); 720 $bytesofdata = $size*hexdec($count); 721 722 // 4 byte value or pointer to value if larger than 4 bytes 723 $value = fread( $in, 4 ); 724 725 if ($bytesofdata <= 4) { // if datatype is 4 bytes or less, its the value 726 $data = $value; 727 } else if ($bytesofdata < 100000) { // otherwise its a pointer to the value, so lets go get it 728 $value = bin2hex($value); 729 if ($intel == 1) $value = intel2Moto($value); 730 $v = fseek($seek, $globalOffset+hexdec($value)); // offsets are from TIFF header which is 12 bytes from the start of the file 731 if ($v == 0) { 732 $data = fread($seek, $bytesofdata); 733 } else if ($v == -1) { 734 $result['Errors'] = $result['Errors']+1; 735 } 736 } else { // bytesofdata was too big, so the exif had an error 737 $result['Errors'] = $result['Errors']+1; 738 return; 739 } 740 if ($tag_name == 'MakerNote') { // if its a maker tag, we need to parse this specially 741 $make = $result['IFD0']['Make']; 742 if ($result['VerboseOutput'] == 1) { 743 $result[$ifd_name]['MakerNote']['RawData'] = $data; 744 } 745 if (preg_match('/NIKON/i',$make)) { 746 require_once(dirname(__FILE__).'/makers/nikon.php'); 747 parseNikon($data,$result); 748 $result[$ifd_name]['KnownMaker'] = 1; 749 } else if (preg_match('/OLYMPUS/i',$make)) { 750 require_once(dirname(__FILE__).'/makers/olympus.php'); 751 parseOlympus($data,$result,$seek,$globalOffset); 752 $result[$ifd_name]['KnownMaker'] = 1; 753 } else if (preg_match('/Canon/i',$make)) { 754 require_once(dirname(__FILE__).'/makers/canon.php'); 755 parseCanon($data,$result,$seek,$globalOffset); 756 $result[$ifd_name]['KnownMaker'] = 1; 757 } else if (preg_match('/FUJIFILM/i',$make)) { 758 require_once(dirname(__FILE__).'/makers/fujifilm.php'); 759 parseFujifilm($data,$result); 760 $result[$ifd_name]['KnownMaker'] = 1; 761 } else if (preg_match('/SANYO/i',$make)) { 762 require_once(dirname(__FILE__).'/makers/sanyo.php'); 763 parseSanyo($data,$result,$seek,$globalOffset); 764 $result[$ifd_name]['KnownMaker'] = 1; 765 } else if (preg_match('/Panasonic/i',$make)) { 766 require_once(dirname(__FILE__).'/makers/panasonic.php'); 767 parsePanasonic($data,$result,$seek,$globalOffset); 768 $result[$ifd_name]['KnownMaker'] = 1; 769 } else { 770 $result[$ifd_name]['KnownMaker'] = 0; 771 } 772 } else if ($tag_name == 'GPSInfoOffset') { 773 require_once(dirname(__FILE__).'/makers/gps.php'); 774 $formated_data = formatData($type,$tag,$intel,$data); 775 $result[$ifd_name]['GPSInfo'] = $formated_data; 776 parseGPS($data,$result,$formated_data,$seek,$globalOffset); 777 } else { 778 // Format the data depending on the type and tag 779 $formated_data = formatData($type,$tag,$intel,$data); 780 781 $result[$ifd_name][$tag_name] = $formated_data; 782 783 if ($result['VerboseOutput'] == 1) { 784 if ($type == 'URATIONAL' || $type == 'SRATIONAL' || $type == 'USHORT' || $type == 'SSHORT' || $type == 'ULONG' || $type == 'SLONG' || $type == 'FLOAT' || $type == 'DOUBLE') { 785 $data = bin2hex($data); 786 if ($intel == 1) $data = intel2Moto($data); 787 } 788 $result[$ifd_name][$tag_name.'_Verbose']['RawData'] = $data; 789 $result[$ifd_name][$tag_name.'_Verbose']['Type'] = $type; 790 $result[$ifd_name][$tag_name.'_Verbose']['Bytes'] = $bytesofdata; 791 } 792 } 793} 794 795 796//================================================================================================ 797// Pass in a file and this reads the EXIF data 798// 799// Usefull resources 800// http:// www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html 801// http:// www.w3.org/Graphics/JPEG/jfif.txt 802// http:// exif.org/ 803// http:// www.ozhiker.com/electronics/pjmt/library/list_contents.php4 804// http:// www.ozhiker.com/electronics/pjmt/jpeg_info/makernotes.html 805// http:// pel.sourceforge.net/ 806// http:// us2.php.net/manual/en/function.exif-read-data.php 807//================================================================================================ 808function read_exif_data_raw($path,$verbose) { 809 810 if ($path == '' || $path == 'none') return; 811 812 $in = @fopen($path, 'rb'); // the b is for windows machines to open in binary mode 813 $seek = @fopen($path, 'rb'); // There may be an elegant way to do this with one file handle. 814 815 $globalOffset = 0; 816 817 if (!isset($verbose)) $verbose=0; 818 819 $result['VerboseOutput'] = $verbose; 820 $result['Errors'] = 0; 821 822 if (!$in || !$seek) { // if the path was invalid, this error will catch it 823 $result['Errors'] = 1; 824 $result['Error'][$result['Errors']] = gettext('The file could not be found.'); 825 return $result; 826 } 827 828 $GLOBALS['exiferFileSize'] = filesize($path); 829 830 // First 2 bytes of JPEG are 0xFFD8 831 $data = bin2hex(fread( $in, 2 )); 832 if ($data == 'ffd8') { 833 $result['ValidJpeg'] = 1; 834 } else { 835 $result['ValidJpeg'] = 0; 836 fseek($in, 0); 837 } 838 839 $result['ValidIPTCData'] = 0; 840 $result['ValidJFIFData'] = 0; 841 $result['ValidEXIFData'] = 0; 842 $result['ValidAPP2Data'] = 0; 843 $result['ValidCOMData'] = 0; 844 845if ($result['ValidJpeg'] == 1) { 846 847 // LOOP THROUGH MARKERS TILL ffe1 EXIF Marker 848 $abortCount = 0; 849 $header = '\0'; 850 while(!feof($in) && ++$abortCount < 200) { 851 852 // Next 2 bytes are MARKER tag (0xFF**) 853 $data = bin2hex(fread( $in, 2 )); 854 $size = bin2hex(fread( $in, 2 )); 855 856 if ($data == 'ffc0' || $data == 'ffd9') { // Start Of Frame Marker or End of Image Marker 857 break; 858 } else if ($data == 'ffe0') { // JFIF Marker 859 $result['ValidJFIFData'] = 1; 860 $result['JFIF']['Size'] = hexdec($size); 861 862 if (hexdec($size)-2 > 0) { 863 $data = fread( $in, hexdec($size)-2); 864 $result['JFIF']['Data'] = $data; 865 } 866 867 $result['JFIF']['Identifier'] = substr($data,0,5);; 868 $result['JFIF']['ExtensionCode'] = bin2hex(substr($data,6,1)); 869 870 $globalOffset+=hexdec($size)+2; 871 872 } else if ($data == 'ffe1') { // APP1 Marker : EXIF Metadata(TIFF IFD format) or JPEG Thumbnail or Adobe XMP 873 $header = fread( $in, 6 ); // Exif block starts with 'Exif\0\0' header 874 if ($header == "Exif\0\0") { // EXIF Marker ? 875 $result['ValidEXIFData'] = 1; 876 $result['ValidAPP1Data'] = 1; 877 $result['APP1']['Size'] = hexdec($size); 878 break; 879 } else { 880 if (hexdec($size)-2 > 0) { 881 $data = fread( $in, hexdec($size)-2-6); // skip XMP or Thumbnail data, and loop again 882 } 883 $globalOffset+=hexdec($size)+2; 884 } 885 886 } else if ($data == 'ffe2') { // APP2 Marker : EXIF extension 887 $result['ValidAPP2Data'] = 1; 888 $result['APP2']['Size'] = hexdec($size); 889 890 if (hexdec($size)-2 > 0) { 891 $data = fread( $in, hexdec($size)-2); 892 $result['APP2']['Data'] = $data ; 893 } 894 $globalOffset+=hexdec($size)+2; 895 896 } else if ($data == 'ffed') { // IPTC Marker 897 $result['ValidIPTCData'] = 1; 898 $result['IPTC']['Size'] = hexdec($size); 899 900 if (hexdec($size)-2 > 0) { 901 $data = fread( $in, hexdec($size)-2); 902 $result['IPTC']['Data'] = $data ; 903 } 904 $globalOffset+=hexdec($size)+2; 905 906 } else if ($data == 'fffe') { // Comment extension Marker 907 $result['ValidCOMData'] = 1; 908 $result['COM']['Size'] = hexdec($size); 909 910 if (hexdec($size)-2 > 0) { 911 $data = fread( $in, hexdec($size)-2); 912 $result['COM']['Data'] = $data ; 913 } 914 $globalOffset+=hexdec($size)+2; 915 916 } else { // unknown Marker 917 if (hexdec($size)-2 > 0) { 918 $data = fread( $in, hexdec($size)-2); 919 } 920 $globalOffset+=hexdec($size)+2; 921 } 922 } 923 // END MARKER LOOP 924 925 if ($header != "Exif\0\0") { 926 fclose($in); 927 fclose($seek); 928 return $result; 929 } 930 931 932} // END IF ValidJpeg 933 934 // Then theres a TIFF header with 2 bytes of endieness (II or MM) 935 $header = fread( $in, 2 ); 936 if ($header==='II') { 937 $intel=1; 938 $result['Endien'] = 'Intel'; 939 } else if ($header==='MM') { 940 $intel=0; 941 $result['Endien'] = 'Motorola'; 942 } else { 943 $intel=1; // not sure what the default should be, but this seems reasonable 944 $result['Endien'] = 'Unknown'; 945 } 946 947 // 2 bytes of 0x002a 948 $tag = bin2hex(fread( $in, 2 )); 949 950 // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) 951 $offset = bin2hex(fread( $in, 4 )); 952 if ($intel == 1) $offset = intel2Moto($offset); 953 954 // Check for extremely large values here 955 if (hexdec($offset) > 100000) { 956 $result['ValidEXIFData'] = 0; 957 fclose($in); 958 fclose($seek); 959 return $result; 960 } 961 962 if (hexdec($offset)>8) $unknown = fread( $in, hexdec($offset)-8); // fixed this bug in 1.3 963 964 // add 12 to the offset to account for TIFF header 965 if ($result['ValidJpeg'] == 1) { 966 $globalOffset+=12; 967 } 968 969 970 //=========================================================== 971 // Start of IFD0 972 $num = bin2hex(fread( $in, 2 )); 973 if ($intel == 1) $num = intel2Moto($num); 974 $num = hexdec($num); 975 $result['IFD0NumTags'] = $num; 976 977 if ($num<1000) { // 1000 entries is too much and is probably an error. 978 for($i=0; $i<$num; $i++) { 979 read_entry($result,$in,$seek,$intel,'IFD0',$globalOffset); 980 } 981 } else { 982 $result['Errors'] = $result['Errors']+1; 983 $result['Error'][$result['Errors']] = 'Illegal size for IFD0'; 984 } 985 986 // store offset to IFD1 987 $offset = bin2hex(fread( $in, 4 )); 988 if ($intel == 1) $offset = intel2Moto($offset); 989 $result['IFD1Offset'] = hexdec($offset); 990 991 // Check for SubIFD 992 if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) { 993 fclose($in); 994 fclose($seek); 995 return $result; 996 } 997 998 // seek to SubIFD (Value of ExifOffset tag) above. 999 $ExitOffset = $result['IFD0']['ExifOffset']; 1000 $v = fseek($in,$globalOffset+$ExitOffset); 1001 if ($v == -1) { 1002 $result['Errors'] = $result['Errors']+1; 1003 $result['Error'][$result['Errors']] = gettext('Could not Find SubIFD'); 1004 } 1005 1006 //=========================================================== 1007 // Start of SubIFD 1008 $num = bin2hex(fread( $in, 2 )); 1009 if ($intel == 1) $num = intel2Moto($num); 1010 $num = hexdec($num); 1011 $result['SubIFDNumTags'] = $num; 1012 1013 if ($num<1000) { // 1000 entries is too much and is probably an error. 1014 for($i=0; $i<$num; $i++) { 1015 read_entry($result,$in,$seek,$intel,'SubIFD',$globalOffset); 1016 } 1017 } else { 1018 $result['Errors'] = $result['Errors']+1; 1019 $result['Error'][$result['Errors']] = gettext('Illegal size for SubIFD'); 1020 } 1021 1022 // Add the 35mm equivalent focal length: 1023 if (isset($result['IFD0']['FocalLengthIn35mmFilm']) && !isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { // found in the wrong place 1024 $result['SubIFD']['FocalLengthIn35mmFilm'] = $result['IFD0']['FocalLengthIn35mmFilm']; 1025 } 1026 if (!isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { 1027 $result['SubIFD']['FocalLengthIn35mmFilm'] = get35mmEquivFocalLength($result); 1028 } 1029 1030 // Check for IFD1 1031 if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) { 1032 fclose($in); 1033 fclose($seek); 1034 return $result; 1035 } 1036 // seek to IFD1 1037 $v = fseek($in,$globalOffset+$result['IFD1Offset']); 1038 if ($v == -1) { 1039 $result['Errors'] = $result['Errors']+1; 1040 $result['Error'][$result['Errors']] = gettext('Could not Find IFD1'); 1041 } 1042 1043 //=========================================================== 1044 // Start of IFD1 1045 $num = bin2hex(fread( $in, 2 )); 1046 if ($intel == 1) $num = intel2Moto($num); 1047 $num = hexdec($num); 1048 $result['IFD1NumTags'] = $num; 1049 1050 if ($num<1000) { // 1000 entries is too much and is probably an error. 1051 for($i=0; $i<$num; $i++) { 1052 read_entry($result,$in,$seek,$intel,'IFD1',$globalOffset); 1053 } 1054 } else { 1055 $result['Errors'] = $result['Errors']+1; 1056 $result['Error'][$result['Errors']] = gettext('Illegal size for IFD1'); 1057 } 1058 // If verbose output is on, include the thumbnail raw data... 1059 if ($result['VerboseOutput'] == 1 && $result['IFD1']['JpegIFOffset']>0 && $result['IFD1']['JpegIFByteCount']>0) { 1060 $v = fseek($seek,$globalOffset+$result['IFD1']['JpegIFOffset']); 1061 if ($v == 0) { 1062 $data = fread($seek, $result['IFD1']['JpegIFByteCount']); 1063 } else if ($v == -1) { 1064 $result['Errors'] = $result['Errors']+1; 1065 } 1066 $result['IFD1']['ThumbnailData'] = $data; 1067 } 1068 1069 1070 // Check for Interoperability IFD 1071 if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset'] == 0) { 1072 fclose($in); 1073 fclose($seek); 1074 return $result; 1075 } 1076 // Seek to InteroperabilityIFD 1077 $v = fseek($in,$globalOffset+$result['SubIFD']['ExifInteroperabilityOffset']); 1078 if ($v == -1) { 1079 $result['Errors'] = $result['Errors']+1; 1080 $result['Error'][$result['Errors']] = gettext('Could not Find InteroperabilityIFD'); 1081 } 1082 1083 //=========================================================== 1084 // Start of InteroperabilityIFD 1085 $num = bin2hex(fread( $in, 2 )); 1086 if ($intel == 1) $num = intel2Moto($num); 1087 $num = hexdec($num); 1088 $result['InteroperabilityIFDNumTags'] = $num; 1089 1090 if ($num<1000) { // 1000 entries is too much and is probably an error. 1091 for($i=0; $i<$num; $i++) { 1092 read_entry($result,$in,$seek,$intel,'InteroperabilityIFD',$globalOffset); 1093 } 1094 } else { 1095 $result['Errors'] = $result['Errors']+1; 1096 $result['Error'][$result['Errors']] = gettext('Illegal size for InteroperabilityIFD'); 1097 } 1098 fclose($in); 1099 fclose($seek); 1100 return $result; 1101} 1102 1103//========================================================= 1104// Converts a floating point number into a simple fraction. 1105//========================================================= 1106function ConvertToFraction($v, &$n, &$d) { 1107 if ($v == 0) { 1108 $n = 0; 1109 $d = 1; 1110 return; 1111 } 1112 for ($n=1; $n<100; $n++) { 1113 $v1 = 1/$v*$n; 1114 $d = round($v1, 0); 1115 if (abs($d - $v1) < 0.02) return; // within tolarance 1116 } 1117} 1118 1119//================================================================================================ 1120// Calculates the 35mm-equivalent focal length from the reported sensor resolution, by Tristan Harward. 1121//================================================================================================ 1122function get35mmEquivFocalLength(&$result) { 1123 if (isset($result['SubIFD']['ExifImageWidth'])) { 1124 $width = $result['SubIFD']['ExifImageWidth']; 1125 } else { 1126 $width = 0; 1127 } 1128 if (isset($result['SubIFD']['FocalPlaneResolutionUnit'])) { 1129 $units = $result['SubIFD']['FocalPlaneResolutionUnit']; 1130 } else { 1131 $units = ''; 1132 } 1133 $unitfactor = 1; 1134 switch ($units) { 1135 case 'Inch' : $unitfactor = 25.4; break; 1136 case 'Centimeter' : $unitfactor = 10; break; 1137 case 'No Unit' : $unitfactor = 25.4; break; 1138 default : $unitfactor = 25.4; 1139 } 1140 if (isset($result['SubIFD']['FocalPlaneXResolution'])) { 1141 $xres = $result['SubIFD']['FocalPlaneXResolution']; 1142 } else { 1143 $xres = ''; 1144 } 1145 if (isset($result['SubIFD']['FocalLength'])) { 1146 $fl = $result['SubIFD']['FocalLength']; 1147 } else { 1148 $fl = 0; 1149 } 1150 1151 if (($width != 0) && !empty($units) && !empty($xres) && !empty($fl) && !empty($width)) { 1152 $ccdwidth = ($width * $unitfactor) / $xres; 1153 $equivfl = $fl / $ccdwidth*36+0.5; 1154 return $equivfl; 1155 } 1156 return null; 1157} 1158//EOF