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.
8 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Martin Geisler.
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.
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.
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
25 namespace lsolesen\pel;
28 * Classes for dealing with Exif IFDs.
30 * @author Martin Geisler <mgeisler@users.sourceforge.net>
31 * @license http://www.gnu.org/licenses/gpl.html GNU General Public
37 * Class representing an Image File Directory (IFD).
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.
43 * @author Martin Geisler <mgeisler@users.sourceforge.net>
46 class PelIfd implements \IteratorAggregate, \ArrayAccess
52 * Pass this to the constructor when creating an IFD which will be
53 * the IFD of the main image.
58 * Thumbnail image IFD.
60 * Pass this to the constructor when creating an IFD which will be
61 * the IFD of the thumbnail image.
68 * Pass this to the constructor when creating an IFD which will be
76 * Pass this to the constructor when creating an IFD which will be
82 * Interoperability IFD.
84 * Pass this to the constructor when creating an IFD which will be
85 * the interoperability sub-IFD.
87 const INTEROPERABILITY = 4;
90 * The entries held by this directory.
92 * Each tag in the directory is represented by a {@link PelEntry}
93 * object in this array.
97 private $entries = array();
100 * The type of this directory.
102 * Initialized in the constructor. Must be one of {@link IFD0},
103 * {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link
111 * The next directory.
113 * This will be initialized in the constructor, or be left as null
114 * if this is the last directory.
118 private $next = null;
121 * Sub-directories pointed to by this directory.
123 * This will be an array of ({@link PelTag}, {@link PelIfd}) pairs.
127 private $sub = array();
130 * The thumbnail data.
132 * This will be initialized in the constructor, or be left as null
133 * if there are no thumbnail as part of this directory.
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;
143 * Construct a new Image File Directory (IFD).
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.
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
155 public function __construct($type)
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);
166 * Load data into a Image File Directory (IFD).
168 * @param PelDataWindow $d
169 * the data window that will provide the data.
172 * the offset within the window where the directory will
175 public function load(PelDataWindow $d, $offset)
180 Pel::debug('Constructing IFD at offset %d from %d bytes...', $offset, $d->getSize());
182 /* Read the number of entries */
183 $n = $d->getShort($offset);
184 Pel::debug('Loading %d entries...', $n);
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));
194 for ($i = 0; $i < $n; $i ++) {
195 // TODO: increment window start instead of using offsets.
196 $tag = $d->getShort($offset + 12 * $i);
198 'Loading entry with tag 0x%04X: %s (%d of %d)...',
200 PelTag::getName($this->type, $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);
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) {
216 } elseif ($tag == PelTag::INTEROPERABILITY_IFD_POINTER) {
217 $type = PelIfd::INTEROPERABILITY;
220 $this->sub[$type] = new PelIfd($type);
221 $this->sub[$type]->load($d, $o);
223 case PelTag::JPEG_INTERCHANGE_FORMAT:
224 $thumb_offset = $d->getLong($offset + 12 * $i + 8);
225 $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
227 case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH:
228 $thumb_length = $d->getLong($offset + 12 * $i + 8);
229 $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
232 $format = $d->getShort($offset + 12 * $i + 2);
233 $components = $d->getLong($offset + 12 * $i + 4);
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
240 $s = PelFormat::getSize($format) * $components;
242 $doff = $offset + 12 * $i + 8;
244 $doff = $d->getLong($doff);
246 $data = $d->getClone($doff, $s);
248 $data = new PelDataWindow();
252 $entry = $this->newEntryFromData($tag, $format, $components, $data);
253 $this->addEntry($entry);
254 } catch (PelException $e) {
256 * Throw the exception when running in strict mode, store
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();
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);
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));
280 if ($this->type == PelIfd::IFD1) {
281 // IFD1 shouldn't link further...
282 Pel::maybeThrow(new PelIfdException('IFD1 links to another IFD!'));
284 $this->next = new PelIfd(PelIfd::IFD1);
285 $this->next->load($d, $o);
288 Pel::debug('Last IFD.');
293 * Make a new entry from a bunch of bytes.
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.
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
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}.
312 * @param integer $tag
313 * the tag of the entry as defined in {@link PelTag}.
315 * @param integer $format
316 * the format of the entry as defined in {@link PelFormat}.
318 * @param int $components
319 * the components in the entry.
321 * @param PelDataWindow $data
322 * the data which will be used to construct the
325 * @return PelEntry a newly created entry, holding the data given.
327 public function newEntryFromData($tag, $format, $components, PelDataWindow $data)
331 * First handle tags for which we have a specific PelEntryXXX
334 switch ($this->type) {
338 case self::INTEROPERABILITY:
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);
346 if ($components != 20) {
347 throw new PelWrongComponentCountException($this->type, $tag, $components, 20);
349 // TODO: handle timezones.
350 return new PelEntryTime($tag, $data->getBytes(0, - 1), PelEntryTime::EXIF_STRING);
352 case PelTag::COPYRIGHT:
353 if ($format != PelFormat::ASCII) {
354 throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::ASCII);
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
362 return new PelEntryCopyright($v[0], $v[1]);
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);
370 return new PelEntryVersion($tag, $data->getBytes() / 100);
372 case PelTag::USER_COMMENT:
373 if ($format != PelFormat::UNDEFINED) {
374 throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::UNDEFINED);
376 if ($data->getSize() < 8) {
377 return new PelEntryUserComment();
379 return new PelEntryUserComment($data->getBytes(8), rtrim($data->getBytes(0, 8)));
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);
391 for ($i = 0; $i < $components; $i ++) {
392 $b = $data->getByte($i);
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.
405 return new PelEntryWindowsString($tag, $v);
407 // This point can be reached! Continue with default.
410 /* Then handle the basic formats. */
412 case PelFormat::BYTE:
413 $v = new PelEntryByte($tag);
414 for ($i = 0; $i < $components; $i ++) {
415 $v->addNumber($data->getByte($i));
419 case PelFormat::SBYTE:
420 $v = new PelEntrySByte($tag);
421 for ($i = 0; $i < $components; $i ++) {
422 $v->addNumber($data->getSByte($i));
426 case PelFormat::ASCII:
427 return new PelEntryAscii($tag, rtrim($data->getBytes(0), "\0"));
429 case PelFormat::SHORT:
430 $v = new PelEntryShort($tag);
431 for ($i = 0; $i < $components; $i ++) {
432 $v->addNumber($data->getShort($i * 2));
436 case PelFormat::SSHORT:
437 $v = new PelEntrySShort($tag);
438 for ($i = 0; $i < $components; $i ++) {
439 $v->addNumber($data->getSShort($i * 2));
443 case PelFormat::LONG:
444 $v = new PelEntryLong($tag);
445 for ($i = 0; $i < $components; $i ++) {
446 $v->addNumber($data->getLong($i * 4));
450 case PelFormat::SLONG:
451 $v = new PelEntrySLong($tag);
452 for ($i = 0; $i < $components; $i ++) {
453 $v->addNumber($data->getSLong($i * 4));
457 case PelFormat::RATIONAL:
458 $v = new PelEntryRational($tag);
459 for ($i = 0; $i < $components; $i ++) {
460 $v->addNumber($data->getRational($i * 8));
464 case PelFormat::SRATIONAL:
465 $v = new PelEntrySRational($tag);
466 for ($i = 0; $i < $components; $i ++) {
467 $v->addNumber($data->getSRational($i * 8));
471 case PelFormat::UNDEFINED:
472 return new PelEntryUndefined($tag, $data->getBytes());
475 throw new PelException('Unsupported format: %s', PelFormat::getName($format));
481 * Extract thumbnail data safely.
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.
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.
491 * @param PelDataWindow $d
492 * the data from which the thumbnail will be
496 * the offset into the data.
499 * the length of the thumbnail.
501 private function safeSetThumbnail(PelDataWindow $d, $offset, $length)
504 * Load the thumbnail if both the offset and the length is
507 if ($offset > 0 && $length > 0) {
509 * Some images have a broken length, so we try to carefully
510 * check the length before we store the thumbnail.
512 if ($offset + $length > $d->getSize()) {
515 'Thumbnail length %d bytes ' . 'adjusted to %d bytes.',
517 $d->getSize() - $offset));
518 $length = $d->getSize() - $offset;
521 /* Now set the thumbnail normally. */
522 $this->setThumbnail($d->getClone($offset, $length));
527 * Set thumbnail data.
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.
535 * @param PelDataWindow $d
536 * the thumbnail data.
538 public function setThumbnail(PelDataWindow $d)
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) {
546 if ($size != $d->getSize()) {
547 Pel::maybeThrow(new PelIfdException('Decrementing thumbnail size ' . 'to %d bytes', $size));
549 $this->thumb_data = $d->getClone(0, $size);
553 * Get the type of this directory.
555 * @return int of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, {@link
556 * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
557 * PelIfd::INTEROPERABILITY}.
559 public function getType()
565 * Is a given tag valid for this IFD?
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.
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).
578 * @return boolean true if the tag is considered valid in this IFD,
581 * @see getValidTags()
583 public function isValidTag($tag)
585 return $tag > 0xF000 || in_array($tag, $this->getValidTags());
589 * Returns a list of valid tags for this IFD.
591 * @return array an array of {@link PelTag}s which are valid for
594 public function getValidTags()
596 switch ($this->type) {
601 PelTag::IMAGE_LENGTH,
602 PelTag::BITS_PER_SAMPLE,
604 PelTag::PHOTOMETRIC_INTERPRETATION,
605 PelTag::DOCUMENT_NAME,
606 PelTag::IMAGE_DESCRIPTION,
609 PelTag::STRIP_OFFSETS,
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,
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,
631 PelTag::EXIF_IFD_POINTER,
632 PelTag::GPS_INFO_IFD_POINTER,
644 PelTag::EXPOSURE_TIME,
646 PelTag::EXPOSURE_PROGRAM,
647 PelTag::SPECTRAL_SENSITIVITY,
648 PelTag::ISO_SPEED_RATINGS,
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,
664 PelTag::FOCAL_LENGTH,
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,
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,
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,
696 PelTag::DEVICE_SETTING_DESCRIPTION,
697 PelTag::SUBJECT_DISTANCE_RANGE,
698 PelTag::IMAGE_UNIQUE_ID,
699 PelTag::INTEROPERABILITY_IFD_POINTER,
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,
715 PelTag::GPS_MEASURE_MODE,
717 PelTag::GPS_SPEED_REF,
719 PelTag::GPS_TRACK_REF,
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
738 case PelIfd::INTEROPERABILITY:
740 PelTag::INTEROPERABILITY_INDEX,
741 PelTag::INTEROPERABILITY_VERSION,
742 PelTag::RELATED_IMAGE_FILE_FORMAT,
743 PelTag::RELATED_IMAGE_WIDTH,
744 PelTag::RELATED_IMAGE_LENGTH
748 * TODO: Where do these tags belong?
749 * PelTag::FILL_ORDER,
750 * PelTag::TRANSFER_RANGE,
752 * PelTag::BATTERY_LEVEL,
754 * PelTag::INTER_COLOR_PROFILE,
755 * PelTag::CFA_REPEAT_PATTERN_DIM,
761 * Get the name of an IFD type.
764 * one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1},
765 * {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
766 * PelIfd::INTEROPERABILITY}.
768 * @return string the name of type.
770 public static function getTypeName($type)
781 case self::INTEROPERABILITY:
782 return 'Interoperability';
784 throw new PelIfdException('Unknown IFD type: %d', $type);
789 * Get the name of this directory.
791 * @return string the name of this directory.
793 public function getName()
795 return $this->getTypeName($this->type);
799 * Adds an entry to the directory.
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.
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
810 public function addEntry(PelEntry $e)
812 if ($this->isValidTag($e->getTag())) {
813 $e->setIfdType($this->type);
814 $this->entries[$e->getTag()] = $e;
816 throw new PelInvalidDataException("IFD %s cannot hold\n%s", $this->getName(), $e->__toString());
821 * Does a given tag exist in this IFD?
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:
828 * if (isset($ifd[PelTag::FNUMBER]))
829 * // ... do something with the F-number.
833 * the offset to check.
835 * @return boolean whether the tag exists.
837 public function offsetExists($tag)
839 return isset($this->entries[$tag]);
843 * Retrieve a given tag from this IFD.
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:
850 * $entry = $ifd[PelTag::FNUMBER];
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
858 * @return PelEntry the entry.
860 public function offsetGet($tag)
862 return $this->entries[$tag];
866 * Set or update a given tag in this IFD.
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:
873 * $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry;
876 * Note that the actual array index passed is ignored! Instead the
877 * {@link PelTag} from the entry is used.
880 * the offset to update.
885 public function offsetSet($tag, $e)
887 if ($e instanceof PelEntry) {
889 $this->entries[$tag] = $e;
891 throw new PelInvalidArgumentException('Argument "%s" must be a PelEntry.', $e);
896 * Unset a given tag in this IFD.
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:
903 * unset($ifd[PelTag::EXPOSURE_BIAS_VALUE])
907 * the offset to delete.
909 public function offsetUnset($tag)
911 unset($this->entries[$tag]);
918 * the tag identifying the entry.
920 * @return PelEntry the entry associated with the tag, or null if no
923 public function getEntry($tag)
925 if (isset($this->entries[$tag])) {
926 return $this->entries[$tag];
933 * Returns all entries contained in this IFD.
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.
942 public function getEntries()
944 return $this->entries;
948 * Return an iterator for all entries contained in this IFD.
950 * Used with foreach as in
953 * foreach ($ifd as $tag => $entry) {
954 * // $tag is now a PelTag and $entry is a PelEntry object.
958 * @return Iterator an iterator using the {@link PelTag tags} as
959 * keys and the entries as values.
961 public function getIterator()
963 return new \ArrayIterator($this->entries);
967 * Returns available thumbnail data.
969 * @return string the bytes in the thumbnail, if any. If the IFD
970 * does not contain any thumbnail data, the empty string is
973 * @todo Throw an exception instead when no data is available?
975 * @todo Return the $this->thumb_data object instead of the bytes?
977 public function getThumbnailData()
979 if ($this->thumb_data !== null) {
980 return $this->thumb_data->getBytes();
987 * Make this directory point to a new directory.
990 * the IFD that this directory will point to.
992 public function setNextIfd(PelIfd $i)
998 * Return the IFD pointed to by this directory.
1000 * @return PelIfd the next IFD, following this IFD. If this is the
1001 * last IFD, null is returned.
1003 public function getNextIfd()
1009 * Check if this is the last IFD.
1011 * @return boolean true if there are no following IFD, false
1014 public function isLastIfd()
1016 return $this->next === null;
1022 * Any previous sub-IFD of the same type will be overwritten.
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}.
1029 public function addSubIfd(PelIfd $sub)
1031 $this->sub[$sub->type] = $sub;
1038 * the type of the sub IFD. This must be one of {@link
1039 * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
1040 * PelIfd::INTEROPERABILITY}.
1042 * @return PelIfd the IFD associated with the type, or null if that
1043 * sub IFD does not exist.
1045 public function getSubIfd($type)
1047 if (isset($this->sub[$type])) {
1048 return $this->sub[$type];
1057 * @return array an associative array with (IFD-type, {@link
1060 public function getSubIfds()
1066 * Turn this directory into bytes.
1068 * This directory will be turned into a byte string, with the
1069 * specified byte order. The offsets will be calculated from the
1072 * @param int $offset
1073 * the offset of the first byte of this directory.
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}.
1080 public function getBytes($offset, $order)
1085 Pel::debug('Bytes from IDF will start at offset %d within Exif data', $offset);
1087 $n = count($this->entries) + count($this->sub);
1088 if ($this->thumb_data !== null) {
1090 * We need two extra entries for the thumbnail offset and
1096 $bytes .= PelConvert::shortToBytes($n, $order);
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.
1104 $end = $offset + 2 + 12 * $n + 4;
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);
1113 * Size? If bigger than 4 bytes, the actual data is not in
1114 * the entry but somewhere else.
1116 $data = $entry->getBytes($order);
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;
1124 Pel::debug('Data size %d fits.', $s);
1126 * Copy data directly, pad with NULL bytes as necessary to
1127 * fill out the four bytes available.
1129 $bytes .= $data . str_repeat(chr(0), 4 - $s);
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);
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);
1147 $extra_bytes .= $this->thumb_data->getBytes();
1148 $end += $this->thumb_data->getSize();
1151 /* Find bytes from sub IFDs. */
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;
1161 // PelConvert::BIG_ENDIAN is the default used by PelConvert
1162 $tag = PelConvert::BIG_ENDIAN;
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);
1171 $data = $sub->getBytes($end, $order);
1173 $sub_bytes .= $data;
1175 $bytes .= PelConvert::longToBytes($end, $order);
1179 /* Make link to next IFD, if any */
1180 if ($this->isLastIFD()) {
1186 Pel::debug('Link to next IFD: %d', $link);
1188 $bytes .= PelConvert::longtoBytes($link, $order);
1190 $bytes .= $extra_bytes . $sub_bytes;
1192 if (! $this->isLastIfd()) {
1193 $bytes .= $this->next->getBytes($end, $order);
1199 * Turn this directory into text.
1201 * @return string information about the directory, mainly for
1204 public function __toString()
1206 $str = Pel::fmt("Dumping IFD %s with %d entries...\n", $this->getName(), count($this->entries));
1208 foreach ($this->entries as $entry) {
1209 $str .= $entry->__toString();
1211 $str .= Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub));
1213 foreach ($this->sub as $type => $ifd) {
1214 $str .= $ifd->__toString();
1216 if ($this->next !== null) {
1217 $str .= $this->next->__toString();