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