Overview

Packages

  • Image
  • None

Classes

  • Horde_Image
  • Horde_Image_Base
  • Horde_Image_Effect
  • Horde_Image_Effect_Border
  • Horde_Image_Effect_Gd_DropShadow
  • Horde_Image_Effect_Gd_RoundCorners
  • Horde_Image_Effect_Gd_TextWatermark
  • Horde_Image_Effect_Gd_Unsharpmask
  • Horde_Image_Effect_Im_Border
  • Horde_Image_Effect_Im_CenterCrop
  • Horde_Image_Effect_Im_Composite
  • Horde_Image_Effect_Im_DropShadow
  • Horde_Image_Effect_Im_LiquidResize
  • Horde_Image_Effect_Im_PhotoStack
  • Horde_Image_Effect_Im_PolaroidImage
  • Horde_Image_Effect_Im_RoundCorners
  • Horde_Image_Effect_Im_TextWatermark
  • Horde_Image_Effect_Im_Unsharpmask
  • Horde_Image_Effect_Imagick_Border
  • Horde_Image_Effect_Imagick_CenterCrop
  • Horde_Image_Effect_Imagick_Composite
  • Horde_Image_Effect_Imagick_DropShadow
  • Horde_Image_Effect_Imagick_LiquidResize
  • Horde_Image_Effect_Imagick_PhotoStack
  • Horde_Image_Effect_Imagick_PolaroidImage
  • Horde_Image_Effect_Imagick_RoundCorners
  • Horde_Image_Effect_Imagick_SmartCrop
  • Horde_Image_Effect_Imagick_TextWatermark
  • Horde_Image_Effect_Imagick_Unsharpmask
  • Horde_Image_Exception
  • Horde_Image_Exif
  • Horde_Image_Exif_Base
  • Horde_Image_Exif_Bundled
  • Horde_Image_Exif_Exiftool
  • Horde_Image_Exif_Parser_Base
  • Horde_Image_Exif_Php
  • Horde_Image_Gd
  • Horde_Image_Im
  • Horde_Image_Imagick
  • Horde_Image_Png
  • Horde_Image_Svg
  • Horde_Image_Swf
  • Horde_Image_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Class for dealing with Exif data using a bundled PHP library based on the
  4:  * Exifer code written by and Copyright 2003 Jake Olefsky
  5:  * See: http://www.offsky.com/software/exif/index.php
  6:  *
  7:  * ---
  8:  * This file is licensed under the GPL as stated in Jake Olefsky's original
  9:  * code. Jake has given Horde permission to incorporate Exifer into our
 10:  * codebase.
 11:  *
 12:  * This program is free software; you can redistribute it and/or modify it
 13:  * under the terms of the GNU General Public License as published by the Free
 14:  * Software Foundation; either version 2 of the License, or (at your option)
 15:  * any later version.
 16:  *
 17:  * This program is distributed in the hope that it will be useful, but
 18:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 19:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 20:  * for more details. http://www.horde.org/licenses/gpl
 21:  * ---
 22:  *
 23:  * The original Exifer library has been heavily modified and refactored. All
 24:  * modifications are
 25:  * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
 26:  *
 27:  * @author  Michael J. Rubinsky <mrubinsk@horde.org>
 28:  * @package Image
 29:  */
 30: class Horde_Image_Exif_Bundled extends Horde_Image_Exif_Base
 31: {
 32:     public function getData($image)
 33:     {
 34:         $raw = $this->_readData($image);
 35:         $exif = array();
 36:         foreach ($raw as $key => $value) {
 37:             if ($key == 'IFD0' || $key == 'SubIFD') {
 38:                 foreach ($value as $subkey => $subvalue) {
 39:                     $exif[$subkey] = $subvalue;
 40:                 }
 41:             } else {
 42:                 $exif[$key] = $value;
 43:             }
 44:         }
 45:         // Not really an EXIF property, but an attribute nonetheless...
 46:         // PHP's exif functions return it, so add it here to be consistent.
 47:         $exif['FileSize'] = @filesize($imageFile);
 48: 
 49:         return $this->_processData($exif);
 50:     }
 51: 
 52:     /**
 53:      *
 54:      * @param $path
 55:      *
 56:      * @return array
 57:      */
 58:     protected function _readData($path)
 59:     {
 60:         // There may be an elegant way to do this with one file handle.
 61:         $in = @fopen($path, 'rb');
 62:         $seek = @fopen($path, 'rb');
 63:         $globalOffset = 0;
 64:         $result = array('Errors' => 0);
 65: 
 66:         // if the path was invalid, this error will catch it
 67:         if (!$in || !$seek) {
 68:             $result['Errors'] = 1;
 69:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("The file could not be opened.");
 70:             return $result;
 71:         }
 72: 
 73:         // First 2 bytes of JPEG are 0xFFD8
 74:         $data = bin2hex(fread($in, 2));
 75:         if ($data == 'ffd8') {
 76:             $result['ValidJpeg'] = 1;
 77:         } else {
 78:             $result['ValidJpeg'] = 0;
 79:             fclose($in);
 80:             fclose($seek);
 81:             return $result;
 82:         }
 83: 
 84:         $result['ValidIPTCData'] = 0;
 85:         $result['ValidJFIFData'] = 0;
 86:         $result['ValidEXIFData'] = 0;
 87:         $result['ValidAPP2Data'] = 0;
 88:         $result['ValidCOMData'] = 0;
 89: 
 90:         // Next 2 bytes are marker tag (0xFFE#)
 91:         $data = bin2hex(fread($in, 2));
 92:         $size = bin2hex(fread($in, 2));
 93: 
 94:         // Loop through markers till you get to FFE1 (Exif marker)
 95:         while(!feof($in) && $data != 'ffe1' && $data != 'ffc0' && $data != 'ffd9') {
 96:             switch ($data) {
 97:             case 'ffe0':
 98:                 // JFIF Marker
 99:                 $result['ValidJFIFData'] = 1;
100:                 $result['JFIF']['Size'] = hexdec($size);
101:                 if (hexdec($size) - 2 > 0) {
102:                     $data = fread($in, hexdec($size) - 2);
103:                     $result['JFIF']['Data'] = $data;
104:                 }
105:                 $result['JFIF']['Identifier'] = substr($data, 0, 5);
106:                 $result['JFIF']['ExtensionCode'] = bin2hex(substr($data, 6, 1));
107:                 $globalOffset += hexdec($size) + 2;
108:                 break;
109: 
110:             case 'ffed':
111:                 // IPTC Marker
112:                 $result['ValidIPTCData'] = 1;
113:                 $result['IPTC']['Size'] = hexdec($size);
114:                 if (hexdec($size) - 2 > 0) {
115:                     $data = fread($in, hexdec($size) - 2);
116:                     $result['IPTC']['Data'] = $data ;
117:                 }
118:                 $globalOffset += hexdec($size) + 2;
119:                 break;
120: 
121:             case 'ffe2':
122:                 // EXIF extension Marker
123:                 $result['ValidAPP2Data'] = 1;
124:                 $result['APP2']['Size'] = hexdec($size);
125:                 if (hexdec($size) - 2 > 0) {
126:                     $data = fread($in, hexdec($size) - 2);
127:                     $result['APP2']['Data'] = $data ;
128:                 }
129:                 $globalOffset += hexdec($size) + 2;
130:                 break;
131: 
132:             case 'fffe':
133:                 // COM extension Marker
134:                 $result['ValidCOMData'] = 1;
135:                 $result['COM']['Size'] = hexdec($size);
136:                 if (hexdec($size) - 2 > 0) {
137:                     $data = fread($in, hexdec($size) - 2);
138:                     $result['COM']['Data'] = $data ;
139:                 }
140:                 $globalOffset += hexdec($size) + 2;
141:                 break;
142: 
143:             case 'ffe1':
144:                 $result['ValidEXIFData'] = 1;
145:                 break;
146:             }
147: 
148:             $data = bin2hex(fread($in, 2));
149:             $size = bin2hex(fread($in, 2));
150:         }
151: 
152:         if ($data != 'ffe1') {
153:             fclose($in);
154:             fclose($seek);
155:             return $result;
156:         }
157: 
158:         $result['ValidEXIFData'] = 1;
159: 
160:         // Size of APP1
161:         $result['APP1Size'] = hexdec($size);
162: 
163:         // Start of APP1 block starts with 'Exif' header (6 bytes)
164:         $header = fread($in, 6);
165: 
166:         // Then theres a TIFF header with 2 bytes of endieness (II or MM)
167:         $header = fread($in, 2);
168:         switch ($header) {
169:         case 'II':
170:             $intel = 1;
171:             $result['Endien'] = 'Intel';
172:             break;
173:         case 'MM':
174:             $intel = 0;
175:             $result['Endien'] = 'Motorola';
176:             break;
177:         default:
178:             // not sure what the default should be, but this seems reasonable
179:             $intel = 1;
180:             $result['Endien'] = 'Unknown';
181:             break;
182:         }
183: 
184:         // 2 bytes of 0x002a
185:         $tag = bin2hex(fread( $in, 2 ));
186: 
187:         // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes
188:         // of TIFF header)
189:         $offset = bin2hex(fread($in, 4));
190:         if ($intel == 1) {
191:             $offset = Horde_Image_Exif::intel2Moto($offset);
192:         }
193: 
194:         // Check for extremely large values here
195:         if (hexdec($offset) > 100000) {
196:             $result['ValidEXIFData'] = 0;
197:             fclose($in);
198:             fclose($seek);
199:             return $result;
200:         }
201: 
202:         if (hexdec($offset) > 8) {
203:             $unknown = fread($in, hexdec($offset) - 8);
204:         }
205: 
206:         // add 12 to the offset to account for TIFF header
207:         $globalOffset += 12;
208: 
209:         //===========================================================
210:         // Start of IFD0
211:         $num = bin2hex(fread($in, 2));
212:         if ($intel == 1) {
213:             $num = Horde_Image_Exif::intel2Moto($num);
214:         }
215:         $num = hexdec($num);
216:         $result['IFD0NumTags'] = $num;
217: 
218:         // 1000 entries is too much and is probably an error.
219:         if ($num < 1000) {
220:             for ($i = 0; $i < $num; $i++) {
221:                 $this->_readEntry($result, $in, $seek, $intel, 'IFD0', $globalOffset);
222:             }
223:         } else {
224:             $result['Errors'] = $result['Errors'] + 1;
225:             $result['Error'][$result['Errors']] = 'Illegal size for IFD0';
226:         }
227: 
228:         // store offset to IFD1
229:         $offset = bin2hex(fread($in, 4));
230:         if ($intel == 1) {
231:             $offset = Horde_Image_Exif::intel2Moto($offset);
232:         }
233:         $result['IFD1Offset'] = hexdec($offset);
234: 
235:         // Check for SubIFD
236:         if (!isset($result['IFD0']['ExifOffset']) ||
237:             $result['IFD0']['ExifOffset'] == 0) {
238:             fclose($in);
239:             fclose($seek);
240:             return $result;
241:         }
242: 
243:         // seek to SubIFD (Value of ExifOffset tag) above.
244:         $ExitOffset = $result['IFD0']['ExifOffset'];
245:         $v = fseek($in, $globalOffset + $ExitOffset);
246:         if ($v == -1) {
247:             $result['Errors'] = $result['Errors'] + 1;
248:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find SubIFD");
249:         }
250: 
251:         //===========================================================
252:         // Start of SubIFD
253:         $num = bin2hex(fread($in, 2));
254:         if ($intel == 1) {
255:             $num = Horde_Image_Exif::intel2Moto($num);
256:         }
257:         $num = hexdec($num);
258:         $result['SubIFDNumTags'] = $num;
259: 
260:         // 1000 entries is too much and is probably an error.
261:         if ($num < 1000) {
262:             for ($i = 0; $i < $num; $i++) {
263:                 $this->_readEntry($result, $in, $seek, $intel, 'SubIFD', $globalOffset);
264:             }
265:         } else {
266:             $result['Errors'] = $result['Errors'] + 1;
267:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for SubIFD");
268:         }
269: 
270:         // Add the 35mm equivalent focal length:
271:         // Now properly get this using the FocalLength35mmFilm tag
272:         //$result['SubIFD']['FocalLength35mmEquiv'] = get35mmEquivFocalLength($result);
273: 
274:         // Check for IFD1
275:         if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) {
276:             fclose($in);
277:             fclose($seek);
278:             return $result;
279:         }
280: 
281:         // seek to IFD1
282:         $v = fseek($in, $globalOffset + $result['IFD1Offset']);
283:         if ($v == -1) {
284:             $result['Errors'] = $result['Errors'] + 1;
285:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find IFD1");
286:         }
287: 
288:         //===========================================================
289:         // Start of IFD1
290:         $num = bin2hex(fread($in, 2));
291:         if ($intel == 1) {
292:             $num = Horde_Image_Exif::intel2Moto($num);
293:         }
294:         $num = hexdec($num);
295:         $result['IFD1NumTags'] = $num;
296: 
297:         // 1000 entries is too much and is probably an error.
298:         if ($num < 1000) {
299:             for ($i = 0; $i < $num; $i++) {
300:                 $this->_readEntry($result, $in, $seek, $intel, 'IFD1', $globalOffset);
301:             }
302:         } else {
303:             $result['Errors'] = $result['Errors'] + 1;
304:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for IFD1");
305:         }
306: 
307:         // include the thumbnail raw data...
308:         if ($result['IFD1']['JpegIFOffset'] > 0 &&
309:             $result['IFD1']['JpegIFByteCount'] > 0) {
310:             $v = fseek($seek, $globalOffset + $result['IFD1']['JpegIFOffset']);
311:             if ($v == 0) {
312:                 $data = fread($seek, $result['IFD1']['JpegIFByteCount']);
313:             } else if ($v == -1) {
314:                 $result['Errors'] = $result['Errors'] + 1;
315:             }
316:             $result['IFD1']['ThumbnailData'] = $data;
317:         }
318: 
319:         // Check for Interoperability IFD
320:         if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) ||
321:             $result['SubIFD']['ExifInteroperabilityOffset'] == 0) {
322:             fclose($in);
323:             fclose($seek);
324:             return $result;
325:         }
326: 
327:         // Seek to InteroperabilityIFD
328:         $v = fseek($in, $globalOffset + $result['SubIFD']['ExifInteroperabilityOffset']);
329:         if ($v == -1) {
330:             $result['Errors'] = $result['Errors'] + 1;
331:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find InteroperabilityIFD");
332:         }
333: 
334:         //===========================================================
335:         // Start of InteroperabilityIFD
336:         $num = bin2hex(fread($in, 2));
337:         if ($intel == 1) {
338:             $num = Horde_Image_Exif::intel2Moto($num);
339:         }
340:         $num = hexdec($num);
341:         $result['InteroperabilityIFDNumTags'] = $num;
342: 
343:         // 1000 entries is too much and is probably an error.
344:         if ($num < 1000) {
345:             for ($i = 0; $i < $num; $i++) {
346:                 $this->_readEntry($result, $in, $seek, $intel, 'InteroperabilityIFD', $globalOffset);
347:             }
348:         } else {
349:             $result['Errors'] = $result['Errors'] + 1;
350:             $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for InteroperabilityIFD");
351:         }
352: 
353:         fclose($in);
354:         fclose($seek);
355:         return $result;
356:     }
357: 
358:     /**
359:      *
360:      * @param $result
361:      * @param $in
362:      * @param $seek
363:      * @param $intel
364:      * @param $ifd_name
365:      * @param $globalOffset
366:      * @return unknown_type
367:      */
368:     protected function _readEntry(&$result, $in, $seek, $intel, $ifd_name,
369:                                   $globalOffset)
370:     {
371:         // Still ok to read?
372:         if (feof($in)) {
373:             $result['Errors'] = $result['Errors'] + 1;
374:             return;
375:         }
376: 
377:         // 2 byte tag
378:         $tag = bin2hex(fread($in, 2));
379:         if ($intel == 1) {
380:             $tag = Horde_Image_Exif::intel2Moto($tag);
381:         }
382:         $tag_name = $this->_lookupTag($tag);
383: 
384:         // 2 byte datatype
385:         $type = bin2hex(fread($in, 2));
386:         if ($intel == 1) {
387:             $type = Horde_Image_Exif::intel2Moto($type);
388:         }
389:         $this->_lookupType($type, $size);
390: 
391:         // 4 byte number of elements
392:         $count = bin2hex(fread($in, 4));
393:         if ($intel == 1) {
394:             $count = Horde_Image_Exif::intel2Moto($count);
395:         }
396:         $bytesofdata = $size * hexdec($count);
397: 
398:         // 4 byte value or pointer to value if larger than 4 bytes
399:         $value = fread($in, 4);
400: 
401:         // if datatype is 4 bytes or less, its the value
402:         if ($bytesofdata <= 4) {
403:             $data = $value;
404:         } elseif ($bytesofdata < 100000) {
405:             // otherwise its a pointer to the value, so lets go get it
406:             $value = bin2hex($value);
407:             if ($intel == 1) {
408:                 $value = Horde_Image_Exif::intel2Moto($value);
409:             }
410:             // offsets are from TIFF header which is 12 bytes from the start of file
411:             $v = fseek($seek, $globalOffset+hexdec($value));
412:             if ($v == 0) {
413:                 $data = fread($seek, $bytesofdata);
414:             } elseif ($v == -1) {
415:                 $result['Errors'] = $result['Errors'] + 1;
416:             }
417:         } else {
418:             // bytesofdata was too big, so the exif had an error
419:             $result['Errors'] = $result['Errors'] + 1;
420:             return;
421:         }
422: 
423:         // if its a maker tag, we need to parse this specially
424:         switch ($tag_name) {
425:         case 'MakerNote':
426:             $make = Horde_String::lower($result['IFD0']['Make']);
427:             $parser = null;
428:             if (strpos($make, 'nikon') !== false) {
429:                 $parser = new Horde_Image_Exif_Parser_Nikon();
430:                 $result[$ifd_name]['KnownMaker'] = 1;
431:             } elseif (strpos($make, 'olympus') !== false) {
432:                 $parser = new Horde_Image_Exif_Parser_Olympus();
433:                 $result[$ifd_name]['KnownMaker'] = 1;
434:             } elseif (strpos($make, 'canon') !== false) {
435:                 $parser = new Horde_Image_Exif_Parser_Canon();
436:                 $result[$ifd_name]['KnownMaker'] = 1;
437:             } elseif (strpos($make, 'fujifilm') !== false) {
438:                 $parser = new Horde_Image_Exif_Parser_Fujifilm();
439:                 $result[$ifd_name]['KnownMaker'] = 1;
440:             } elseif (strpos($make, 'sanyo') !== false) {
441:                 $parser = new Horde_Image_Exif_Parser_Sanyo();
442:                 $result[$ifd_name]['KnownMaker'] = 1;
443:             } elseif (strpos($make, 'panasonic') !== false) {
444:                 $parser = new Horde_Image_Exif_Parser_Panasonic();
445:                 $result[$ifd_name]['KnownMaker'] = 1;
446:             } else {
447:                 $result[$ifd_name]['KnownMaker'] = 0;
448:             }
449:             if ($parser) {
450:                 $parser->parse($data, $result, $seek, $globalOffset);
451:             }
452:             break;
453: 
454:         case 'GPSInfoOffset':
455:             $formated_data = $this->_formatData($type, $tag, $intel, $data);
456:             $result[$ifd_name]['GPSInfo'] = $formated_data;
457:             $parser = new Horde_Image_Exif_Parser_Gps();
458:             $parser->parse($data, $result, $formated_data, $seek, $globalOffset);
459:             break;
460: 
461:         default:
462:             // Format the data depending on the type and tag
463:             $formated_data = $this->_formatData($type, $tag, $intel, $data);
464:             $result[$ifd_name][$tag_name] = $formated_data;
465:         }
466:     }
467: 
468:     /**
469:      *
470:      * @param $tag
471:      * @return unknown_type
472:      */
473:     protected function _lookupTag($tag)
474:     {
475:         switch($tag)
476:         {
477:             // used by IFD0 'Camera Tags'
478:             // text string up to 999 bytes long
479:             case '000b': $tag = 'ACDComment'; break;
480:             // integer -2147483648 to 2147483647
481:             case '00fe': $tag = 'ImageType'; break;
482:             // ?? Please send sample image with this tag
483:             case '0106': $tag = 'PhotometricInterpret'; break;
484:             // text string up to 999 bytes long
485:             case '010e': $tag = 'ImageDescription'; break;
486:             // text string up to 999 bytes long
487:             case '010f': $tag = 'Make'; break;
488:             // text string up to 999 bytes long
489:             case '0110': $tag = 'Model'; break;
490:             // integer values 1-9
491:             case '0112': $tag = 'Orientation'; break;
492:             // integer 0-65535
493:             case '0115': $tag = 'SamplePerPixel'; break;
494:             // positive rational number
495:             case '011a': $tag = 'xResolution'; break;
496:             // positive rational number
497:             case '011b': $tag = 'yResolution'; break;
498:             // integer values 1-2
499:             case '011c': $tag = 'PlanarConfig'; break;
500:             // integer values 1-3
501:             case '0128': $tag = 'ResolutionUnit'; break;
502:             // text string up to 999 bytes long
503:             case '0131': $tag = 'Software'; break;
504:             // YYYY:MM:DD HH:MM:SS
505:             case '0132': $tag = 'DateTime'; break;
506:             // text string up to 999 bytes long
507:             case '013b': $tag = 'Artist'; break;
508:             // text string
509:             case '013c': $tag = 'HostComputer'; break;
510:             // two positive rational numbers
511:             case '013e': $tag = 'WhitePoint'; break;
512:             // six positive rational numbers
513:             case '013f': $tag = 'PrimaryChromaticities'; break;
514:             // three positive rational numbers
515:             case '0211': $tag = 'YCbCrCoefficients'; break;
516:             // integer values 1-2
517:             case '0213': $tag = 'YCbCrPositioning'; break;
518:             // six positive rational numbers
519:             case '0214': $tag = 'ReferenceBlackWhite'; break;
520:             // text string up to 999 bytes long
521:             case '8298': $tag = 'Copyright'; break;
522:             // ??
523:             case '8649': $tag = 'PhotoshopSettings'; break;
524:             case '8825': $tag = 'GPSInfoOffset'; break;
525:             // positive integer
526:             case '8769': $tag = 'ExifOffset'; break;
527: 
528:             // used by Exif SubIFD 'Image Tags'
529:             // seconds or fraction of seconds 1/x
530:             case '829a': $tag = 'ExposureTime'; break;
531:             // positive rational number
532:             case '829d': $tag = 'FNumber'; break;
533:             // integer value 1-9
534:             case '8822': $tag = 'ExposureProgram'; break;
535:             // ??
536:             case '8824': $tag = 'SpectralSensitivity'; break;
537:             // integer 0-65535
538:             case '8827': $tag = 'ISOSpeedRatings'; break;
539:             // ??
540:             case '9000': $tag = 'ExifVersion'; break;
541:             // YYYY:MM:DD HH:MM:SS
542:             case '9003': $tag = 'DateTimeOriginal'; break;
543:             // YYYY:MM:DD HH:MM:SS
544:             case '9004': $tag = 'DateTimedigitized'; break;
545:             // ??
546:             case '9101': $tag = 'ComponentsConfiguration'; break;
547:             // positive rational number
548:             case '9102': $tag = 'CompressedBitsPerPixel'; break;
549:             // seconds or fraction of seconds 1/x
550:             case '9201': $tag = 'ShutterSpeedValue'; break;
551:             // positive rational number
552:             case '9202': $tag = 'ApertureValue'; break;
553:             // positive rational number
554:             case '9203': $tag = 'BrightnessValue'; break;
555:             // positive rational number (EV)
556:             case '9204': $tag = 'ExposureBiasValue'; break;
557:             // positive rational number
558:             case '9205': $tag = 'MaxApertureValue'; break;
559:             // positive rational number (meters)
560:             case '9206': $tag = 'SubjectDistance'; break;
561:             // integer 1-6 and 255
562:             case '9207': $tag = 'MeteringMode'; break;
563:             // integer 1-255
564:             case '9208': $tag = 'LightSource'; break;
565:             // integer 1-255
566:             case '9209': $tag = 'Flash'; break;
567:             // positive rational number (mm)
568:             case '920a': $tag = 'FocalLength'; break;
569:             // text string up to 999 bytes long
570:             case '9213': $tag = 'ImageHistory'; break;
571:             // a bunch of data
572:             case '927c': $tag = 'MakerNote'; break;
573:             // text string
574:             case '9286': $tag = 'UserComment'; break;
575:             // text string up to 999 bytes long
576:             case '9290': $tag = 'SubsecTime'; break;
577:             // text string up to 999 bytes long
578:             case '9291': $tag = 'SubsecTimeOriginal'; break;
579:             // text string up to 999 bytes long
580:             case '9292': $tag = 'SubsecTimeDigitized'; break;
581:             // ??
582:             case 'a000': $tag = 'FlashPixVersion'; break;
583:             // values 1 or 65535
584:             case 'a001': $tag = 'ColorSpace'; break;
585:             // ingeter 1-65535
586:             case 'a002': $tag = 'ExifImageWidth'; break;
587:             // ingeter 1-65535
588:             case 'a003': $tag = 'ExifImageHeight'; break;
589:             // text string 12 bytes long
590:             case 'a004': $tag = 'RelatedSoundFile'; break;
591:             // positive integer
592:             case 'a005': $tag = 'ExifInteroperabilityOffset'; break;
593:             // ??
594:             case 'a20c': $tag = 'SpacialFreqResponse'; break;
595:             // positive rational number
596:             case 'a20b': $tag = 'FlashEnergy'; break;
597:             // positive rational number
598:             case 'a20e': $tag = 'FocalPlaneXResolution'; break;
599:             // positive rational number
600:             case 'a20f': $tag = 'FocalPlaneYResolution'; break;
601:             // values 1-3
602:             case 'a210': $tag = 'FocalPlaneResolutionUnit'; break;
603:             // two integers 0-65535
604:             case 'a214': $tag = 'SubjectLocation'; break;
605:             // positive rational number
606:             case 'a215': $tag = 'ExposureIndex'; break;
607:             // values 1-8
608:             case 'a217': $tag = 'SensingMethod'; break;
609:             // integer
610:             case 'a300': $tag = 'FileSource'; break;
611:             // integer
612:             case 'a301': $tag = 'SceneType'; break;
613:             // undefined data type
614:             case 'a302': $tag = 'CFAPattern'; break;
615:             // values 0 or 1
616:             case 'a401': $tag = 'CustomerRender'; break;
617:             // values 0-2
618:             case 'a402': $tag = 'ExposureMode'; break;
619:             // values 0 or 1
620:             case 'a403': $tag = 'WhiteBalance'; break;
621:             // positive rational number
622:             case 'a404': $tag = 'DigitalZoomRatio'; break;
623:             case 'a405': $tag = 'FocalLengthIn35mmFilm';break;
624:             // values 0-3
625:             case 'a406': $tag = 'SceneCaptureMode'; break;
626:             // values 0-4
627:             case 'a407': $tag = 'GainControl'; break;
628:             // values 0-2
629:             case 'a408': $tag = 'Contrast'; break;
630:             // values 0-2
631:             case 'a409': $tag = 'Saturation'; break;
632:             // values 0-2
633:             case 'a40a': $tag = 'Sharpness'; break;
634: 
635:             // used by Interoperability IFD
636:             // text string 3 bytes long
637:             case '0001': $tag = 'InteroperabilityIndex'; break;
638:             // datatype undefined
639:             case '0002': $tag = 'InteroperabilityVersion'; break;
640:             // text string up to 999 bytes long
641:             case '1000': $tag = 'RelatedImageFileFormat'; break;
642:             // integer in range 0-65535
643:             case '1001': $tag = 'RelatedImageWidth'; break;
644:             // integer in range 0-65535
645: 
646:             case '1002': $tag = 'RelatedImageLength'; break;
647:             // used by IFD1 'Thumbnail'
648:             // integer in range 0-65535
649:             case '0100': $tag = 'ImageWidth'; break;
650:             // integer in range 0-65535
651:             case '0101': $tag = 'ImageLength'; break;
652:             // integers in range 0-65535
653:             case '0102': $tag = 'BitsPerSample'; break;
654:             // values 1 or 6
655:             case '0103': $tag = 'Compression'; break;
656:             // values 0-4
657:             case '0106': $tag = 'PhotometricInterpretation'; break;
658:             // text string up to 999 bytes long
659:             case '010e': $tag = 'ThumbnailDescription'; break;
660:             // text string up to 999 bytes long
661:             case '010f': $tag = 'ThumbnailMake'; break;
662:             // text string up to 999 bytes long
663:             case '0110': $tag = 'ThumbnailModel'; break;
664:             // ??
665:             case '0111': $tag = 'StripOffsets'; break;
666:             // integer 1-9
667:             case '0112': $tag = 'ThumbnailOrientation'; break;
668:             // ??
669:             case '0115': $tag = 'SamplesPerPixel'; break;
670:             // ??
671:             case '0116': $tag = 'RowsPerStrip'; break;
672:             // ??
673:             case '0117': $tag = 'StripByteCounts'; break;
674:             // positive rational number
675:             case '011a': $tag = 'ThumbnailXResolution'; break;
676:             // positive rational number
677:             case '011b': $tag = 'ThumbnailYResolution'; break;
678:             // values 1 or 2
679:             case '011c': $tag = 'PlanarConfiguration'; break;
680:             // values 1-3
681:             case '0128': $tag = 'ThumbnailResolutionUnit'; break;
682:             case '0201': $tag = 'JpegIFOffset'; break;
683:             case '0202': $tag = 'JpegIFByteCount'; break;
684:             case '0212': $tag = 'YCbCrSubSampling'; break;
685: 
686:             // misc
687:             case '00ff': $tag = 'SubfileType'; break;
688:             case '012d': $tag = 'TransferFunction'; break;
689:             case '013d': $tag = 'Predictor'; break;
690:             case '0142': $tag = 'TileWidth'; break;
691:             case '0143': $tag = 'TileLength'; break;
692:             case '0144': $tag = 'TileOffsets'; break;
693:             case '0145': $tag = 'TileByteCounts'; break;
694:             case '014a': $tag = 'SubIFDs'; break;
695:             case '015b': $tag = 'JPEGTables'; break;
696:             case '828d': $tag = 'CFARepeatPatternDim'; break;
697:             case '828e': $tag = 'CFAPattern'; break;
698:             case '828f': $tag = 'BatteryLevel'; break;
699:             case '83bb': $tag = 'IPTC/NAA'; break;
700:             case '8773': $tag = 'InterColorProfile'; break;
701: 
702:             case '8828': $tag = 'OECF'; break;
703:             case '8829': $tag = 'Interlace'; break;
704:             case '882a': $tag = 'TimeZoneOffset'; break;
705:             case '882b': $tag = 'SelfTimerMode'; break;
706:             case '920b': $tag = 'FlashEnergy'; break;
707:             case '920c': $tag = 'SpatialFrequencyResponse'; break;
708:             case '920d': $tag = 'Noise'; break;
709:             case '9211': $tag = 'ImageNumber'; break;
710:             case '9212': $tag = 'SecurityClassification'; break;
711:             case '9214': $tag = 'SubjectLocation'; break;
712:             case '9215': $tag = 'ExposureIndex'; break;
713:             case '9216': $tag = 'TIFF/EPStandardID'; break;
714:             case 'a20b': $tag = 'FlashEnergy'; break;
715: 
716:             default: $tag = 'unknown:'.$tag; break;
717:         }
718: 
719:         return $tag;
720:     }
721: 
722:     /**
723:      *
724:      * @param $type
725:      * @param $tag
726:      * @param $intel
727:      * @param $data
728:      * @return unknown_type
729:      */
730:     protected function _formatData($type, $tag, $intel, $data)
731:     {
732:         switch ($type) {
733:         case 'ASCII':
734:             // Search for a null byte and stop there.
735:             if (($pos = strpos($data, chr(0))) !== false) {
736:                 $data = substr($data, 0, $pos);
737:             }
738:             // Format certain kinds of strings nicely (Camera make etc.)
739:             if ($tag == '010f') {
740:                 $data = ucwords(strtolower(trim($data)));
741:             }
742:             break;
743: 
744:         case 'URATIONAL':
745:         case 'SRATIONAL':
746:             $data = bin2hex($data);
747:             if ($intel == 1) {
748:                 $data = Horde_Image_Exif::intel2Moto($data);
749:             }
750: 
751:             if ($intel == 1) {
752:                 // intel stores them bottom-top
753:                 $top = hexdec(substr($data, 8, 8));
754:             } else {
755:                 // motorola stores them top-bottom
756:                 $top = hexdec(substr($data, 0, 8));
757:             }
758: 
759:             if ($intel == 1) {
760:                 // intel stores them bottom-top
761:                 $bottom = hexdec(substr($data, 0, 8));
762:             } else {
763:                 // motorola stores them top-bottom
764:                 $bottom = hexdec(substr($data, 8, 8));
765:             }
766: 
767:             if ($type == 'SRATIONAL' && $top > 2147483647) {
768:                 // this makes the number signed instead of unsigned
769:                 $top = $top - 4294967296;
770:             }
771:             if ($bottom != 0) {
772:                 $data = $top / $bottom;
773:             } elseif ($top == 0) {
774:                 $data = 0;
775:             } else {
776:                 $data = $top . '/' . $bottom;
777:             }
778: 
779:             // Exposure Time
780:             if ($tag == '829a') {
781:                 if ($bottom != 0) {
782:                     $data = $top . '/' . $bottom;
783:                 } else {
784:                     $data = 0;
785:                 }
786:             }
787:             break;
788: 
789:         case 'USHORT':
790:         case 'SSHORT':
791:         case 'ULONG':
792:         case 'SLONG':
793:         case 'FLOAT':
794:         case 'DOUBLE':
795:             $data = bin2hex($data);
796:             if ($intel == 1) {
797:                 $data = Horde_Image_Exif::intel2Moto($data);
798:             }
799:             if ($intel == 0 && ($type == 'USHORT' || $type == 'SSHORT')) {
800:                 $data = substr($data, 0, 4);
801:             }
802:             $data = hexdec($data);
803:             if ($type == 'SSHORT' && $data > 32767) {
804:                 // this makes the number signed instead of unsigned
805:                 $data = $data - 65536;
806:             }
807:             if ($type == 'SLONG' && $data > 2147483647) {
808:                 // this makes the number signed instead of unsigned
809:                 $data = $data - 4294967296;
810:             }
811:             break;
812: 
813:         case 'UNDEFINED':
814:             // ExifVersion,FlashPixVersion,InteroperabilityVersion
815:             if ($tag == '9000' || $tag == 'a000' || $tag == '0002') {
816:                 $data = sprintf(Horde_Image_Translation::t("version %d"), $data / 100);
817:             }
818:             break;
819: 
820:         default:
821:             $data = bin2hex($data);
822:             if ($intel == 1) {
823:                 $data = Horde_Image_Exif::intel2Moto($data);
824:             }
825:             break;
826:         }
827: 
828:         return $data;
829:     }
830: 
831:     /**
832:      *
833:      * @param $type
834:      * @param $size
835:      * @return unknown_type
836:      */
837:     protected function _lookupType(&$type, &$size)
838:     {
839:         switch ($type) {
840:         case '0001': $type = 'UBYTE'; $size = 1; break;
841:         case '0002': $type = 'ASCII'; $size = 1; break;
842:         case '0003': $type = 'USHORT'; $size = 2; break;
843:         case '0004': $type = 'ULONG'; $size = 4; break;
844:         case '0005': $type = 'URATIONAL'; $size = 8; break;
845:         case '0006': $type = 'SBYTE'; $size = 1; break;
846:         case '0007': $type = 'UNDEFINED'; $size = 1; break;
847:         case '0008': $type = 'SSHORT'; $size = 2; break;
848:         case '0009': $type = 'SLONG'; $size = 4; break;
849:         case '000a': $type = 'SRATIONAL'; $size = 8; break;
850:         case '000b': $type = 'FLOAT'; $size = 4; break;
851:         case '000c': $type = 'DOUBLE'; $size = 8; break;
852:         default: $type = 'error:'.$type; $size = 0; break;
853:         }
854: 
855:         return $type;
856:     }
857: 
858:     public function supportedCategories()
859:     {
860:         return array('EXIF');
861:     }
862: 
863: }
API documentation generated by ApiGen