Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / vendor / lsolesen / pel / src / PelIfd.php
1 <?php
2
3 /**
4  * PEL: PHP Exif Library.
5  * A library with support for reading and
6  * writing all Exif headers in JPEG and TIFF images using PHP.
7  *
8  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Martin Geisler.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program in the file COPYING; if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23  * Boston, MA 02110-1301 USA
24  */
25 namespace lsolesen\pel;
26
27 /**
28  * Classes for dealing with Exif IFDs.
29  *
30  * @author Martin Geisler <mgeisler@users.sourceforge.net>
31  * @license http://www.gnu.org/licenses/gpl.html GNU General Public
32  *          License (GPL)
33  * @package PEL
34  */
35
36 /**
37  * Class representing an Image File Directory (IFD).
38  *
39  * {@link PelTiff TIFF data} is structured as a number of Image File
40  * Directories, IFDs for short. Each IFD contains a number of {@link
41  * PelEntry entries}, some data and finally a link to the next IFD.
42  *
43  * @author Martin Geisler <mgeisler@users.sourceforge.net>
44  * @package PEL
45  */
46 class PelIfd implements \IteratorAggregate, \ArrayAccess
47 {
48
49     /**
50      * Main image IFD.
51      *
52      * Pass this to the constructor when creating an IFD which will be
53      * the IFD of the main image.
54      */
55     const IFD0 = 0;
56
57     /**
58      * Thumbnail image IFD.
59      *
60      * Pass this to the constructor when creating an IFD which will be
61      * the IFD of the thumbnail image.
62      */
63     const IFD1 = 1;
64
65     /**
66      * Exif IFD.
67      *
68      * Pass this to the constructor when creating an IFD which will be
69      * the Exif sub-IFD.
70      */
71     const EXIF = 2;
72
73     /**
74      * GPS IFD.
75      *
76      * Pass this to the constructor when creating an IFD which will be
77      * the GPS sub-IFD.
78      */
79     const GPS = 3;
80
81     /**
82      * Interoperability IFD.
83      *
84      * Pass this to the constructor when creating an IFD which will be
85      * the interoperability sub-IFD.
86      */
87     const INTEROPERABILITY = 4;
88
89     /**
90      * The entries held by this directory.
91      *
92      * Each tag in the directory is represented by a {@link PelEntry}
93      * object in this array.
94      *
95      * @var array
96      */
97     private $entries = array();
98
99     /**
100      * The type of this directory.
101      *
102      * Initialized in the constructor. Must be one of {@link IFD0},
103      * {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link
104      * INTEROPERABILITY}.
105      *
106      * @var int
107      */
108     private $type;
109
110     /**
111      * The next directory.
112      *
113      * This will be initialized in the constructor, or be left as null
114      * if this is the last directory.
115      *
116      * @var PelIfd
117      */
118     private $next = null;
119
120     /**
121      * Sub-directories pointed to by this directory.
122      *
123      * This will be an array of ({@link PelTag}, {@link PelIfd}) pairs.
124      *
125      * @var array
126      */
127     private $sub = array();
128
129     /**
130      * The thumbnail data.
131      *
132      * This will be initialized in the constructor, or be left as null
133      * if there are no thumbnail as part of this directory.
134      *
135      * @var PelDataWindow
136      */
137     private $thumb_data = null;
138     // TODO: use this format to choose between the
139     // JPEG_INTERCHANGE_FORMAT and STRIP_OFFSETS tags.
140     // private $thumb_format;
141
142     /**
143      * Construct a new Image File Directory (IFD).
144      *
145      * The IFD will be empty, use the {@link addEntry()} method to add
146      * an {@link PelEntry}. Use the {@link setNext()} method to link
147      * this IFD to another.
148      *
149      * @param
150      *            int type the type of this IFD. Must be one of {@link
151      *            IFD0}, {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link
152      *            INTEROPERABILITY}. An {@link PelIfdException} will be thrown
153      *            otherwise.
154      */
155     public function __construct($type)
156     {
157         if ($type != PelIfd::IFD0 && $type != PelIfd::IFD1 && $type != PelIfd::EXIF && $type != PelIfd::GPS &&
158              $type != PelIfd::INTEROPERABILITY) {
159             throw new PelIfdException('Unknown IFD type: %d', $type);
160         }
161
162         $this->type = $type;
163     }
164
165     /**
166      * Load data into a Image File Directory (IFD).
167      *
168      * @param PelDataWindow $d
169      *            the data window that will provide the data.
170      *
171      * @param int $offset
172      *            the offset within the window where the directory will
173      *            be found.
174      */
175     public function load(PelDataWindow $d, $offset)
176     {
177         $thumb_offset = 0;
178         $thumb_length = 0;
179
180         Pel::debug('Constructing IFD at offset %d from %d bytes...', $offset, $d->getSize());
181
182         /* Read the number of entries */
183         $n = $d->getShort($offset);
184         Pel::debug('Loading %d entries...', $n);
185
186         $offset += 2;
187
188         /* Check if we have enough data. */
189         if ($offset + 12 * $n > $d->getSize()) {
190             $n = floor(($offset - $d->getSize()) / 12);
191             Pel::maybeThrow(new PelIfdException('Adjusted to: %d.', $n));
192         }
193
194         for ($i = 0; $i < $n; $i ++) {
195             // TODO: increment window start instead of using offsets.
196             $tag = $d->getShort($offset + 12 * $i);
197             Pel::debug(
198                 'Loading entry with tag 0x%04X: %s (%d of %d)...',
199                 $tag,
200                 PelTag::getName($this->type, $tag),
201                 $i + 1,
202                 $n);
203
204             switch ($tag) {
205                 case PelTag::EXIF_IFD_POINTER:
206                 case PelTag::GPS_INFO_IFD_POINTER:
207                 case PelTag::INTEROPERABILITY_IFD_POINTER:
208                     $o = $d->getLong($offset + 12 * $i + 8);
209                     Pel::debug('Found sub IFD at offset %d', $o);
210
211                     /* Map tag to IFD type. */
212                     if ($tag == PelTag::EXIF_IFD_POINTER) {
213                         $type = PelIfd::EXIF;
214                     } elseif ($tag == PelTag::GPS_INFO_IFD_POINTER) {
215                         $type = PelIfd::GPS;
216                     } elseif ($tag == PelTag::INTEROPERABILITY_IFD_POINTER) {
217                         $type = PelIfd::INTEROPERABILITY;
218                     }
219
220                     $this->sub[$type] = new PelIfd($type);
221                     $this->sub[$type]->load($d, $o);
222                     break;
223                 case PelTag::JPEG_INTERCHANGE_FORMAT:
224                     $thumb_offset = $d->getLong($offset + 12 * $i + 8);
225                     $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
226                     break;
227                 case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH:
228                     $thumb_length = $d->getLong($offset + 12 * $i + 8);
229                     $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
230                     break;
231                 default:
232                     $format = $d->getShort($offset + 12 * $i + 2);
233                     $components = $d->getLong($offset + 12 * $i + 4);
234
235                     /*
236                      * The data size. If bigger than 4 bytes, the actual data is
237                      * not in the entry but somewhere else, with the offset stored
238                      * in the entry.
239                      */
240                     $s = PelFormat::getSize($format) * $components;
241                     if ($s > 0) {
242                         $doff = $offset + 12 * $i + 8;
243                         if ($s > 4) {
244                             $doff = $d->getLong($doff);
245                         }
246                         $data = $d->getClone($doff, $s);
247                     } else {
248                         $data = new PelDataWindow();
249                     }
250
251                     try {
252                         $entry = $this->newEntryFromData($tag, $format, $components, $data);
253                         $this->addEntry($entry);
254                     } catch (PelException $e) {
255                         /*
256                          * Throw the exception when running in strict mode, store
257                          * otherwise.
258                          */
259                         Pel::maybeThrow($e);
260                     }
261
262                     /* The format of the thumbnail is stored in this tag. */
263                     // TODO: handle TIFF thumbnail.
264                     // if ($tag == PelTag::COMPRESSION) {
265                     // $this->thumb_format = $data->getShort();
266                     // }
267                     break;
268             }
269         }
270
271         /* Offset to next IFD */
272         $o = $d->getLong($offset + 12 * $n);
273         Pel::debug('Current offset is %d, link at %d points to %d.', $offset, $offset + 12 * $n, $o);
274
275         if ($o > 0) {
276             /* Sanity check: we need 6 bytes */
277             if ($o > $d->getSize() - 6) {
278                 Pel::maybeThrow(new PelIfdException('Bogus offset to next IFD: ' . '%d > %d!', $o, $d->getSize() - 6));
279             } else {
280                 if ($this->type == PelIfd::IFD1) {
281                     // IFD1 shouldn't link further...
282                     Pel::maybeThrow(new PelIfdException('IFD1 links to another IFD!'));
283                 }
284                 $this->next = new PelIfd(PelIfd::IFD1);
285                 $this->next->load($d, $o);
286             }
287         } else {
288             Pel::debug('Last IFD.');
289         }
290     }
291
292     /**
293      * Make a new entry from a bunch of bytes.
294      *
295      * This method will create the proper subclass of {@link PelEntry}
296      * corresponding to the {@link PelTag} and {@link PelFormat} given.
297      * The entry will be initialized with the data given.
298      *
299      * Please note that the data you pass to this method should come
300      * from an image, that is, it should be raw bytes. If instead you
301      * want to create an entry for holding, say, an short integer, then
302      * create a {@link PelEntryShort} object directly and load the data
303      * into it.
304      *
305      * A {@link PelUnexpectedFormatException} is thrown if a mismatch is
306      * discovered between the tag and format, and likewise a {@link
307      * PelWrongComponentCountException} is thrown if the number of
308      * components does not match the requirements of the tag. The
309      * requirements for a given tag (if any) can be found in the
310      * documentation for {@link PelTag}.
311      *
312      * @param integer $tag
313      *            the tag of the entry as defined in {@link PelTag}.
314      *
315      * @param integer $format
316      *            the format of the entry as defined in {@link PelFormat}.
317      *
318      * @param int $components
319      *            the components in the entry.
320      *
321      * @param PelDataWindow $data
322      *            the data which will be used to construct the
323      *            entry.
324      *
325      * @return PelEntry a newly created entry, holding the data given.
326      */
327     public function newEntryFromData($tag, $format, $components, PelDataWindow $data)
328     {
329
330         /*
331          * First handle tags for which we have a specific PelEntryXXX
332          * class.
333          */
334         switch ($this->type) {
335             case self::IFD0:
336             case self::IFD1:
337             case self::EXIF:
338             case self::INTEROPERABILITY:
339                 switch ($tag) {
340                     case PelTag::DATE_TIME:
341                     case PelTag::DATE_TIME_ORIGINAL:
342                     case PelTag::DATE_TIME_DIGITIZED:
343                         if ($format != PelFormat::ASCII) {
344                             throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::ASCII);
345                         }
346                         if ($components != 20) {
347                             throw new PelWrongComponentCountException($this->type, $tag, $components, 20);
348                         }
349                         // TODO: handle timezones.
350                         return new PelEntryTime($tag, $data->getBytes(0, - 1), PelEntryTime::EXIF_STRING);
351
352                     case PelTag::COPYRIGHT:
353                         if ($format != PelFormat::ASCII) {
354                             throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::ASCII);
355                         }
356                         $v = explode("\0", trim($data->getBytes(), ' '));
357                         if (! isset($v[1])) {
358                             Pel::maybeThrow(new PelException('Invalid copyright: %s', $data->getBytes()));
359                             // when not in strict mode, set empty copyright and continue
360                             $v[1] = '';
361                         }
362                         return new PelEntryCopyright($v[0], $v[1]);
363
364                     case PelTag::EXIF_VERSION:
365                     case PelTag::FLASH_PIX_VERSION:
366                     case PelTag::INTEROPERABILITY_VERSION:
367                         if ($format != PelFormat::UNDEFINED) {
368                             throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::UNDEFINED);
369                         }
370                         return new PelEntryVersion($tag, $data->getBytes() / 100);
371
372                     case PelTag::USER_COMMENT:
373                         if ($format != PelFormat::UNDEFINED) {
374                             throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::UNDEFINED);
375                         }
376                         if ($data->getSize() < 8) {
377                             return new PelEntryUserComment();
378                         } else {
379                             return new PelEntryUserComment($data->getBytes(8), rtrim($data->getBytes(0, 8)));
380                         }
381                     // this point can not be reached
382                     case PelTag::XP_TITLE:
383                     case PelTag::XP_COMMENT:
384                     case PelTag::XP_AUTHOR:
385                     case PelTag::XP_KEYWORDS:
386                     case PelTag::XP_SUBJECT:
387                         if ($format != PelFormat::BYTE) {
388                             throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::BYTE);
389                         }
390                         $v = '';
391                         for ($i = 0; $i < $components; $i ++) {
392                             $b = $data->getByte($i);
393                             /*
394                              * Convert the byte to a character if it is non-null ---
395                              * information about the character encoding of these entries
396                              * would be very nice to have! So far my tests have shown
397                              * that characters in the Latin-1 character set are stored in
398                              * a single byte followed by a NULL byte.
399                              */
400                             if ($b != 0) {
401                                 $v .= chr($b);
402                             }
403                         }
404
405                         return new PelEntryWindowsString($tag, $v);
406                 }
407             // This point can be reached! Continue with default.
408             case self::GPS:
409             default:
410                 /* Then handle the basic formats. */
411                 switch ($format) {
412                     case PelFormat::BYTE:
413                         $v = new PelEntryByte($tag);
414                         for ($i = 0; $i < $components; $i ++) {
415                             $v->addNumber($data->getByte($i));
416                         }
417                         return $v;
418
419                     case PelFormat::SBYTE:
420                         $v = new PelEntrySByte($tag);
421                         for ($i = 0; $i < $components; $i ++) {
422                             $v->addNumber($data->getSByte($i));
423                         }
424                         return $v;
425
426                     case PelFormat::ASCII:
427                         return new PelEntryAscii($tag, rtrim($data->getBytes(0), "\0"));
428
429                     case PelFormat::SHORT:
430                         $v = new PelEntryShort($tag);
431                         for ($i = 0; $i < $components; $i ++) {
432                             $v->addNumber($data->getShort($i * 2));
433                         }
434                         return $v;
435
436                     case PelFormat::SSHORT:
437                         $v = new PelEntrySShort($tag);
438                         for ($i = 0; $i < $components; $i ++) {
439                             $v->addNumber($data->getSShort($i * 2));
440                         }
441                         return $v;
442
443                     case PelFormat::LONG:
444                         $v = new PelEntryLong($tag);
445                         for ($i = 0; $i < $components; $i ++) {
446                             $v->addNumber($data->getLong($i * 4));
447                         }
448                         return $v;
449
450                     case PelFormat::SLONG:
451                         $v = new PelEntrySLong($tag);
452                         for ($i = 0; $i < $components; $i ++) {
453                             $v->addNumber($data->getSLong($i * 4));
454                         }
455                         return $v;
456
457                     case PelFormat::RATIONAL:
458                         $v = new PelEntryRational($tag);
459                         for ($i = 0; $i < $components; $i ++) {
460                             $v->addNumber($data->getRational($i * 8));
461                         }
462                         return $v;
463
464                     case PelFormat::SRATIONAL:
465                         $v = new PelEntrySRational($tag);
466                         for ($i = 0; $i < $components; $i ++) {
467                             $v->addNumber($data->getSRational($i * 8));
468                         }
469                         return $v;
470
471                     case PelFormat::UNDEFINED:
472                         return new PelEntryUndefined($tag, $data->getBytes());
473
474                     default:
475                         throw new PelException('Unsupported format: %s', PelFormat::getName($format));
476                 }
477         }
478     }
479
480     /**
481      * Extract thumbnail data safely.
482      *
483      * It is safe to call this method repeatedly with either the offset
484      * or the length set to zero, since it requires both of these
485      * arguments to be positive before the thumbnail is extracted.
486      *
487      * When both parameters are set it will check the length against the
488      * available data and adjust as necessary. Only then is the
489      * thumbnail data loaded.
490      *
491      * @param PelDataWindow $d
492      *            the data from which the thumbnail will be
493      *            extracted.
494      *
495      * @param int $offset
496      *            the offset into the data.
497      *
498      * @param int $length
499      *            the length of the thumbnail.
500      */
501     private function safeSetThumbnail(PelDataWindow $d, $offset, $length)
502     {
503         /*
504          * Load the thumbnail if both the offset and the length is
505          * available.
506          */
507         if ($offset > 0 && $length > 0) {
508             /*
509              * Some images have a broken length, so we try to carefully
510              * check the length before we store the thumbnail.
511              */
512             if ($offset + $length > $d->getSize()) {
513                 Pel::maybeThrow(
514                     new PelIfdException(
515                         'Thumbnail length %d bytes ' . 'adjusted to %d bytes.',
516                         $length,
517                         $d->getSize() - $offset));
518                 $length = $d->getSize() - $offset;
519             }
520
521             /* Now set the thumbnail normally. */
522             $this->setThumbnail($d->getClone($offset, $length));
523         }
524     }
525
526     /**
527      * Set thumbnail data.
528      *
529      * Use this to embed an arbitrary JPEG image within this IFD. The
530      * data will be checked to ensure that it has a proper {@link
531      * PelJpegMarker::EOI} at the end. If not, then the length is
532      * adjusted until one if found. An {@link PelIfdException} might be
533      * thrown (depending on {@link Pel::$strict}) this case.
534      *
535      * @param PelDataWindow $d
536      *            the thumbnail data.
537      */
538     public function setThumbnail(PelDataWindow $d)
539     {
540         $size = $d->getSize();
541         /* Now move backwards until we find the EOI JPEG marker. */
542         while ($d->getByte($size - 2) != 0xFF || $d->getByte($size - 1) != PelJpegMarker::EOI) {
543             $size --;
544         }
545
546         if ($size != $d->getSize()) {
547             Pel::maybeThrow(new PelIfdException('Decrementing thumbnail size ' . 'to %d bytes', $size));
548         }
549         $this->thumb_data = $d->getClone(0, $size);
550     }
551
552     /**
553      * Get the type of this directory.
554      *
555      * @return int of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, {@link
556      *         PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
557      *         PelIfd::INTEROPERABILITY}.
558      */
559     public function getType()
560     {
561         return $this->type;
562     }
563
564     /**
565      * Is a given tag valid for this IFD?
566      *
567      * Different types of IFDs can contain different kinds of tags ---
568      * the {@link IFD0} type, for example, cannot contain a {@link
569      * PelTag::GPS_LONGITUDE} tag.
570      *
571      * A special exception is tags with values above 0xF000. They are
572      * treated as private tags and will be allowed everywhere (use this
573      * for testing or for implementing your own types of tags).
574      *
575      * @param PelTag $tag
576      *            the tag.
577      *
578      * @return boolean true if the tag is considered valid in this IFD,
579      *         false otherwise.
580      *
581      * @see getValidTags()
582      */
583     public function isValidTag($tag)
584     {
585         return $tag > 0xF000 || in_array($tag, $this->getValidTags());
586     }
587
588     /**
589      * Returns a list of valid tags for this IFD.
590      *
591      * @return array an array of {@link PelTag}s which are valid for
592      *         this IFD.
593      */
594     public function getValidTags()
595     {
596         switch ($this->type) {
597             case PelIfd::IFD0:
598             case PelIfd::IFD1:
599                 return array(
600                     PelTag::IMAGE_WIDTH,
601                     PelTag::IMAGE_LENGTH,
602                     PelTag::BITS_PER_SAMPLE,
603                     PelTag::COMPRESSION,
604                     PelTag::PHOTOMETRIC_INTERPRETATION,
605                     PelTag::DOCUMENT_NAME,
606                     PelTag::IMAGE_DESCRIPTION,
607                     PelTag::MAKE,
608                     PelTag::MODEL,
609                     PelTag::STRIP_OFFSETS,
610                     PelTag::ORIENTATION,
611                     PelTag::SAMPLES_PER_PIXEL,
612                     PelTag::ROWS_PER_STRIP,
613                     PelTag::STRIP_BYTE_COUNTS,
614                     PelTag::X_RESOLUTION,
615                     PelTag::Y_RESOLUTION,
616                     PelTag::PLANAR_CONFIGURATION,
617                     PelTag::RESOLUTION_UNIT,
618                     PelTag::TRANSFER_FUNCTION,
619                     PelTag::SOFTWARE,
620                     PelTag::DATE_TIME,
621                     PelTag::ARTIST,
622                     PelTag::WHITE_POINT,
623                     PelTag::PRIMARY_CHROMATICITIES,
624                     PelTag::JPEG_INTERCHANGE_FORMAT,
625                     PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH,
626                     PelTag::YCBCR_COEFFICIENTS,
627                     PelTag::YCBCR_SUB_SAMPLING,
628                     PelTag::YCBCR_POSITIONING,
629                     PelTag::REFERENCE_BLACK_WHITE,
630                     PelTag::COPYRIGHT,
631                     PelTag::EXIF_IFD_POINTER,
632                     PelTag::GPS_INFO_IFD_POINTER,
633                     PelTag::PRINT_IM,
634                     PelTag::XP_TITLE,
635                     PelTag::XP_COMMENT,
636                     PelTag::XP_AUTHOR,
637                     PelTag::XP_KEYWORDS,
638                     PelTag::XP_SUBJECT,
639                     PelTag::RATING
640                 );
641
642             case PelIfd::EXIF:
643                 return array(
644                     PelTag::EXPOSURE_TIME,
645                     PelTag::FNUMBER,
646                     PelTag::EXPOSURE_PROGRAM,
647                     PelTag::SPECTRAL_SENSITIVITY,
648                     PelTag::ISO_SPEED_RATINGS,
649                     PelTag::OECF,
650                     PelTag::EXIF_VERSION,
651                     PelTag::DATE_TIME_ORIGINAL,
652                     PelTag::DATE_TIME_DIGITIZED,
653                     PelTag::COMPONENTS_CONFIGURATION,
654                     PelTag::COMPRESSED_BITS_PER_PIXEL,
655                     PelTag::SHUTTER_SPEED_VALUE,
656                     PelTag::APERTURE_VALUE,
657                     PelTag::BRIGHTNESS_VALUE,
658                     PelTag::EXPOSURE_BIAS_VALUE,
659                     PelTag::MAX_APERTURE_VALUE,
660                     PelTag::SUBJECT_DISTANCE,
661                     PelTag::METERING_MODE,
662                     PelTag::LIGHT_SOURCE,
663                     PelTag::FLASH,
664                     PelTag::FOCAL_LENGTH,
665                     PelTag::MAKER_NOTE,
666                     PelTag::USER_COMMENT,
667                     PelTag::SUB_SEC_TIME,
668                     PelTag::SUB_SEC_TIME_ORIGINAL,
669                     PelTag::SUB_SEC_TIME_DIGITIZED,
670                     PelTag::FLASH_PIX_VERSION,
671                     PelTag::COLOR_SPACE,
672                     PelTag::PIXEL_X_DIMENSION,
673                     PelTag::PIXEL_Y_DIMENSION,
674                     PelTag::RELATED_SOUND_FILE,
675                     PelTag::FLASH_ENERGY,
676                     PelTag::SPATIAL_FREQUENCY_RESPONSE,
677                     PelTag::FOCAL_PLANE_X_RESOLUTION,
678                     PelTag::FOCAL_PLANE_Y_RESOLUTION,
679                     PelTag::FOCAL_PLANE_RESOLUTION_UNIT,
680                     PelTag::SUBJECT_LOCATION,
681                     PelTag::EXPOSURE_INDEX,
682                     PelTag::SENSING_METHOD,
683                     PelTag::FILE_SOURCE,
684                     PelTag::SCENE_TYPE,
685                     PelTag::CFA_PATTERN,
686                     PelTag::CUSTOM_RENDERED,
687                     PelTag::EXPOSURE_MODE,
688                     PelTag::WHITE_BALANCE,
689                     PelTag::DIGITAL_ZOOM_RATIO,
690                     PelTag::FOCAL_LENGTH_IN_35MM_FILM,
691                     PelTag::SCENE_CAPTURE_TYPE,
692                     PelTag::GAIN_CONTROL,
693                     PelTag::CONTRAST,
694                     PelTag::SATURATION,
695                     PelTag::SHARPNESS,
696                     PelTag::DEVICE_SETTING_DESCRIPTION,
697                     PelTag::SUBJECT_DISTANCE_RANGE,
698                     PelTag::IMAGE_UNIQUE_ID,
699                     PelTag::INTEROPERABILITY_IFD_POINTER,
700                     PelTag::GAMMA
701                 );
702
703             case PelIfd::GPS:
704                 return array(
705                     PelTag::GPS_VERSION_ID,
706                     PelTag::GPS_LATITUDE_REF,
707                     PelTag::GPS_LATITUDE,
708                     PelTag::GPS_LONGITUDE_REF,
709                     PelTag::GPS_LONGITUDE,
710                     PelTag::GPS_ALTITUDE_REF,
711                     PelTag::GPS_ALTITUDE,
712                     PelTag::GPS_TIME_STAMP,
713                     PelTag::GPS_SATELLITES,
714                     PelTag::GPS_STATUS,
715                     PelTag::GPS_MEASURE_MODE,
716                     PelTag::GPS_DOP,
717                     PelTag::GPS_SPEED_REF,
718                     PelTag::GPS_SPEED,
719                     PelTag::GPS_TRACK_REF,
720                     PelTag::GPS_TRACK,
721                     PelTag::GPS_IMG_DIRECTION_REF,
722                     PelTag::GPS_IMG_DIRECTION,
723                     PelTag::GPS_MAP_DATUM,
724                     PelTag::GPS_DEST_LATITUDE_REF,
725                     PelTag::GPS_DEST_LATITUDE,
726                     PelTag::GPS_DEST_LONGITUDE_REF,
727                     PelTag::GPS_DEST_LONGITUDE,
728                     PelTag::GPS_DEST_BEARING_REF,
729                     PelTag::GPS_DEST_BEARING,
730                     PelTag::GPS_DEST_DISTANCE_REF,
731                     PelTag::GPS_DEST_DISTANCE,
732                     PelTag::GPS_PROCESSING_METHOD,
733                     PelTag::GPS_AREA_INFORMATION,
734                     PelTag::GPS_DATE_STAMP,
735                     PelTag::GPS_DIFFERENTIAL
736                 );
737
738             case PelIfd::INTEROPERABILITY:
739                 return array(
740                     PelTag::INTEROPERABILITY_INDEX,
741                     PelTag::INTEROPERABILITY_VERSION,
742                     PelTag::RELATED_IMAGE_FILE_FORMAT,
743                     PelTag::RELATED_IMAGE_WIDTH,
744                     PelTag::RELATED_IMAGE_LENGTH
745                 );
746
747             /*
748              * TODO: Where do these tags belong?
749              * PelTag::FILL_ORDER,
750              * PelTag::TRANSFER_RANGE,
751              * PelTag::JPEG_PROC,
752              * PelTag::BATTERY_LEVEL,
753              * PelTag::IPTC_NAA,
754              * PelTag::INTER_COLOR_PROFILE,
755              * PelTag::CFA_REPEAT_PATTERN_DIM,
756              */
757         }
758     }
759
760     /**
761      * Get the name of an IFD type.
762      *
763      * @param int $type
764      *            one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1},
765      *            {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
766      *            PelIfd::INTEROPERABILITY}.
767      *
768      * @return string the name of type.
769      */
770     public static function getTypeName($type)
771     {
772         switch ($type) {
773             case self::IFD0:
774                 return '0';
775             case self::IFD1:
776                 return '1';
777             case self::EXIF:
778                 return 'Exif';
779             case self::GPS:
780                 return 'GPS';
781             case self::INTEROPERABILITY:
782                 return 'Interoperability';
783             default:
784                 throw new PelIfdException('Unknown IFD type: %d', $type);
785         }
786     }
787
788     /**
789      * Get the name of this directory.
790      *
791      * @return string the name of this directory.
792      */
793     public function getName()
794     {
795         return $this->getTypeName($this->type);
796     }
797
798     /**
799      * Adds an entry to the directory.
800      *
801      * @param PelEntry $e
802      *            the entry that will be added. If the entry is not
803      *            valid in this IFD (as per {@link isValidTag()}) an
804      *            {@link PelInvalidDataException} is thrown.
805      *
806      * @todo The entry will be identified with its tag, so each
807      *       directory can only contain one entry with each tag. Is this a
808      *       bug?
809      */
810     public function addEntry(PelEntry $e)
811     {
812         if ($this->isValidTag($e->getTag())) {
813             $e->setIfdType($this->type);
814             $this->entries[$e->getTag()] = $e;
815         } else {
816             throw new PelInvalidDataException("IFD %s cannot hold\n%s", $this->getName(), $e->__toString());
817         }
818     }
819
820     /**
821      * Does a given tag exist in this IFD?
822      *
823      * This methods is part of the ArrayAccess SPL interface for
824      * overriding array access of objects, it allows you to check for
825      * existance of an entry in the IFD:
826      *
827      * <code>
828      * if (isset($ifd[PelTag::FNUMBER]))
829      * // ... do something with the F-number.
830      * </code>
831      *
832      * @param PelTag $tag
833      *            the offset to check.
834      *
835      * @return boolean whether the tag exists.
836      */
837     public function offsetExists($tag)
838     {
839         return isset($this->entries[$tag]);
840     }
841
842     /**
843      * Retrieve a given tag from this IFD.
844      *
845      * This methods is part of the ArrayAccess SPL interface for
846      * overriding array access of objects, it allows you to read entries
847      * from the IFD the same was as for an array:
848      *
849      * <code>
850      * $entry = $ifd[PelTag::FNUMBER];
851      * </code>
852      *
853      * @param PelTag $tag
854      *            the tag to return. It is an error to ask for a tag
855      *            which is not in the IFD, just like asking for a non-existant
856      *            array entry.
857      *
858      * @return PelEntry the entry.
859      */
860     public function offsetGet($tag)
861     {
862         return $this->entries[$tag];
863     }
864
865     /**
866      * Set or update a given tag in this IFD.
867      *
868      * This methods is part of the ArrayAccess SPL interface for
869      * overriding array access of objects, it allows you to add new
870      * entries or replace esisting entries by doing:
871      *
872      * <code>
873      * $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry;
874      * </code>
875      *
876      * Note that the actual array index passed is ignored! Instead the
877      * {@link PelTag} from the entry is used.
878      *
879      * @param PelTag $tag
880      *            the offset to update.
881      *
882      * @param PelEntry $e
883      *            the new value.
884      */
885     public function offsetSet($tag, $e)
886     {
887         if ($e instanceof PelEntry) {
888             $tag = $e->getTag();
889             $this->entries[$tag] = $e;
890         } else {
891             throw new PelInvalidArgumentException('Argument "%s" must be a PelEntry.', $e);
892         }
893     }
894
895     /**
896      * Unset a given tag in this IFD.
897      *
898      * This methods is part of the ArrayAccess SPL interface for
899      * overriding array access of objects, it allows you to delete
900      * entries in the IFD by doing:
901      *
902      * <code>
903      * unset($ifd[PelTag::EXPOSURE_BIAS_VALUE])
904      * </code>
905      *
906      * @param PelTag $tag
907      *            the offset to delete.
908      */
909     public function offsetUnset($tag)
910     {
911         unset($this->entries[$tag]);
912     }
913
914     /**
915      * Retrieve an entry.
916      *
917      * @param PelTag $tag
918      *            the tag identifying the entry.
919      *
920      * @return PelEntry the entry associated with the tag, or null if no
921      *         such entry exists.
922      */
923     public function getEntry($tag)
924     {
925         if (isset($this->entries[$tag])) {
926             return $this->entries[$tag];
927         } else {
928             return null;
929         }
930     }
931
932     /**
933      * Returns all entries contained in this IFD.
934      *
935      * @return array an array of {@link PelEntry} objects, or rather
936      *         descendant classes. The array has {@link PelTag}s as keys
937      *         and the entries as values.
938      *
939      * @see getEntry
940      * @see getIterator
941      */
942     public function getEntries()
943     {
944         return $this->entries;
945     }
946
947     /**
948      * Return an iterator for all entries contained in this IFD.
949      *
950      * Used with foreach as in
951      *
952      * <code>
953      * foreach ($ifd as $tag => $entry) {
954      * // $tag is now a PelTag and $entry is a PelEntry object.
955      * }
956      * </code>
957      *
958      * @return Iterator an iterator using the {@link PelTag tags} as
959      *         keys and the entries as values.
960      */
961     public function getIterator()
962     {
963         return new \ArrayIterator($this->entries);
964     }
965
966     /**
967      * Returns available thumbnail data.
968      *
969      * @return string the bytes in the thumbnail, if any. If the IFD
970      *         does not contain any thumbnail data, the empty string is
971      *         returned.
972      *
973      * @todo Throw an exception instead when no data is available?
974      *
975      * @todo Return the $this->thumb_data object instead of the bytes?
976      */
977     public function getThumbnailData()
978     {
979         if ($this->thumb_data !== null) {
980             return $this->thumb_data->getBytes();
981         } else {
982             return '';
983         }
984     }
985
986     /**
987      * Make this directory point to a new directory.
988      *
989      * @param PelIfd $i
990      *            the IFD that this directory will point to.
991      */
992     public function setNextIfd(PelIfd $i)
993     {
994         $this->next = $i;
995     }
996
997     /**
998      * Return the IFD pointed to by this directory.
999      *
1000      * @return PelIfd the next IFD, following this IFD. If this is the
1001      *         last IFD, null is returned.
1002      */
1003     public function getNextIfd()
1004     {
1005         return $this->next;
1006     }
1007
1008     /**
1009      * Check if this is the last IFD.
1010      *
1011      * @return boolean true if there are no following IFD, false
1012      *         otherwise.
1013      */
1014     public function isLastIfd()
1015     {
1016         return $this->next === null;
1017     }
1018
1019     /**
1020      * Add a sub-IFD.
1021      *
1022      * Any previous sub-IFD of the same type will be overwritten.
1023      *
1024      * @param PelIfd $sub
1025      *            the sub IFD. The type of must be one of {@link
1026      *            PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
1027      *            PelIfd::INTEROPERABILITY}.
1028      */
1029     public function addSubIfd(PelIfd $sub)
1030     {
1031         $this->sub[$sub->type] = $sub;
1032     }
1033
1034     /**
1035      * Return a sub IFD.
1036      *
1037      * @param int $type
1038      *            the type of the sub IFD. This must be one of {@link
1039      *            PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
1040      *            PelIfd::INTEROPERABILITY}.
1041      *
1042      * @return PelIfd the IFD associated with the type, or null if that
1043      *         sub IFD does not exist.
1044      */
1045     public function getSubIfd($type)
1046     {
1047         if (isset($this->sub[$type])) {
1048             return $this->sub[$type];
1049         } else {
1050             return null;
1051         }
1052     }
1053
1054     /**
1055      * Get all sub IFDs.
1056      *
1057      * @return array an associative array with (IFD-type, {@link
1058      *         PelIfd}) pairs.
1059      */
1060     public function getSubIfds()
1061     {
1062         return $this->sub;
1063     }
1064
1065     /**
1066      * Turn this directory into bytes.
1067      *
1068      * This directory will be turned into a byte string, with the
1069      * specified byte order. The offsets will be calculated from the
1070      * offset given.
1071      *
1072      * @param int $offset
1073      *            the offset of the first byte of this directory.
1074      *
1075      * @param PelByteOrder $order
1076      *            the byte order that should be used when
1077      *            turning integers into bytes. This should be one of {@link
1078      *            PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}.
1079      */
1080     public function getBytes($offset, $order)
1081     {
1082         $bytes = '';
1083         $extra_bytes = '';
1084
1085         Pel::debug('Bytes from IDF will start at offset %d within Exif data', $offset);
1086
1087         $n = count($this->entries) + count($this->sub);
1088         if ($this->thumb_data !== null) {
1089             /*
1090              * We need two extra entries for the thumbnail offset and
1091              * length.
1092              */
1093             $n += 2;
1094         }
1095
1096         $bytes .= PelConvert::shortToBytes($n, $order);
1097
1098         /*
1099          * Initialize offset of extra data. This included the bytes
1100          * preceding this IFD, the bytes needed for the count of entries,
1101          * the entries themselves (and sub entries), the extra data in the
1102          * entries, and the IFD link.
1103          */
1104         $end = $offset + 2 + 12 * $n + 4;
1105
1106         foreach ($this->entries as $tag => $entry) {
1107             /* Each entry is 12 bytes long. */
1108             $bytes .= PelConvert::shortToBytes($entry->getTag(), $order);
1109             $bytes .= PelConvert::shortToBytes($entry->getFormat(), $order);
1110             $bytes .= PelConvert::longToBytes($entry->getComponents(), $order);
1111
1112             /*
1113              * Size? If bigger than 4 bytes, the actual data is not in
1114              * the entry but somewhere else.
1115              */
1116             $data = $entry->getBytes($order);
1117             $s = strlen($data);
1118             if ($s > 4) {
1119                 Pel::debug('Data size %d too big, storing at offset %d instead.', $s, $end);
1120                 $bytes .= PelConvert::longToBytes($end, $order);
1121                 $extra_bytes .= $data;
1122                 $end += $s;
1123             } else {
1124                 Pel::debug('Data size %d fits.', $s);
1125                 /*
1126                  * Copy data directly, pad with NULL bytes as necessary to
1127                  * fill out the four bytes available.
1128                  */
1129                 $bytes .= $data . str_repeat(chr(0), 4 - $s);
1130             }
1131         }
1132
1133         if ($this->thumb_data !== null) {
1134             Pel::debug('Appending %d bytes of thumbnail data at %d', $this->thumb_data->getSize(), $end);
1135             // TODO: make PelEntry a class that can be constructed with
1136             // arguments corresponding to the newt four lines.
1137             $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH, $order);
1138             $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
1139             $bytes .= PelConvert::longToBytes(1, $order);
1140             $bytes .= PelConvert::longToBytes($this->thumb_data->getSize(), $order);
1141
1142             $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT, $order);
1143             $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
1144             $bytes .= PelConvert::longToBytes(1, $order);
1145             $bytes .= PelConvert::longToBytes($end, $order);
1146
1147             $extra_bytes .= $this->thumb_data->getBytes();
1148             $end += $this->thumb_data->getSize();
1149         }
1150
1151         /* Find bytes from sub IFDs. */
1152         $sub_bytes = '';
1153         foreach ($this->sub as $type => $sub) {
1154             if ($type == PelIfd::EXIF) {
1155                 $tag = PelTag::EXIF_IFD_POINTER;
1156             } elseif ($type == PelIfd::GPS) {
1157                 $tag = PelTag::GPS_INFO_IFD_POINTER;
1158             } elseif ($type == PelIfd::INTEROPERABILITY) {
1159                 $tag = PelTag::INTEROPERABILITY_IFD_POINTER;
1160             } else {
1161                 // PelConvert::BIG_ENDIAN is the default used by PelConvert
1162                 $tag = PelConvert::BIG_ENDIAN;
1163             }
1164             /* Make an aditional entry with the pointer. */
1165             $bytes .= PelConvert::shortToBytes($tag, $order);
1166             /* Next the format, which is always unsigned long. */
1167             $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
1168             /* There is only one component. */
1169             $bytes .= PelConvert::longToBytes(1, $order);
1170
1171             $data = $sub->getBytes($end, $order);
1172             $s = strlen($data);
1173             $sub_bytes .= $data;
1174
1175             $bytes .= PelConvert::longToBytes($end, $order);
1176             $end += $s;
1177         }
1178
1179         /* Make link to next IFD, if any */
1180         if ($this->isLastIFD()) {
1181             $link = 0;
1182         } else {
1183             $link = $end;
1184         }
1185
1186         Pel::debug('Link to next IFD: %d', $link);
1187
1188         $bytes .= PelConvert::longtoBytes($link, $order);
1189
1190         $bytes .= $extra_bytes . $sub_bytes;
1191
1192         if (! $this->isLastIfd()) {
1193             $bytes .= $this->next->getBytes($end, $order);
1194         }
1195         return $bytes;
1196     }
1197
1198     /**
1199      * Turn this directory into text.
1200      *
1201      * @return string information about the directory, mainly for
1202      *         debugging.
1203      */
1204     public function __toString()
1205     {
1206         $str = Pel::fmt("Dumping IFD %s with %d entries...\n", $this->getName(), count($this->entries));
1207
1208         foreach ($this->entries as $entry) {
1209             $str .= $entry->__toString();
1210         }
1211         $str .= Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub));
1212
1213         foreach ($this->sub as $type => $ifd) {
1214             $str .= $ifd->__toString();
1215         }
1216         if ($this->next !== null) {
1217             $str .= $this->next->__toString();
1218         }
1219         return $str;
1220     }
1221 }