source: extensions/AMetaData/JpegMetaData/Readers/IfdReader.class.php @ 5038

Last change on this file since 5038 was 5038, checked in by grum, 14 years ago

Manage multiple lenses for one Id for Pentax camera
Add some minors features

  • Property svn:executable set to *
File size: 22.5 KB
Line 
1<?php
2/*
3 * --:: JPEG MetaDatas ::-------------------------------------------------------
4 *
5 *  Author    : Grum
6 *   email    : grum at piwigo.org
7 *   website  : http://photos.grum.fr
8 *
9 *   << May the Little SpaceFrog be with you ! >>
10 *
11 *
12 * +-----------------------------------------------------------------------+
13 * | JpegMetaData - a PHP based Jpeg Metadata manager                      |
14 * +-----------------------------------------------------------------------+
15 * | Copyright(C) 2010  Grum - http://www.grum.fr                          |
16 * +-----------------------------------------------------------------------+
17 * | This program is free software; you can redistribute it and/or modify  |
18 * | it under the terms of the GNU General Public License as published by  |
19 * | the Free Software Foundation                                          |
20 * |                                                                       |
21 * | This program is distributed in the hope that it will be useful, but   |
22 * | WITHOUT ANY WARRANTY; without even the implied warranty of            |
23 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
24 * | General Public License for more details.                              |
25 * |                                                                       |
26 * | You should have received a copy of the GNU General Public License     |
27 * | along with this program; if not, write to the Free Software           |
28 * | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
29 * | USA.                                                                  |
30 * +-----------------------------------------------------------------------+
31 *
32 *
33 * -----------------------------------------------------------------------------
34 *
35 * The IfdReader class is the dedicated to read IFDs from a TIFF structure
36 *
37 * =====> See TiffReader.class.php to know more about a Tiff structure <========
38 *
39 *
40 * An IFD is formatted as this :
41 *  - number of IFD entries : 2 bytes
42 *  - entries               : 12 bytes x number of entries
43 *  - next IFD offset       : 4 bytes
44 *  - extra datas           : number of bytes depends of the stored extra datas
45 *
46 * ====> See IfdEntryReader.class.php to know more about an IFD structure <=====
47 *
48 * -----------------------------------------------------------------------------
49 *
50 * .. Notes ..
51 *
52 *
53 * The Ifdreader class is derived from the GenericReader class.
54 *
55 * ======> See GenericReader.class.php to know more about common methods <======
56 *
57 *
58 * Derived classes :
59 *  - GpsReader         (for exif GPS tags)
60 *  - MakerNotesReader  (for maker notes tags)
61 *  - PentaxReader      (for Pentax exif tags, in fact derived from
62 *                      MakerNotesReader)
63 *
64 *
65 * This class provides theses public functions :
66 *  - (static) convert
67 *  - getNextIFDOffset
68 *
69 * -----------------------------------------------------------------------------
70 */
71
72  require_once(JPEG_METADATA_DIR."Common/Data.class.php");
73  require_once(JPEG_METADATA_DIR."Common/GlobalTags.class.php");
74  require_once(JPEG_METADATA_DIR."Common/MakerNotesSignatures.class.php");
75  require_once(JPEG_METADATA_DIR."Readers/GenericReader.class.php");
76  require_once(JPEG_METADATA_DIR."Readers/IfdEntryReader.class.php");
77  require_once(JPEG_METADATA_DIR."TagDefinitions/IfdTags.class.php");
78
79
80  class IfdReader extends GenericReader
81  {
82    protected $byteOrder = BYTE_ORDER_LITTLE_ENDIAN;
83
84    private $nextIFDOffset = 0;
85
86    private $dataOffset = 0;
87
88    /**
89     * The constructor needs, like the ancestor, the datas to be parsed
90     *
91     * Some datas are offfset on extra data, and this offset can be (some time)
92     * absolute inside the IFD, or relative. So, the offset of the IFD structure
93     * is needed
94     *
95     * The byte order of data is the same byte order than the TIFF structure
96     *
97     * @param String $data
98     * @param ULong $offset : offset of IFD block in the jpeg file
99     * @param String $byteOrder
100     */
101    function __construct($data, $offset, $byteOrder)
102    {
103      parent::__construct($data);
104
105      $this->data->setByteOrder($byteOrder);
106      $this->byteOrder=$byteOrder;
107      $this->dataOffset=$offset;
108
109      $this->skipHeader($this->headerSize);
110
111      $dataPointer = $this->data->offset();
112      /*
113       * number of entries is defined byte an UShort at the begining of the
114       * data structure
115       */
116      $this->nbEntries=$this->data->readUShort();
117
118      /*
119       * if the file is written in little_endian, we assume the maker note
120       * values are stored with little_endian byte order too
121       *
122       * in some case, software modify the exif and rewrite the file with the
123       * big_endian byte order. but the maker note stays in little_endian
124       *
125       * this code try to determine the maker note byte order : if number of
126       * entries is higher than 256, we can think that the maker note byte order
127       * is inverted...
128       * (the constructor for maker note is not overrided, so this trick is
129       * placed here)
130       *
131       */
132      if($this->nbEntries>0xff)
133      {
134        $this->data->seek($dataPointer);
135        if($this->byteOrder==BYTE_ORDER_BIG_ENDIAN)
136          $this->byteOrder = BYTE_ORDER_LITTLE_ENDIAN;
137        else
138          $this->byteOrder = BYTE_ORDER_BIG_ENDIAN;
139
140        $this->data->setByteOrder($this->byteOrder);
141        $this->nbEntries=$this->data->readUShort();
142        if($this->nbEntries>0xff)
143        {
144          /*
145           * if number of entries is always higher than 0xFF after reverting the
146           * byte order, set num entries to 0
147           * (at now, unable to manage this)
148           */
149          $this->nbEntries=0;
150        }
151
152      }
153
154      $this->initializeEntries();
155
156      /* Next IFD Offset is defined after the entries  */
157      $this->nextIFDOffset=$this->data->readULong();
158    }
159
160    function __destruct()
161    {
162      parent::__destruct();
163    }
164
165    /**
166     * return the offset for the next IFD block
167     * offset of next IFD is relative to the current TIFF block, not the Jpeg
168     * file
169     *
170     * @return ULong
171     */
172    public function getNextIFDOffset()
173    {
174      return($this->nextIFDOffset);
175    }
176
177    public function toString()
178    {
179      $returned="IFD Offset: ".sprintf("%08x", $this->dataOffset).
180                " ; NbEntries: ".sprintf("%02d", $this->nbEntries).
181                " ; next IFD Offset: 0x".sprintf("%08x", $this->nextIFDOffset);
182      return($returned);
183    }
184
185    /**
186     * initialize the definition for classic exif tags (in fact Tiff 6.0 and
187     * Exif 2.2 tags)
188     */
189    protected function initializeTagDef()
190    {
191      $this->tagDef = new IfdTags();
192    }
193
194    /**
195     * reads all the entries in the IFD block, and set the Tag properties for
196     * the entry.
197     *
198     * An entry is a IfdEntryReader object
199     *
200     * Add the entry to the entries array
201     *
202     */
203    protected function initializeEntries()
204    {
205      for($i=0;$i<$this->nbEntries;$i++)
206      {
207        $entry=new IfdEntryReader($this->data->readASCII(12), $this->byteOrder, $this->data, $this->dataOffset, $this->tagDef);
208        $this->setTagProperties($entry);
209        $this->entries[]=$entry;
210      }
211    }
212
213    /**
214     * Interprets the tag values into a 'human readable values'
215     *
216     * @param IfdEntryReader $entry
217     */
218    private function setTagProperties($entry)
219    {
220      /*
221       * if the given tag id is defined, analyzing its values
222       */
223      if($this->tagDef->tagIdExists($entry->getTagId()))
224      {
225        $tag=$this->tagDef->getTagById($entry->getTagId());
226
227        $entry->getTag()->setKnown(true);
228        $entry->getTag()->setName($tag['tagName']);
229        $entry->getTag()->setImplemented($tag['implemented']);
230        $entry->getTag()->setTranslatable($tag['translatable']);
231
232        /*
233         * if there is values defined for the tag, analyze it
234         */
235        if(array_key_exists('tagValues', $tag))
236        {
237          /*
238           * if the combiTag value equals 0 exploit it as this
239           */
240          if($tag['combiTag']==0 and !is_array($entry->getValue()))
241          {
242            if(array_key_exists($entry->getValue(), $tag['tagValues']))
243            {
244              $entry->getTag()->setLabel($tag['tagValues'][$entry->getValue()]);
245            }
246            else
247            {
248              $entry->getTag()->setLabel("[unknown value 0x".sprintf("%04x", $entry->getValue())."]");
249            }
250          }
251          else
252          {
253            /*
254             * the combiTag value does not equals 0, so exploit it as a combi tag
255             */
256            $combiValue=$this->processCombiTag($entry->getValue(), ($tag['combiTag']==0)?1:$tag['combiTag'] );
257            if(array_key_exists($combiValue, $tag['tagValues']))
258            {
259              $entry->getTag()->setLabel($tag['tagValues'][$combiValue]);
260            }
261            else
262            {
263              $entry->getTag()->setLabel("[unknown combi value 0x".sprintf("%0".(2*$tag['combiTag'])."x", $combiValue)."]");
264            }
265          }
266        }
267        else
268        {
269          /*
270           * there is no values defined for the tag, analyzing it with dedicated
271           * function
272           */
273          $entry->getTag()->setLabel($this->processSpecialTag($entry->getTagId(), $entry->getValue(), $entry->getType(), $entry->getExtraDataOffset()));
274        }
275      }
276    }
277
278    /**
279     * Some values are obtained after combination of values
280     */
281    private function processCombiTag($values, $combi)
282    {
283      if(is_array($values))
284      {
285        $returned=0;
286        if($combi<=count($values))
287        {
288          for($i=0;$i<$combi;$i++)
289          {
290            $returned+=$values[$i]*pow(2,8*($combi-$i-1));
291          }
292        }
293      }
294      else
295      {
296        $returned=$values;
297      }
298      return($returned);
299    }
300
301    /**
302     * skip IFD header, if any
303     * (used by maker sub IFD classes)
304     */
305    protected function skipHeader($headerSize=0)
306    {
307      $this->data->seek($headerSize);
308    }
309
310    /**
311     * this function do the interpretation of specials tags
312     * must be overrided by derived classes
313     *
314     * the function return the interpreted value for the tag
315     *
316     * @param $tagId             : the id of the tag
317     * @param $values            : 'raw' value to be interpreted
318     * @param UByte $type        : if needed (for IFD structure) the type of data
319     * @param ULong $valueOffset : if needed, the offset of data in the jpeg file
320     * @return String or Array or DateTime or Integer or Float...
321     */
322    protected function processSpecialTag($tagId, $values, $type, $valuesOffset=0)
323    {
324      switch($tagId)
325      {
326        /*
327         * Tags managed
328         */
329        case 0x0100: // ImageWidth, tag 0x0100
330        case 0x0101: // ImageHeight, tag 0x0101
331        case 0x0102: // BitsPerSample, tag 0x0102
332        case 0x0115: // SamplesPerPixel, tag 0x0115
333        case 0x0116: // RowsPerStrip, tag 0x0116
334        case 0x0117: // StripByteCounts, tag 0x0117
335        case 0x0201: // JPEGInterchangeFormat, tag 0x0201
336        case 0x0202: // JPEGInterchangeFormatLength, tag 0x0202
337        case 0x8824: // SpectralSensitivity, tag 0x8824
338        case 0x8827: // ISOSpeedRatings, tag 0x8827
339        case 0xA002: // PixelXDimension, tag 0xA002
340        case 0xA003: // PixelYDimension, tag 0xA003
341          $returned=$values;
342          break;
343        case 0x000b: // ProcessingSoftware, tag 0x000b
344        case 0x010D: // DocumentName, tag 0x010D
345        case 0x010E: // ImageDescription, tag 0x010E
346        case 0x0131: // Software, tag 0x0131
347        case 0x013B: // Artist, tag 0x013B
348        case 0x8298: // Copyright, tag 0x8298
349        case 0x9290: // SubsecTime, tag 0x9290
350        case 0x9291: // SubsecTimeOriginal, tag 0x9291
351        case 0x9292: // SubsecTimeDigitized, tag 0x9292
352        case 0xA004: // RelatedSoundFile, tag 0xA004
353          /*
354           * null terminated strings
355           */
356          $returned=ConvertData::toStrings($values);
357          break;
358        case 0x010F: // Make, tag 0x010F
359        case 0x0110: // Model, tag 0x0110
360          /* Make and Model are null terminated strings
361           * memorize the maker & camera from the exif tag : it's used to
362           * recognize the Canon camera (no header in the maker note)
363           */
364          $returned=ConvertData::toStrings($values);
365          GlobalTags::setExifMaker($returned);
366          break;
367        case 0x011A: // XResolution, tag 0x011A
368        case 0x011B: // YResolution, tag 0x011B
369          if($values[1]>0)
370            $returned=$values[0]/$values[1];
371          else
372            $returned=$values[0];
373          break;
374        case 0x0132: // DateTime, tag 0x0132
375        case 0x9003: // DateTimeOriginal, tag 0x9003
376        case 0x9004: // DateTimeDigitized, tag 0x9004
377          /* formatted as "YYYY:MM:DD HH:MM:SS\x00"
378           * if date is defined, returns a DateTime object
379           * otherwise return "unknown date"
380           */
381          $returned=ConvertData::toDateTime($values);
382          break;
383        case 0x0212: // YCbCrSubSampling, tag 0x0212
384          /* is a rationnal number
385           * [1, 1] = YCbCr4:4:4
386           * [1, 2] = YCbCr4:4:0
387           * [1, 4] = YCbCr4:4:1
388           * [2, 1] = YCbCr4:2:2
389           * [2, 2] = YCbCr4:2:0
390           * [2, 4] = YCbCr4:2:1
391           * [4, 1] = YCbCr4:1:1
392           * [4, 2] = YCbCr4:1:0
393           */
394          $tag=$this->tagDef->getTagById(0x0212);
395          if(array_key_exists($values[0], $tags['tagValues.special']))
396          {
397            if(array_key_exists($values[1], $tags['tagValues.special'][$values[0]]))
398            {
399              $returned=$tags['tagValues.special'][$values[0]][$values[1]];
400            }
401            else
402            {
403              $returned="unknown";
404            }
405          }
406          else
407          {
408            $returned="unknown";
409          }
410          break;
411        case 0x829A: // ExposureTime, tag 0x829A
412          if($values[1]==0) $values[1]=1;
413          $returned=ConvertData::toExposureTime($values[0]/$values[1]);
414          break;
415        case 0x829D: // FNumber, tag 0x829D
416          if($values[1]==0) $values[1]=1;
417          $returned=ConvertData::toFNumber(GlobalTags::setExifAperture($values[0]/$values[1]));
418          break;
419        case 0x8769: // Exif IFD Pointer, tag 0x8769
420          /*
421           * the tag 0x8769 value is an offset to an EXIF sub IFD
422           * the returned value is a parsed sub IFD
423           */
424          $returned=new IfdReader($this->data->readASCII(-1,$values-$this->dataOffset), $values, $this->byteOrder);
425          break;
426        case 0x8825: // GPS IFD Pointer, tag 0x8825
427          /*
428           * the tag 0x8825 value is an offset to an EXIF sub IFD
429           * the returned value is a parsed sub IFD
430           */
431          require_once(JPEG_METADATA_DIR."Readers/GpsReader.class.php");
432          $returned=new GpsReader($this->data->readASCII(-1,$values-$this->dataOffset), $values, $this->byteOrder);
433          break;
434        case 0x9000: // ExifVersion, tag 0x9000
435        case 0xA000: // FlashpixVersion, tag 0xa0000
436          $returned=(int)substr($values,0,2).".".(int)substr($values,2);
437          break;
438        case 0x9101: // ComponentsConfiguration, tag0x9101
439          switch($values)
440          {
441            case "\x01\x02\x03\x00":
442              $returned="YCbCr";
443              break;
444            case "\x04\x05\x06\x00":
445              $returned="RGB";
446              break;
447            default:
448              $returned="unknown";
449              break;
450          }
451          break;
452        case 0x9102: // CompressedBitsPerPixel, tag0x9102
453        case 0x9203: // BrightnessValue, tag 0x9203 (APEX format)
454          if($values[1]==0) $values[1]=1;
455          $returned=round($values[0]/$values[1],4);
456          break;
457        case 0x9201: // ShutterSpeedValue, tag0x9201
458          if($values[1]==0) $values[1]=1;
459          /*
460           * the formula to compute the shutter speed value is 1/pow(2, x)
461           *
462           * Because of rounding errors, the result obtained using this formula
463           * is sometimes incorrect.
464           *
465           * We consider that if the result is greater than the second, the
466           * result is rounded to 2 hundredths of a second (to avoid something
467           * like 3.0000124500015s)
468           *
469           * in other case (result is less than 1 second) there is no rules
470           * result can be strange, but for now I don't know how to resolve this
471           * problem
472           *
473           */
474
475          $value=1/pow(2, $values[0]/$values[1]);
476          if($value>1)
477          {
478            $value=round($value,2);
479          }
480          $returned=ConvertData::toExposureTime($value);
481          break;
482        case 0x9202: // ApertureValue, tag0x9202
483          if($values[1]==0) $values[1]=1;
484          if(GlobalTags::getExifAperture()=="")
485          {
486            // set only if empty (if not empty, it means the value was already
487            // set with the FNumber tag)
488            GlobalTags::setExifAperture(pow(1.414213562, $values[0]/$values[1]));
489          }
490          //no break, $returned value is the same than the 0x9205 tag
491        case 0x9205: // MaxApertureValue, tag0x9205
492          if($values[1]==0) $values[1]=1;
493          $returned=ConvertData::toFNumber(pow(1.414213562, $values[0]/$values[1]));
494          break;
495        case 0x9204: // ExposureBiasValue, tag0x9204
496          if($values[1]==0) $values[1]=1;
497          $returned=ConvertData::toEV($values[0]/$values[1]);
498          break;
499        case 0x9206: // SubjectDistance, tag 0x9206
500          if($values[1]==0) $values[1]=1;
501          $returned=ConvertData::toDistance($values[0]/$values[1], "m");
502          break;
503        case 0x9209: // flash, tag 0x9209
504          $tag=$this->tagDef->getTagById(0x9209);
505          $returned=Array(
506            "computed" => (isset($tag['tagValues.computed'][$values])?$tag['tagValues.computed'][$values]:"unknown"),
507            "detail" => Array()
508          );
509
510          $value=$values & 0x0001;
511          $returned["detail"][]=$tag['tagValues.specialValues'][0x0001][$value];
512
513          $value=($values & 0x0006)>>1;
514          $returned["detail"][]=$tag['tagValues.specialValues'][0x0006][$value];
515
516          $value=($values & 0x0018)>>3;
517          $returned["detail"][]=$tag['tagValues.specialValues'][0x0018][$value];
518
519          $value=($values & 0x0020)>>5;
520          $returned["detail"][]=$tag['tagValues.specialValues'][0x0020][$value];
521
522          $value=($values & 0x0040)>>6;
523          $returned["detail"][]=$tag['tagValues.specialValues'][0x0040][$value];
524
525          break;
526        case 0x920A: // FocalLength, tag 0x920A
527          if($values[1]==0) $values[1]=1;
528          $returned=ConvertData::toFocalLength(GlobalTags::setExifFocal($values[0]/$values[1]));
529          break;
530        case 0x927c: // MakerNote, tag 0x927c
531          /* try to return a specific maker sub ifd
532           *
533           * if $values is n
534           */
535          $makerSignature=MakerNotesSignatures::getMaker($values);
536          switch($makerSignature)
537          {
538            case MakerNotesSignatures::OlympusHeader:
539            case MakerNotesSignatures::Olympus2Header:
540              $returned="Olympus is not implemented yet";
541              break;
542            case MakerNotesSignatures::FujiFilmHeader:
543              $returned="FujiFilm is not implemented yet";
544              break;
545            case MakerNotesSignatures::Nikon2Header:
546            case MakerNotesSignatures::Nikon3Header:
547              require_once(JPEG_METADATA_DIR."Readers/NikonReader.class.php");
548              $returned=new NikonReader($values, $valuesOffset, $this->byteOrder, $makerSignature);
549              break;
550            case MakerNotesSignatures::PanasonicHeader:
551              $returned="Panasonic is not implemented yet";
552              break;
553            case MakerNotesSignatures::PentaxHeader:
554            case MakerNotesSignatures::Pentax2Header:
555              require_once(JPEG_METADATA_DIR."Readers/PentaxReader.class.php");
556              $returned=new PentaxReader($values, $valuesOffset, $this->byteOrder, $makerSignature);
557              break;
558            case MakerNotesSignatures::SigmaHeader:
559            case MakerNotesSignatures::Sigma2Header:
560              $returned="Sigma is not implemented yet";
561              break;
562            case MakerNotesSignatures::SonyHeader:
563              $returned="Sony is not implemented yet";
564              break;
565            default:
566              /*
567               * Canon maker notes don't have any header
568               * So, the only method to know if the maker note is from a Canon
569               * camera is looking the exif maker value equals "Canon" or
570               * the camera model contains "Canon"
571               */
572              if(preg_match("/.*canon.*/i",GlobalTags::getExifMaker()))
573              {
574                require_once(JPEG_METADATA_DIR."Readers/CanonReader.class.php");
575                $returned=new CanonReader($values, $valuesOffset, $this->byteOrder, "");
576              }
577              else
578              {
579                $returned="unknown maker => ".ConvertData::toHexDump($values, $type, 16);
580              }
581              break;
582          }
583          break;
584        case 0x9286: // UserComment, tag 0x9286
585          /*
586           * user comment format :
587           *  first 8 bytes : format type
588           *  other bytes : text
589           */
590          $returned=substr($values,8);
591          break;
592        case 0xA20B: // FlashEnergy, tag 0xA20B
593        case 0xA20E: // FocalPlaneXResolution, tag 0xA20E
594        case 0xA20F: // FocalPlaneYResolution, tag 0xA20F
595        case 0xA215: // ExposureIndex, tag 0xA20F
596        case 0xA404: // DigitalZoomRatio, tag 0xA404
597          if($values[1]==0) $values[1]=1;
598          $returned=round($values[0]/$values[1],2);
599          break;
600        case 0xA405: // FocalLengthIn35mmFilm, tag 0xA405
601          $returned=ConvertData::toFocalLength($values);
602          break;
603        /*
604         * Tags not managed
605         */
606        case 0x0111: // StripOffsets, tag 0x0111
607        case 0x012D: // TransferFunction, tag 0x012D
608        case 0x013E: // WhitePoint, tag 0x013E
609        case 0x013F: // PrimaryChromaticities, tag 0x013F
610        case 0x0211: // YCbCrCoefficients, tag 0x0211
611        case 0x0214: // ReferenceBlackWhite, tag 0x0214
612        case 0x8828: // ReferenceBlackWhite, tag 0x0214
613        case 0x9214: // SubjectArea, tag 0x9214
614        case 0xA20C: // SpatialFrequencyResponse, tag 0xA20C
615        case 0xA40B: // DeviceSettingDescription, tag 0xA40B
616        default:
617          $returned="Not yet implemented;".ConvertData::toHexDump($tagId, ByteType::USHORT)." => ".ConvertData::toHexDump($values, $type, 64);
618          break;
619      }
620      return($returned);
621    }
622  }
623
624
625?>
Note: See TracBrowser for help on using the repository browser.