Overview

Packages

  • Image
  • None

Classes

  • Horde_Image_Exif_Parser_Canon
  • Horde_Image_Exif_Parser_Fujifilm
  • Horde_Image_Exif_Parser_Gps
  • Horde_Image_Exif_Parser_Nikon
  • Horde_Image_Exif_Parser_Olympus
  • Horde_Image_Exif_Parser_Panasonic
  • Horde_Image_Exif_Parser_Sanyo
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @author   Michael J. Rubinsky <mrubinsk@horde.org>
  4:  * @author   Jan Schneider <jan@horde.org>
  5:  * @category Horde
  6:  * @package  Image
  7:  */
  8: 
  9: /**
 10:  * Exifer
 11:  * Extracts EXIF information from digital photos.
 12:  *
 13: 
 14:  * http://www.offsky.com/software/exif/index.php
 15:  * jake@olefsky.com
 16:  *
 17:  * ------------
 18:  *
 19:  * This program is free software; you can redistribute it and/or modify it
 20:  * under the terms of the GNU General Public License as published by the Free
 21:  * Software Foundation; either version 2 of the License, or (at your option)
 22:  * any later version.
 23:  *
 24:  * This program is distributed in the hope that it will be useful, but WITHOUT
 25:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 26:  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 27:  * more details. http://www.horde.org/licenses/gpl
 28:  */
 29: class Horde_Image_Exif_Parser_Gps extends Horde_Image_Exif_Parser_Base
 30: {
 31:     /**
 32:      * Looks up the name of the tag
 33:      *
 34:      * @param unknown_type $tag
 35:      * @return string
 36:      */
 37:     protected function _lookupTag($tag)
 38:     {
 39:         switch($tag) {
 40:         case '0000': return 'Version';
 41:         //north or south
 42:         case '0001': return 'LatitudeRef';
 43:         //dd mm.mm or dd mm ss
 44:         case '0002': return 'Latitude';
 45:         //east or west
 46:         case '0003': return 'LongitudeRef';
 47:         //dd mm.mm or dd mm ss
 48:         case '0004': return 'Longitude';
 49:         //sea level or below sea level
 50:         case '0005': return 'AltitudeRef';
 51:         //positive rational number
 52:         case '0006': return 'Altitude';
 53:         //three positive rational numbers
 54:         case '0007': return 'Time';
 55:         //text string up to 999 bytes long
 56:         case '0008': return 'Satellite';
 57:             //in progress or interop
 58:         case '0009': return 'ReceiveStatus';
 59:         //2D or 3D
 60:         case '000a': return 'MeasurementMode';
 61:         //positive rational number
 62:         case '000b': return 'MeasurementPrecision';
 63:         //KPH, MPH, knots
 64:         case '000c': return 'SpeedUnit';
 65:             //positive rational number
 66:         case '000d': return 'ReceiverSpeed';
 67:         //true or magnetic north
 68:         case '000e': return 'MovementDirectionRef';
 69:         //positive rational number
 70:         case '000f': return 'MovementDirection';
 71:         //true or magnetic north
 72:         case '0010': return 'ImageDirectionRef';
 73:             //positive rational number
 74:         case '0011': return 'ImageDirection';
 75:         //text string up to 999 bytes long
 76:         case '0012': return 'GeodeticSurveyData';
 77:         //north or south
 78:         case '0013': return 'DestLatitudeRef';
 79:         //three positive rational numbers
 80:         case '0014': return 'DestinationLatitude';
 81:         //east or west
 82:         case '0015': return 'DestLongitudeRef';
 83:         //three positive rational numbers
 84:         case '0016': return 'DestinationLongitude';
 85:             //true or magnetic north
 86:         case '0017': return 'DestBearingRef';
 87:         //positive rational number
 88:         case '0018': return 'DestinationBearing';
 89:         //km, miles, knots
 90:         case '0019': return 'DestDistanceRef';
 91:         //positive rational number
 92:         case '001a': return 'DestinationDistance';
 93:         case '001b': return 'ProcessingMethod';
 94:         case '001c': return 'AreaInformation';
 95:         //text string 10 bytes long
 96:         case '001d': return 'Datestamp';
 97:         //integer in range 0-65535
 98:         case '001e': return 'DifferentialCorrection';
 99:         default: return 'unknown: ' . $tag;
100:         }
101:     }
102: 
103:     /**
104:      * Formats a rational number
105:      */
106:     protected function _rational($data, $intel)
107:     {
108:         if ($intel == 1) {
109:             //intel stores them bottom-top
110:             $top = hexdec(substr($data, 8, 8));
111:         } else {
112:             //motorola stores them top-bottom
113:             $top = hexdec(substr($data, 0, 8));
114:         }
115: 
116:         if ($intel == 1) {
117:             $bottom = hexdec(substr($data, 0, 8));
118:         } else {
119:             $bottom = hexdec(substr($data, 8, 8));
120:         }
121: 
122:         if ($bottom != 0) {
123:             $data = $top / $bottom;
124:         } elseif ($top == 0) {
125:             $data = 0;
126:         } else {
127:             $data = $top . '/' . $bottom;
128:         }
129: 
130:         return $data;
131:     }
132: 
133:     /**
134:      * Formats Data for the data type
135:      */
136:     protected function _formatData($type, $tag, $intel, $data)
137:     {
138:         switch ($type) {
139:         case 'ASCII':
140:             // Latitude Reference, Longitude Reference
141:             if ($tag == '0001' || $tag == '0003') {
142:                 $data = ($data{1} == $data{2} && $data{1} == $data{3}) ? $data{0} : $data;
143:             }
144:             break;
145: 
146:         case 'URATIONAL':
147:         case 'SRATIONAL':
148:             $data = bin2hex($data);
149:             if ($intel == 1) {
150:                 $data = Horde_Image_Exif::intel2Moto($data);
151:             }
152:             if ($intel == 1) {
153:                 //intel stores them bottom-top
154:                 $top = hexdec(substr($data, 8, 8));
155:             } else {
156:                 //motorola stores them top-bottom
157:                 $top = hexdec(substr($data, 0, 8));
158:             }
159: 
160:             if ($intel == 1) {
161:                 $bottom = hexdec(substr($data, 0, 8));
162:             } else {
163:                 $bottom = hexdec(substr($data, 8, 8));
164:             }
165: 
166:             if ($type == 'SRATIONAL' && $top > 2147483647) {
167:                 // make the number signed instead of unsigned
168:                 $top = $top - 4294967296;
169:             }
170: 
171:             switch ($tag) {
172:             case '0002':
173:             case '0004':
174:                 //Latitude, Longitude
175:                 if ($intel == 1) {
176:                     $seconds = $this->_rational(substr($data, 0, 16), $intel);
177:                     $hour = $this->_rational(substr($data, 32, 16), $intel);
178:                 } else {
179:                     $hour = $this->_rational(substr($data, 0, 16), $intel);
180:                     $seconds = $this->_rational(substr($data, 32, 16), $intel);
181:                 }
182:                 $minutes = $this->_rational(substr($data, 16, 16), $intel);
183:                 $data = array($hour, $minutes, $seconds);
184:                 break;
185: 
186:             case '0007':
187:                 //Time
188:                 $seconds = $this->_rational(substr($data, 0, 16), $intel);
189:                 $minutes = $this->_rational(substr($data, 16, 16), $intel);
190:                 $hour = $this->_rational(substr($data, 32, 16), $intel);
191:                 $data = $hour . ':' . $minutes . ':' . $seconds;
192:                 break;
193: 
194:             default:
195:                 if ($bottom != 0) {
196:                     $data = $top / $bottom;
197:                 } elseif ($top == 0) {
198:                     $data = 0;
199:                 } else {
200:                     $data = $top . '/' . $bottom;
201:                 }
202:                 if ($tag == '0006') {
203:                     $data .= 'm';
204:                 }
205:                 break;
206:             }
207:             break;
208: 
209:         case 'USHORT':
210:         case 'SSHORT':
211:         case 'ULONG':
212:         case 'SLONG':
213:         case 'FLOAT':
214:         case 'DOUBLE':
215:             $data = bin2hex($data);
216:             if ($intel == 1) {
217:                 $data = Horde_Image_Exif::intel2Moto($data);
218:             }
219:             $data = hexdec($data);
220:             break;
221: 
222:         case 'UNDEFINED':
223:             break;
224: 
225:         case 'UBYTE':
226:             $data = bin2hex($data);
227:             if ($intel == 1) {
228:                 $num = Horde_Image_Exif::intel2Moto($data);
229:             }
230:             switch ($tag) {
231:             case '0000':
232:                 // VersionID
233:                 $data = hexdec(substr($data, 0, 2))
234:                     . '.' . hexdec(substr($data, 2, 2))
235:                     . '.' . hexdec(substr($data, 4, 2))
236:                     . '.'. hexdec(substr($data, 6, 2));
237:                 break;
238:             case '0005':
239:                 // Altitude Reference
240:                 if ($data == '00000000') {
241:                     $data = 'Above Sea Level';
242:                 } elseif ($data == '01000000') {
243:                     $data = 'Below Sea Level';
244:                 }
245:                 break;
246:             }
247:             break;
248: 
249:         default:
250:             $data = bin2hex($data);
251:             if ($intel == 1) {
252:                 $data = Horde_Image_Exif::intel2Moto($data);
253:             }
254:             break;
255:         }
256: 
257:         return $data;
258:     }
259: 
260:     /**
261:      * GPS Special data section
262:      *
263:      * @see http://drewnoakes.com/code/exif/sampleOutput.html
264:      * @see http://www.geosnapper.com
265:      */
266:     public function parse($block, &$result, $offset, $seek, $globalOffset)
267:     {
268:         if ($result['Endien'] == 'Intel') {
269:             $intel = 1;
270:         } else {
271:             $intel = 0;
272:         }
273: 
274:         //offsets are from TIFF header which is 12 bytes from the start of the
275:         //file
276:         $v = fseek($seek, $globalOffset + $offset);
277:         if ($v == -1) {
278:             $result['Errors'] = $result['Errors']++;
279:         }
280: 
281:         $num = bin2hex(fread($seek, 2));
282:         if ($intel == 1) {
283:             $num = Horde_Image_Exif::intel2Moto($num);
284:         }
285:         $num = hexdec($num);
286:         $result['GPS']['NumTags'] = $num;
287:         $block = fread($seek, $num * 12);
288:         $place = 0;
289: 
290:         //loop thru all tags  Each field is 12 bytes
291:         for ($i = 0; $i < $num; $i++) {
292:             //2 byte tag
293:             $tag = bin2hex(substr($block, $place, 2));
294:             $place += 2;
295:             if ($intel == 1) {
296:                 $tag = Horde_Image_Exif::intel2Moto($tag);
297:             }
298:             $tag_name = $this->_lookupTag($tag);
299: 
300:             //2 byte datatype
301:             $type = bin2hex(substr($block, $place, 2));
302:             $place += 2;
303:             if ($intel == 1) {
304:                 $type = Horde_Image_Exif::intel2Moto($type);
305:             }
306:             $this->_lookupType($type, $size);
307: 
308:             //4 byte number of elements
309:             $count = bin2hex(substr($block, $place, 4));
310:             $place += 4;
311:             if ($intel==1) {
312:                 $count = Horde_Image_Exif::intel2Moto($count);
313:             }
314:             $bytesofdata = $size * hexdec($count);
315: 
316:             //4 byte value or pointer to value if larger than 4 bytes
317:             $value = substr($block, $place, 4);
318:             $place += 4;
319: 
320:             if ($bytesofdata <= 4) {
321:                 $data = $value;
322:             } else {
323:                 $value = bin2hex($value);
324:                 if ($intel == 1) {
325:                     $value = Horde_Image_Exif::intel2Moto($value);
326:                 }
327:                 //offsets are from TIFF header which is 12 bytes from the start
328:                 //of the file
329:                 $v = fseek($seek, $globalOffset + hexdec($value));
330:                 if ($v == 0) {
331:                     $data = fread($seek, $bytesofdata);
332:                 } elseif ($v == -1) {
333:                     $result['Errors'] = $result['Errors']++;
334:                 }
335:             }
336:             $result['GPS' . $tag_name] = $this->_formatData($type, $tag, $intel, $data);
337:         }
338:     }
339: }
340: 
API documentation generated by ApiGen