Source for file PelIfd.php
Documentation is available at PelIfd.php
/* PEL: PHP Exif Library. A library with support for reading and
* writing all Exif headers in JPEG and TIFF images using PHP.
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Martin Geisler.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program in the file COPYING; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
* Classes for dealing with Exif IFDs.
* @author Martin Geisler <mgeisler@users.sourceforge.net>
* @license http://www.gnu.org/licenses/gpl.html GNU General Public
/**#@+ Required class definitions. */
require_once('PelEntryUndefined.php');
require_once('PelEntryRational.php');
require_once('PelDataWindow.php');
require_once('PelEntryAscii.php');
require_once('PelEntryShort.php');
require_once('PelEntryByte.php');
require_once('PelEntryLong.php');
require_once('PelException.php');
require_once('PelFormat.php');
require_once('PelEntry.php');
require_once('PelTag.php');
* Exception indicating a general problem with the IFD.
* @author Martin Geisler <mgeisler@users.sourceforge.net>
* Class representing an Image File Directory (IFD).
* {@link PelTiff TIFF data} is structured as a number of Image File
* Directories, IFDs for short. Each IFD contains a number of {@link }
* PelEntry entries}, some data and finally a link to the next IFD.
* @author Martin Geisler <mgeisler@users.sourceforge.net>
class PelIfd implements IteratorAggregate, ArrayAccess {
* Pass this to the constructor when creating an IFD which will be
* the IFD of the main image.
* Pass this to the constructor when creating an IFD which will be
* the IFD of the thumbnail image.
* Pass this to the constructor when creating an IFD which will be
* Pass this to the constructor when creating an IFD which will be
* Pass this to the constructor when creating an IFD which will be
* the interoperability sub-IFD.
* The entries held by this directory.
* Each tag in the directory is represented by a {@link PelEntry}
private $entries =
array();
* The type of this directory.
* Initialized in the constructor. Must be one of {@link IFD0},
* {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link }
* This will be initialized in the constructor, or be left as null
* if this is the last directory.
* Sub-directories pointed to by this directory.
* This will be an array of ({@link PelTag}, {@link PelIfd}) pairs.
* This will be initialized in the constructor, or be left as null
* if there are no thumbnail as part of this directory.
private $thumb_data =
null;
// TODO: use this format to choose between the
// JPEG_INTERCHANGE_FORMAT and STRIP_OFFSETS tags.
// private $thumb_format;
* Construct a new Image File Directory (IFD).
* The IFD will be empty, use the {@link addEntry()} method to add
* an {@link PelEntry}. Use the {@link setNext()} method to link
* @param int type the type of this IFD. Must be one of {@link }
* IFD0}, {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link }
* INTEROPERABILITY}. An {@link PelIfdException} will be thrown
$type !=
PelIfd::INTEROPERABILITY)
* Load data into a Image File Directory (IFD).
* @param PelDataWindow the data window that will provide the data.
* @param int the offset within the window where the directory will
function load(PelDataWindow $d, $offset) {
Pel::debug('Constructing IFD at offset %d from %d bytes...',
/* Read the number of entries */
$n =
$d->getShort($offset);
Pel::debug('Loading %d entries...', $n);
/* Check if we have enough data. */
if ($offset +
12 *
$n >
$d->getSize()) {
$n =
floor(($offset -
$d->getSize()) /
12);
for ($i =
0; $i <
$n; $i++
) {
// TODO: increment window start instead of using offsets.
$tag =
$d->getShort($offset +
12 *
$i);
Pel::debug('Loading entry with tag 0x%04X: %s (%d of %d)...',
case PelTag::EXIF_IFD_POINTER:
case PelTag::GPS_INFO_IFD_POINTER:
case PelTag::INTEROPERABILITY_IFD_POINTER:
$o =
$d->getLong($offset +
12 *
$i +
8);
Pel::debug('Found sub IFD at offset %d', $o);
/* Map tag to IFD type. */
if ($tag ==
PelTag::EXIF_IFD_POINTER)
elseif ($tag ==
PelTag::GPS_INFO_IFD_POINTER)
elseif ($tag ==
PelTag::INTEROPERABILITY_IFD_POINTER)
$type =
PelIfd::INTEROPERABILITY;
$this->sub[$type] =
new PelIfd($type);
$this->sub[$type]->load($d, $o);
case PelTag::JPEG_INTERCHANGE_FORMAT:
$thumb_offset =
$d->getLong($offset +
12 *
$i +
8);
$this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH:
$thumb_length =
$d->getLong($offset +
12 *
$i +
8);
$this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
$format =
$d->getShort($offset +
12 *
$i +
2);
$components =
$d->getLong($offset +
12 *
$i +
4);
/* The data size. If bigger than 4 bytes, the actual data is
* not in the entry but somewhere else, with the offset stored
$doff =
$offset +
12 *
$i +
8;
$doff =
$d->getLong($doff);
$data =
$d->getClone($doff, $s);
/* Throw the exception when running in strict mode, store
/* The format of the thumbnail is stored in this tag. */
// TODO: handle TIFF thumbnail.
// if ($tag == PelTag::COMPRESSION) {
// $this->thumb_format = $data->getShort();
$o =
$d->getLong($offset +
12 *
$n);
Pel::debug('Current offset is %d, link at %d points to %d.',
$offset, $offset +
12 *
$n, $o);
/* Sanity check: we need 6 bytes */
if ($o >
$d->getSize() -
6) {
if ($this->type ==
PelIfd::IFD1) // IFD1 shouldn't link further...
$this->next->load($d, $o);
* Make a new entry from a bunch of bytes.
* This method will create the proper subclass of {@link PelEntry}
* corresponding to the {@link PelTag} and {@link PelFormat} given.
* The entry will be initialized with the data given.
* Please note that the data you pass to this method should come
* from an image, that is, it should be raw bytes. If instead you
* want to create an entry for holding, say, an short integer, then
* create a {@link PelEntryShort} object directly and load the data
* A {@link PelUnexpectedFormatException} is thrown if a mismatch is
* discovered between the tag and format, and likewise a {@link }
* PelWrongComponentCountException} is thrown if the number of
* components does not match the requirements of the tag. The
* requirements for a given tag (if any) can be found in the
* documentation for {@link PelTag}.
* @param PelTag the tag of the entry.
* @param PelFormat the format of the entry.
* @param int the components in the entry.
* @param PelDataWindow the data which will be used to construct the
* @return PelEntry a newly created entry, holding the data given.
/* First handle tags for which we have a specific PelEntryXXX
case self::INTEROPERABILITY:
case PelTag::DATE_TIME_ORIGINAL:
case PelTag::DATE_TIME_DIGITIZED:
// TODO: handle timezones.
case PelTag::FLASH_PIX_VERSION:
case PelTag::INTEROPERABILITY_VERSION:
if ($data->getSize() <
8) {
rtrim($data->getBytes(0, 8)));
for ($i =
0; $i <
$components; $i++
) {
/* Convert the byte to a character if it is non-null ---
* information about the character encoding of these entries
* would be very nice to have! So far my tests have shown
* that characters in the Latin-1 character set are stored in
* a single byte followed by a NULL byte. */
/* Then handle the basic formats. */
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getByte($i));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getSByte($i));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getShort($i*
2));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getSShort($i*
2));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getLong($i*
4));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getSLong($i*
4));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getRational($i*
8));
for ($i =
0; $i <
$components; $i++
)
$v->addNumber($data->getSRational($i*
8));
* Extract thumbnail data safely.
* It is safe to call this method repeatedly with either the offset
* or the length set to zero, since it requires both of these
* arguments to be positive before the thumbnail is extracted.
* When both parameters are set it will check the length against the
* available data and adjust as necessary. Only then is the
* @param PelDataWindow the data from which the thumbnail will be
* @param int the offset into the data.
* @param int the length of the thumbnail.
private function safeSetThumbnail(PelDataWindow $d, $offset, $length) {
/* Load the thumbnail if both the offset and the length is
if ($offset >
0 &&
$length >
0) {
/* Some images have a broken length, so we try to carefully
* check the length before we store the thumbnail. */
if ($offset +
$length >
$d->getSize()) {
$d->getSize() -
$offset));
$length =
$d->getSize() -
$offset;
/* Now set the thumbnail normally. */
* Use this to embed an arbitrary JPEG image within this IFD. The
* data will be checked to ensure that it has a proper {@link }
* PelJpegMarker::EOI} at the end. If not, then the length is
* adjusted until one if found. An {@link PelIfdException} might be
* thrown (depending on {@link Pel::$strict}) this case.
* @param PelDataWindow the thumbnail data.
/* Now move backwards until we find the EOI JPEG marker. */
while ($d->getByte($size -
2) !=
0xFF ||
if ($size !=
$d->getSize())
$this->thumb_data =
$d->getClone(0, $size);
* Get the type of this directory.
* @return int of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, {@link }
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link }
* PelIfd::INTEROPERABILITY}.
* Is a given tag valid for this IFD?
* Different types of IFDs can contain different kinds of tags ---
* the {@link IFD0} type, for example, cannot contain a {@link }
* PelTag::GPS_LONGITUDE} tag.
* A special exception is tags with values above 0xF000. They are
* treated as private tags and will be allowed everywhere (use this
* for testing or for implementing your own types of tags).
* @return boolean true if the tag is considered valid in this IFD,
* Returns a list of valid tags for this IFD.
* @return array an array of {@link PelTag}s which are valid for
return array(PelTag::IMAGE_WIDTH,
PelTag::PHOTOMETRIC_INTERPRETATION,
PelTag::PRIMARY_CHROMATICITIES,
PelTag::JPEG_INTERCHANGE_FORMAT,
PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH,
PelTag::REFERENCE_BLACK_WHITE,
return array(PelTag::EXPOSURE_TIME,
PelTag::COMPONENTS_CONFIGURATION,
PelTag::COMPRESSED_BITS_PER_PIXEL,
PelTag::SUB_SEC_TIME_ORIGINAL,
PelTag::SUB_SEC_TIME_DIGITIZED,
PelTag::SPATIAL_FREQUENCY_RESPONSE,
PelTag::FOCAL_PLANE_X_RESOLUTION,
PelTag::FOCAL_PLANE_Y_RESOLUTION,
PelTag::FOCAL_PLANE_RESOLUTION_UNIT,
PelTag::FOCAL_LENGTH_IN_35MM_FILM,
PelTag::DEVICE_SETTING_DESCRIPTION,
PelTag::SUBJECT_DISTANCE_RANGE,
PelTag::INTEROPERABILITY_IFD_POINTER,
return array(PelTag::GPS_VERSION_ID,
PelTag::GPS_IMG_DIRECTION_REF,
PelTag::GPS_DEST_LATITUDE_REF,
PelTag::GPS_DEST_LONGITUDE_REF,
PelTag::GPS_DEST_DISTANCE_REF,
PelTag::GPS_PROCESSING_METHOD,
case PelIfd::INTEROPERABILITY:
return array(PelTag::INTEROPERABILITY_INDEX,
PelTag::INTEROPERABILITY_VERSION,
PelTag::RELATED_IMAGE_FILE_FORMAT,
PelTag::RELATED_IMAGE_LENGTH);
/* TODO: Where do these tags belong?
PelTag::INTER_COLOR_PROFILE,
PelTag::CFA_REPEAT_PATTERN_DIM,
* Get the name of an IFD type.
* @param int one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1},
* {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link }
* PelIfd::INTEROPERABILITY}.
* @return string the name of type.
case self::INTEROPERABILITY:
return 'Interoperability';
* Get the name of this directory.
* @return string the name of this directory.
* Adds an entry to the directory.
* @param PelEntry the entry that will be added. If the entry is not
* valid in this IFD (as per {@link isValidTag()}) an
* {@link PelInvalidDataException} is thrown.
* @todo The entry will be identified with its tag, so each
* directory can only contain one entry with each tag. Is this a
$e->setIfdType($this->type);
$this->entries[$e->getTag()] =
$e;
$this->getName(), $e->__toString());
* Does a given tag exist in this IFD?
* This methods is part of the ArrayAccess SPL interface for
* overriding array access of objects, it allows you to check for
* existance of an entry in the IFD:
* if (isset($ifd[PelTag::FNUMBER]))
* // ... do something with the F-number.
* @param PelTag the offset to check.
* @return boolean whether the tag exists.
return isset
($this->entries[$tag]);
* Retrieve a given tag from this IFD.
* This methods is part of the ArrayAccess SPL interface for
* overriding array access of objects, it allows you to read entries
* from the IFD the same was as for an array:
* $entry = $ifd[PelTag::FNUMBER];
* @param PelTag the tag to return. It is an error to ask for a tag
* which is not in the IFD, just like asking for a non-existant
* @return PelEntry the entry.
return $this->entries[$tag];
* Set or update a given tag in this IFD.
* This methods is part of the ArrayAccess SPL interface for
* overriding array access of objects, it allows you to add new
* entries or replace esisting entries by doing:
* $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry;
* Note that the actual array index passed is ignored! Instead the
* {@link PelTag} from the entry is used.
* @param PelTag the offset to update.
* @param PelEntry the new value.
$this->entries[$tag] =
$e;
* Unset a given tag in this IFD.
* This methods is part of the ArrayAccess SPL interface for
* overriding array access of objects, it allows you to delete
* entries in the IFD by doing:
* unset($ifd[PelTag::EXPOSURE_BIAS_VALUE])
* @param PelTag the offset to delete.
unset
($this->entries[$tag]);
* @param PelTag the tag identifying the entry.
* @return PelEntry the entry associated with the tag, or null if no
if (isset
($this->entries[$tag]))
return $this->entries[$tag];
* Returns all entries contained in this IFD.
* @return array an array of {@link PelEntry} objects, or rather
* descendant classes. The array has {@link PelTag}s as keys
* and the entries as values.
* Return an iterator for all entries contained in this IFD.
* Used with foreach as in
* foreach ($ifd as $tag => $entry) {
* // $tag is now a PelTag and $entry is a PelEntry object.
* @return Iterator an iterator using the {@link PelTag tags} as
* keys and the entries as values.
return new ArrayIterator($this->entries);
* Returns available thumbnail data.
* @return string the bytes in the thumbnail, if any. If the IFD
* does not contain any thumbnail data, the empty string is
* @todo Throw an exception instead when no data is available?
* @todo Return the $this->thumb_data object instead of the bytes?
if ($this->thumb_data !=
null)
* Make this directory point to a new directory.
* @param PelIfd the IFD that this directory will point to.
* Return the IFD pointed to by this directory.
* @return PelIfd the next IFD, following this IFD. If this is the
* last IFD, null is returned.
* Check if this is the last IFD.
* @return boolean true if there are no following IFD, false
return $this->next ==
null;
* Any previous sub-IFD of the same type will be overwritten.
* @param PelIfd the sub IFD. The type of must be one of {@link }
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link }
* PelIfd::INTEROPERABILITY}.
$this->sub[$sub->type] =
$sub;
* @param int the type of the sub IFD. This must be one of {@link }
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link }
* PelIfd::INTEROPERABILITY}.
* @return PelIfd the IFD associated with the type, or null if that
* sub IFD does not exist.
if (isset
($this->sub[$type]))
return $this->sub[$type];
* @return array an associative array with (IFD-type, {@link }
* Turn this directory into bytes.
* This directory will be turned into a byte string, with the
* specified byte order. The offsets will be calculated from the
* @param int the offset of the first byte of this directory.
* @param PelByteOrder the byte order that should be used when
* turning integers into bytes. This should be one of {@link }
* PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}.
Pel::debug('Bytes from IDF will start at offset %d within Exif data',
if ($this->thumb_data !=
null) {
/* We need two extra entries for the thumbnail offset and
/* Initialize offset of extra data. This included the bytes
* preceding this IFD, the bytes needed for the count of entries,
* the entries themselves (and sub entries), the extra data in the
* entries, and the IFD link.
$end =
$offset +
2 +
12 *
$n +
4;
foreach ($this->entries as $tag =>
$entry) {
/* Each entry is 12 bytes long. */
* Size? If bigger than 4 bytes, the actual data is not in
* the entry but somewhere else.
$data =
$entry->getBytes($order);
Pel::debug('Data size %d too big, storing at offset %d instead.',
/* Copy data directly, pad with NULL bytes as necessary to
* fill out the four bytes available.*/
if ($this->thumb_data !=
null) {
Pel::debug('Appending %d bytes of thumbnail data at %d',
$this->thumb_data->getSize(), $end);
// TODO: make PelEntry a class that can be constructed with
// arguments corresponding to the newt four lines.
$extra_bytes .=
$this->thumb_data->getBytes();
$end +=
$this->thumb_data->getSize();
/* Find bytes from sub IFDs. */
foreach ($this->sub as $type =>
$sub) {
$tag =
PelTag::EXIF_IFD_POINTER;
$tag =
PelTag::GPS_INFO_IFD_POINTER;
elseif ($type ==
PelIfd::INTEROPERABILITY)
$tag =
PelTag::INTEROPERABILITY_IFD_POINTER;
/* Make an aditional entry with the pointer. */
/* Next the format, which is always unsigned long. */
/* There is only one component. */
$data =
$sub->getBytes($end, $order);
/* Make link to next IFD, if any*/
if ($this->isLastIFD()) {
Pel::debug('Link to next IFD: %d', $link);
$bytes .=
$extra_bytes .
$sub_bytes;
$bytes .=
$this->next->getBytes($end, $order);
* Turn this directory into text.
* @return string information about the directory, mainly for
$str =
Pel::fmt("Dumping IFD %s with %d entries...\n",
foreach ($this->entries as $entry)
$str .=
$entry->__toString();
$str .=
Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub));
foreach ($this->sub as $type =>
$ifd)
$str .=
$ifd->__toString();
Documentation generated on Thu, 05 May 2011 07:19:16 +0200 by phpDocumentor 1.4.3