summaryrefslogtreecommitdiff
path: root/modules/autorotate/lib/pel/PelIfd.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules/autorotate/lib/pel/PelIfd.php')
-rw-r--r--modules/autorotate/lib/pel/PelIfd.php1200
1 files changed, 1200 insertions, 0 deletions
diff --git a/modules/autorotate/lib/pel/PelIfd.php b/modules/autorotate/lib/pel/PelIfd.php
new file mode 100644
index 0000000..851bb30
--- /dev/null
+++ b/modules/autorotate/lib/pel/PelIfd.php
@@ -0,0 +1,1200 @@
+<?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
+ */
+
+/* $Id$ */
+
+
+/**
+ * Classes for dealing with Exif IFDs.
+ *
+ * @author Martin Geisler <mgeisler@users.sourceforge.net>
+ * @version $Revision$
+ * @date $Date$
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**#@+ 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');
+require_once('Pel.php');
+/**#@-*/
+
+
+/**
+ * Exception indicating a general problem with the IFD.
+ *
+ * @author Martin Geisler <mgeisler@users.sourceforge.net>
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelIfdException extends PelException {}
+
+/**
+ * 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>
+ * @package PEL
+ */
+class PelIfd implements IteratorAggregate, ArrayAccess {
+
+ /**
+ * Main image IFD.
+ *
+ * Pass this to the constructor when creating an IFD which will be
+ * the IFD of the main image.
+ */
+ const IFD0 = 0;
+
+ /**
+ * Thumbnail image IFD.
+ *
+ * Pass this to the constructor when creating an IFD which will be
+ * the IFD of the thumbnail image.
+ */
+ const IFD1 = 1;
+
+ /**
+ * Exif IFD.
+ *
+ * Pass this to the constructor when creating an IFD which will be
+ * the Exif sub-IFD.
+ */
+ const EXIF = 2;
+
+ /**
+ * GPS IFD.
+ *
+ * Pass this to the constructor when creating an IFD which will be
+ * the GPS sub-IFD.
+ */
+ const GPS = 3;
+
+ /**
+ * Interoperability IFD.
+ *
+ * Pass this to the constructor when creating an IFD which will be
+ * the interoperability sub-IFD.
+ */
+ const INTEROPERABILITY = 4;
+
+ /**
+ * The entries held by this directory.
+ *
+ * Each tag in the directory is represented by a {@link PelEntry}
+ * object in this array.
+ *
+ * @var array
+ */
+ 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
+ * INTEROPERABILITY}.
+ *
+ * @var int
+ */
+ private $type;
+
+ /**
+ * The next directory.
+ *
+ * This will be initialized in the constructor, or be left as null
+ * if this is the last directory.
+ *
+ * @var PelIfd
+ */
+ private $next = null;
+
+ /**
+ * Sub-directories pointed to by this directory.
+ *
+ * This will be an array of ({@link PelTag}, {@link PelIfd}) pairs.
+ *
+ * @var array
+ */
+ private $sub = array();
+
+ /**
+ * The thumbnail data.
+ *
+ * This will be initialized in the constructor, or be left as null
+ * if there are no thumbnail as part of this directory.
+ *
+ * @var PelDataWindow
+ */
+ 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
+ * this IFD to another.
+ *
+ * @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
+ * otherwise.
+ */
+ function __construct($type) {
+ if ($type != PelIfd::IFD0 && $type != PelIfd::IFD1 &&
+ $type != PelIfd::EXIF && $type != PelIfd::GPS &&
+ $type != PelIfd::INTEROPERABILITY)
+ throw new PelIfdException('Unknown IFD type: %d', $type);
+
+ $this->type = $type;
+ }
+
+
+ /**
+ * 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
+ * be found.
+ */
+ function load(PelDataWindow $d, $offset) {
+ $thumb_offset = 0;
+ $thumb_length = 0;
+
+ Pel::debug('Constructing IFD at offset %d from %d bytes...',
+ $offset, $d->getSize());
+
+ /* Read the number of entries */
+ $n = $d->getShort($offset);
+ Pel::debug('Loading %d entries...', $n);
+
+ $offset += 2;
+
+ /* Check if we have enough data. */
+ if ($offset + 12 * $n > $d->getSize()) {
+ $n = floor(($offset - $d->getSize()) / 12);
+ Pel::maybeThrow(new PelIfdException('Adjusted to: %d.', $n));
+ }
+
+ 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)...',
+ $tag, PelTag::getName($this->type, $tag), $i + 1, $n);
+
+ switch ($tag) {
+ 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)
+ $type = PelIfd::EXIF;
+ elseif ($tag == PelTag::GPS_INFO_IFD_POINTER)
+ $type = PelIfd::GPS;
+ elseif ($tag == PelTag::INTEROPERABILITY_IFD_POINTER)
+ $type = PelIfd::INTEROPERABILITY;
+
+ $this->sub[$type] = new PelIfd($type);
+ $this->sub[$type]->load($d, $o);
+ break;
+ case PelTag::JPEG_INTERCHANGE_FORMAT:
+ $thumb_offset = $d->getLong($offset + 12 * $i + 8);
+ $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
+ break;
+ case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH:
+ $thumb_length = $d->getLong($offset + 12 * $i + 8);
+ $this->safeSetThumbnail($d, $thumb_offset, $thumb_length);
+ break;
+ default:
+ $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
+ * in the entry.
+ */
+ $s = PelFormat::getSize($format) * $components;
+ if ($s > 0) {
+ $doff = $offset + 12 * $i + 8;
+ if ($s > 4)
+ $doff = $d->getLong($doff);
+
+ $data = $d->getClone($doff, $s);
+ } else {
+ $data = new PelDataWindow();
+ }
+
+ try {
+ $entry = $this->newEntryFromData($tag, $format, $components, $data);
+ $this->addEntry($entry);
+ } catch (PelException $e) {
+ /* Throw the exception when running in strict mode, store
+ * otherwise. */
+ Pel::maybeThrow($e);
+ }
+
+ /* The format of the thumbnail is stored in this tag. */
+ // TODO: handle TIFF thumbnail.
+ // if ($tag == PelTag::COMPRESSION) {
+ // $this->thumb_format = $data->getShort();
+ // }
+ break;
+ }
+ }
+
+ /* Offset to next IFD */
+ $o = $d->getLong($offset + 12 * $n);
+ Pel::debug('Current offset is %d, link at %d points to %d.',
+ $offset, $offset + 12 * $n, $o);
+
+ if ($o > 0) {
+ /* Sanity check: we need 6 bytes */
+ if ($o > $d->getSize() - 6) {
+ Pel::maybeThrow(new PelIfdException('Bogus offset to next IFD: ' .
+ '%d > %d!',
+ $o, $d->getSize() - 6));
+ } else {
+ if ($this->type == PelIfd::IFD1) // IFD1 shouldn't link further...
+ Pel::maybeThrow(new PelIfdException('IFD1 links to another IFD!'));
+
+ $this->next = new PelIfd(PelIfd::IFD1);
+ $this->next->load($d, $o);
+ }
+ } else {
+ Pel::debug('Last IFD.');
+ }
+ }
+
+
+ /**
+ * 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
+ * into it.
+ *
+ * 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
+ * entry.
+ *
+ * @return PelEntry a newly created entry, holding the data given.
+ */
+ function newEntryFromData($tag, $format, $components, PelDataWindow $data) {
+
+ /* First handle tags for which we have a specific PelEntryXXX
+ * class. */
+
+ switch ($this->type) {
+
+ case self::IFD0:
+ case self::IFD1:
+ case self::EXIF:
+ case self::INTEROPERABILITY:
+
+ switch ($tag) {
+ case PelTag::DATE_TIME:
+ case PelTag::DATE_TIME_ORIGINAL:
+ case PelTag::DATE_TIME_DIGITIZED:
+ if ($format != PelFormat::ASCII)
+ throw new PelUnexpectedFormatException($this->type, $tag, $format,
+ PelFormat::ASCII);
+
+ if ($components != 20)
+ throw new PelWrongComponentCountException($this->type, $tag, $components, 20);
+
+ // TODO: handle timezones.
+ return new PelEntryTime($tag, $data->getBytes(0, -1), PelEntryTime::EXIF_STRING);
+
+ case PelTag::COPYRIGHT:
+ if ($format != PelFormat::ASCII)
+ throw new PelUnexpectedFormatException($this->type, $tag, $format,
+ PelFormat::ASCII);
+
+ $v = explode("\0", trim($data->getBytes(), ' '));
+ return new PelEntryCopyright($v[0], $v[1]);
+
+ case PelTag::EXIF_VERSION:
+ case PelTag::FLASH_PIX_VERSION:
+ case PelTag::INTEROPERABILITY_VERSION:
+ if ($format != PelFormat::UNDEFINED)
+ throw new PelUnexpectedFormatException($this->type, $tag, $format,
+ PelFormat::UNDEFINED);
+
+ return new PelEntryVersion($tag, $data->getBytes() / 100);
+
+ case PelTag::USER_COMMENT:
+ if ($format != PelFormat::UNDEFINED)
+ throw new PelUnexpectedFormatException($this->type, $tag, $format,
+ PelFormat::UNDEFINED);
+ if ($data->getSize() < 8) {
+ return new PelEntryUserComment();
+ } else {
+ return new PelEntryUserComment($data->getBytes(8),
+ rtrim($data->getBytes(0, 8)));
+ }
+
+ case PelTag::XP_TITLE:
+ case PelTag::XP_COMMENT:
+ case PelTag::XP_AUTHOR:
+ case PelTag::XP_KEYWORDS:
+ case PelTag::XP_SUBJECT:
+ if ($format != PelFormat::BYTE)
+ throw new PelUnexpectedFormatException($this->type, $tag, $format,
+ PelFormat::BYTE);
+
+ $v = '';
+ for ($i = 0; $i < $components; $i++) {
+ $b = $data->getByte($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. */
+ if ($b != 0)
+ $v .= chr($b);
+ }
+
+ return new PelEntryWindowsString($tag, $v);
+ }
+
+ case self::GPS:
+
+ default:
+ /* Then handle the basic formats. */
+ switch ($format) {
+ case PelFormat::BYTE:
+ $v = new PelEntryByte($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getByte($i));
+ return $v;
+
+ case PelFormat::SBYTE:
+ $v = new PelEntrySByte($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getSByte($i));
+ return $v;
+
+ case PelFormat::ASCII:
+ return new PelEntryAscii($tag, $data->getBytes(0, -1));
+
+ case PelFormat::SHORT:
+ $v = new PelEntryShort($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getShort($i*2));
+ return $v;
+
+ case PelFormat::SSHORT:
+ $v = new PelEntrySShort($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getSShort($i*2));
+ return $v;
+
+ case PelFormat::LONG:
+ $v = new PelEntryLong($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getLong($i*4));
+ return $v;
+
+ case PelFormat::SLONG:
+ $v = new PelEntrySLong($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getSLong($i*4));
+ return $v;
+
+ case PelFormat::RATIONAL:
+ $v = new PelEntryRational($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getRational($i*8));
+ return $v;
+
+ case PelFormat::SRATIONAL:
+ $v = new PelEntrySRational($tag);
+ for ($i = 0; $i < $components; $i++)
+ $v->addNumber($data->getSRational($i*8));
+ return $v;
+
+ case PelFormat::UNDEFINED:
+ return new PelEntryUndefined($tag, $data->getBytes());
+
+ default:
+ throw new PelException('Unsupported format: %s',
+ PelFormat::getName($format));
+ }
+ }
+ }
+
+
+
+
+ /**
+ * 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
+ * thumbnail data loaded.
+ *
+ * @param PelDataWindow the data from which the thumbnail will be
+ * extracted.
+ *
+ * @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
+ * available. */
+ 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()) {
+ Pel::maybeThrow(new PelIfdException('Thumbnail length %d bytes ' .
+ 'adjusted to %d bytes.',
+ $length,
+ $d->getSize() - $offset));
+ $length = $d->getSize() - $offset;
+ }
+
+ /* Now set the thumbnail normally. */
+ $this->setThumbnail($d->getClone($offset, $length));
+ }
+ }
+
+
+ /**
+ * Set thumbnail data.
+ *
+ * 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.
+ */
+ function setThumbnail(PelDataWindow $d) {
+ $size = $d->getSize();
+ /* Now move backwards until we find the EOI JPEG marker. */
+ while ($d->getByte($size - 2) != 0xFF ||
+ $d->getByte($size - 1) != PelJpegMarker::EOI) {
+ $size--;
+ }
+
+ if ($size != $d->getSize())
+ Pel::maybeThrow(new PelIfdException('Decrementing thumbnail size ' .
+ 'to %d bytes', $size));
+
+ $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}.
+ */
+ function getType() {
+ return $this->type;
+ }
+
+
+ /**
+ * 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).
+ *
+ * @param PelTag the tag.
+ *
+ * @return boolean true if the tag is considered valid in this IFD,
+ * false otherwise.
+ *
+ * @see getValidTags()
+ */
+ function isValidTag($tag) {
+ return $tag > 0xF000 || in_array($tag, $this->getValidTags());
+ }
+
+
+ /**
+ * Returns a list of valid tags for this IFD.
+ *
+ * @return array an array of {@link PelTag}s which are valid for
+ * this IFD.
+ */
+ function getValidTags() {
+ switch ($this->type) {
+ case PelIfd::IFD0:
+ case PelIfd::IFD1:
+ return array(PelTag::IMAGE_WIDTH,
+ PelTag::IMAGE_LENGTH,
+ PelTag::BITS_PER_SAMPLE,
+ PelTag::COMPRESSION,
+ PelTag::PHOTOMETRIC_INTERPRETATION,
+ PelTag::IMAGE_DESCRIPTION,
+ PelTag::MAKE,
+ PelTag::MODEL,
+ PelTag::STRIP_OFFSETS,
+ PelTag::ORIENTATION,
+ PelTag::SAMPLES_PER_PIXEL,
+ PelTag::ROWS_PER_STRIP,
+ PelTag::STRIP_BYTE_COUNTS,
+ PelTag::X_RESOLUTION,
+ PelTag::Y_RESOLUTION,
+ PelTag::PLANAR_CONFIGURATION,
+ PelTag::RESOLUTION_UNIT,
+ PelTag::TRANSFER_FUNCTION,
+ PelTag::SOFTWARE,
+ PelTag::DATE_TIME,
+ PelTag::ARTIST,
+ PelTag::WHITE_POINT,
+ PelTag::PRIMARY_CHROMATICITIES,
+ PelTag::JPEG_INTERCHANGE_FORMAT,
+ PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH,
+ PelTag::YCBCR_COEFFICIENTS,
+ PelTag::YCBCR_SUB_SAMPLING,
+ PelTag::YCBCR_POSITIONING,
+ PelTag::REFERENCE_BLACK_WHITE,
+ PelTag::COPYRIGHT,
+ PelTag::EXIF_IFD_POINTER,
+ PelTag::GPS_INFO_IFD_POINTER,
+ PelTag::PRINT_IM,
+ PelTag::XP_TITLE,
+ PelTag::XP_COMMENT,
+ PelTag::XP_AUTHOR,
+ PelTag::XP_KEYWORDS,
+ PelTag::XP_SUBJECT);
+
+ case PelIfd::EXIF:
+ return array(PelTag::EXPOSURE_TIME,
+ PelTag::FNUMBER,
+ PelTag::EXPOSURE_PROGRAM,
+ PelTag::SPECTRAL_SENSITIVITY,
+ PelTag::ISO_SPEED_RATINGS,
+ PelTag::OECF,
+ PelTag::EXIF_VERSION,
+ PelTag::DATE_TIME_ORIGINAL,
+ PelTag::DATE_TIME_DIGITIZED,
+ PelTag::COMPONENTS_CONFIGURATION,
+ PelTag::COMPRESSED_BITS_PER_PIXEL,
+ PelTag::SHUTTER_SPEED_VALUE,
+ PelTag::APERTURE_VALUE,
+ PelTag::BRIGHTNESS_VALUE,
+ PelTag::EXPOSURE_BIAS_VALUE,
+ PelTag::MAX_APERTURE_VALUE,
+ PelTag::SUBJECT_DISTANCE,
+ PelTag::METERING_MODE,
+ PelTag::LIGHT_SOURCE,
+ PelTag::FLASH,
+ PelTag::FOCAL_LENGTH,
+ PelTag::MAKER_NOTE,
+ PelTag::USER_COMMENT,
+ PelTag::SUB_SEC_TIME,
+ PelTag::SUB_SEC_TIME_ORIGINAL,
+ PelTag::SUB_SEC_TIME_DIGITIZED,
+ PelTag::FLASH_PIX_VERSION,
+ PelTag::COLOR_SPACE,
+ PelTag::PIXEL_X_DIMENSION,
+ PelTag::PIXEL_Y_DIMENSION,
+ PelTag::RELATED_SOUND_FILE,
+ PelTag::FLASH_ENERGY,
+ PelTag::SPATIAL_FREQUENCY_RESPONSE,
+ PelTag::FOCAL_PLANE_X_RESOLUTION,
+ PelTag::FOCAL_PLANE_Y_RESOLUTION,
+ PelTag::FOCAL_PLANE_RESOLUTION_UNIT,
+ PelTag::SUBJECT_LOCATION,
+ PelTag::EXPOSURE_INDEX,
+ PelTag::SENSING_METHOD,
+ PelTag::FILE_SOURCE,
+ PelTag::SCENE_TYPE,
+ PelTag::CFA_PATTERN,
+ PelTag::CUSTOM_RENDERED,
+ PelTag::EXPOSURE_MODE,
+ PelTag::WHITE_BALANCE,
+ PelTag::DIGITAL_ZOOM_RATIO,
+ PelTag::FOCAL_LENGTH_IN_35MM_FILM,
+ PelTag::SCENE_CAPTURE_TYPE,
+ PelTag::GAIN_CONTROL,
+ PelTag::CONTRAST,
+ PelTag::SATURATION,
+ PelTag::SHARPNESS,
+ PelTag::DEVICE_SETTING_DESCRIPTION,
+ PelTag::SUBJECT_DISTANCE_RANGE,
+ PelTag::IMAGE_UNIQUE_ID,
+ PelTag::INTEROPERABILITY_IFD_POINTER,
+ PelTag::GAMMA);
+
+ case PelIfd::GPS:
+ return array(PelTag::GPS_VERSION_ID,
+ PelTag::GPS_LATITUDE_REF,
+ PelTag::GPS_LATITUDE,
+ PelTag::GPS_LONGITUDE_REF,
+ PelTag::GPS_LONGITUDE,
+ PelTag::GPS_ALTITUDE_REF,
+ PelTag::GPS_ALTITUDE,
+ PelTag::GPS_TIME_STAMP,
+ PelTag::GPS_SATELLITES,
+ PelTag::GPS_STATUS,
+ PelTag::GPS_MEASURE_MODE,
+ PelTag::GPS_DOP,
+ PelTag::GPS_SPEED_REF,
+ PelTag::GPS_SPEED,
+ PelTag::GPS_TRACK_REF,
+ PelTag::GPS_TRACK,
+ PelTag::GPS_IMG_DIRECTION_REF,
+ PelTag::GPS_IMG_DIRECTION,
+ PelTag::GPS_MAP_DATUM,
+ PelTag::GPS_DEST_LATITUDE_REF,
+ PelTag::GPS_DEST_LATITUDE,
+ PelTag::GPS_DEST_LONGITUDE_REF,
+ PelTag::GPS_DEST_LONGITUDE,
+ PelTag::GPS_DEST_BEARING_REF,
+ PelTag::GPS_DEST_BEARING,
+ PelTag::GPS_DEST_DISTANCE_REF,
+ PelTag::GPS_DEST_DISTANCE,
+ PelTag::GPS_PROCESSING_METHOD,
+ PelTag::GPS_AREA_INFORMATION,
+ PelTag::GPS_DATE_STAMP,
+ PelTag::GPS_DIFFERENTIAL);
+
+ case PelIfd::INTEROPERABILITY:
+ return array(PelTag::INTEROPERABILITY_INDEX,
+ PelTag::INTEROPERABILITY_VERSION,
+ PelTag::RELATED_IMAGE_FILE_FORMAT,
+ PelTag::RELATED_IMAGE_WIDTH,
+ PelTag::RELATED_IMAGE_LENGTH);
+
+ /* TODO: Where do these tags belong?
+ PelTag::FILL_ORDER,
+ PelTag::DOCUMENT_NAME,
+ PelTag::TRANSFER_RANGE,
+ PelTag::JPEG_PROC,
+ PelTag::BATTERY_LEVEL,
+ PelTag::IPTC_NAA,
+ 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.
+ */
+ static function getTypeName($type) {
+ switch ($type) {
+ case self::IFD0:
+ return '0';
+ case self::IFD1:
+ return '1';
+ case self::EXIF:
+ return 'Exif';
+ case self::GPS:
+ return 'GPS';
+ case self::INTEROPERABILITY:
+ return 'Interoperability';
+ default:
+ throw new PelIfdException('Unknown IFD type: %d', $type);
+ }
+ }
+
+
+ /**
+ * Get the name of this directory.
+ *
+ * @return string the name of this directory.
+ */
+ function getName() {
+ return $this->getTypeName($this->type);
+ }
+
+
+ /**
+ * 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
+ * bug?
+ */
+ function addEntry(PelEntry $e) {
+ if ($this->isValidTag($e->getTag())) {
+ $e->setIfdType($this->type);
+ $this->entries[$e->getTag()] = $e;
+ } else {
+ throw new PelInvalidDataException("IFD %s cannot hold\n%s",
+ $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:
+ *
+ * <code>
+ * if (isset($ifd[PelTag::FNUMBER]))
+ * // ... do something with the F-number.
+ * </code>
+ *
+ * @param PelTag the offset to check.
+ *
+ * @return boolean whether the tag exists.
+ */
+ function offsetExists($tag) {
+ 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:
+ *
+ * <code>
+ * $entry = $ifd[PelTag::FNUMBER];
+ * </code>
+ *
+ * @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
+ * array entry.
+ *
+ * @return PelEntry the entry.
+ */
+ function offsetGet($tag) {
+ 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:
+ *
+ * <code>
+ * $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry;
+ * </code>
+ *
+ * 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.
+ */
+ function offsetSet($tag, $e) {
+ if ($e instanceof PelEntry) {
+ $tag = $e->getTag();
+ $this->entries[$tag] = $e;
+ } else {
+ throw new PelInvalidArgumentException('Argument "%s" must be a PelEntry.', $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:
+ *
+ * <code>
+ * unset($ifd[PelTag::EXPOSURE_BIAS_VALUE])
+ * </code>
+ *
+ * @param PelTag the offset to delete.
+ */
+ function offsetUnset($tag) {
+ unset($this->entries[$tag]);
+ }
+
+
+ /**
+ * Retrieve an entry.
+ *
+ * @param PelTag the tag identifying the entry.
+ *
+ * @return PelEntry the entry associated with the tag, or null if no
+ * such entry exists.
+ */
+ function getEntry($tag) {
+ if (isset($this->entries[$tag]))
+ return $this->entries[$tag];
+ else
+ return null;
+ }
+
+
+ /**
+ * 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.
+ *
+ * @see getEntry
+ * @see getIterator
+ */
+ function getEntries() {
+ return $this->entries;
+ }
+
+
+ /**
+ * Return an iterator for all entries contained in this IFD.
+ *
+ * Used with foreach as in
+ *
+ * <code>
+ * foreach ($ifd as $tag => $entry) {
+ * // $tag is now a PelTag and $entry is a PelEntry object.
+ * }
+ * </code>
+ *
+ * @return Iterator an iterator using the {@link PelTag tags} as
+ * keys and the entries as values.
+ */
+ function getIterator() {
+ 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
+ * returned.
+ *
+ * @todo Throw an exception instead when no data is available?
+ *
+ * @todo Return the $this->thumb_data object instead of the bytes?
+ */
+ function getThumbnailData() {
+ if ($this->thumb_data != null)
+ return $this->thumb_data->getBytes();
+ else
+ return '';
+ }
+
+
+ /**
+ * Make this directory point to a new directory.
+ *
+ * @param PelIfd the IFD that this directory will point to.
+ */
+ function setNextIfd(PelIfd $i) {
+ $this->next = $i;
+ }
+
+
+ /**
+ * 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.
+ */
+ function getNextIfd() {
+ return $this->next;
+ }
+
+
+ /**
+ * Check if this is the last IFD.
+ *
+ * @return boolean true if there are no following IFD, false
+ * otherwise.
+ */
+ function isLastIfd() {
+ return $this->next == null;
+ }
+
+
+ /**
+ * Add a sub-IFD.
+ *
+ * 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}.
+ */
+ function addSubIfd(PelIfd $sub) {
+ $this->sub[$sub->type] = $sub;
+ }
+
+
+ /**
+ * Return a sub IFD.
+ *
+ * @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.
+ */
+ function getSubIfd($type) {
+ if (isset($this->sub[$type]))
+ return $this->sub[$type];
+ else
+ return null;
+ }
+
+
+ /**
+ * Get all sub IFDs.
+ *
+ * @return array an associative array with (IFD-type, {@link
+ * PelIfd}) pairs.
+ */
+ function getSubIfds() {
+ return $this->sub;
+ }
+
+
+ /**
+ * 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
+ * offset given.
+ *
+ * @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}.
+ */
+ function getBytes($offset, $order) {
+ $bytes = '';
+ $extra_bytes = '';
+
+ Pel::debug('Bytes from IDF will start at offset %d within Exif data',
+ $offset);
+
+ $n = count($this->entries) + count($this->sub);
+ if ($this->thumb_data != null) {
+ /* We need two extra entries for the thumbnail offset and
+ * length. */
+ $n += 2;
+ }
+
+ $bytes .= PelConvert::shortToBytes($n, $order);
+
+ /* 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. */
+ $bytes .= PelConvert::shortToBytes($entry->getTag(), $order);
+ $bytes .= PelConvert::shortToBytes($entry->getFormat(), $order);
+ $bytes .= PelConvert::longToBytes($entry->getComponents(), $order);
+
+ /*
+ * Size? If bigger than 4 bytes, the actual data is not in
+ * the entry but somewhere else.
+ */
+ $data = $entry->getBytes($order);
+ $s = strlen($data);
+ if ($s > 4) {
+ Pel::debug('Data size %d too big, storing at offset %d instead.',
+ $s, $end);
+ $bytes .= PelConvert::longToBytes($end, $order);
+ $extra_bytes .= $data;
+ $end += $s;
+ } else {
+ Pel::debug('Data size %d fits.', $s);
+ /* Copy data directly, pad with NULL bytes as necessary to
+ * fill out the four bytes available.*/
+ $bytes .= $data . str_repeat(chr(0), 4 - $s);
+ }
+ }
+
+ 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.
+ $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH,
+ $order);
+ $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
+ $bytes .= PelConvert::longToBytes(1, $order);
+ $bytes .= PelConvert::longToBytes($this->thumb_data->getSize(),
+ $order);
+
+ $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT,
+ $order);
+ $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
+ $bytes .= PelConvert::longToBytes(1, $order);
+ $bytes .= PelConvert::longToBytes($end, $order);
+
+ $extra_bytes .= $this->thumb_data->getBytes();
+ $end += $this->thumb_data->getSize();
+ }
+
+
+ /* Find bytes from sub IFDs. */
+ $sub_bytes = '';
+ foreach ($this->sub as $type => $sub) {
+ if ($type == PelIfd::EXIF)
+ $tag = PelTag::EXIF_IFD_POINTER;
+ elseif ($type == PelIfd::GPS)
+ $tag = PelTag::GPS_INFO_IFD_POINTER;
+ elseif ($type == PelIfd::INTEROPERABILITY)
+ $tag = PelTag::INTEROPERABILITY_IFD_POINTER;
+
+ /* Make an aditional entry with the pointer. */
+ $bytes .= PelConvert::shortToBytes($tag, $order);
+ /* Next the format, which is always unsigned long. */
+ $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
+ /* There is only one component. */
+ $bytes .= PelConvert::longToBytes(1, $order);
+
+ $data = $sub->getBytes($end, $order);
+ $s = strlen($data);
+ $sub_bytes .= $data;
+
+ $bytes .= PelConvert::longToBytes($end, $order);
+ $end += $s;
+ }
+
+ /* Make link to next IFD, if any*/
+ if ($this->isLastIFD()) {
+ $link = 0;
+ } else {
+ $link = $end;
+ }
+
+ Pel::debug('Link to next IFD: %d', $link);
+
+ $bytes .= PelConvert::longtoBytes($link, $order);
+
+ $bytes .= $extra_bytes . $sub_bytes;
+
+ if (!$this->isLastIfd())
+ $bytes .= $this->next->getBytes($end, $order);
+
+ return $bytes;
+ }
+
+
+ /**
+ * Turn this directory into text.
+ *
+ * @return string information about the directory, mainly for
+ * debugging.
+ */
+ function __toString() {
+ $str = Pel::fmt("Dumping IFD %s with %d entries...\n",
+ $this->getName(), count($this->entries));
+
+ 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();
+
+ if ($this->next != null)
+ $str .= $this->next->__toString();
+
+ return $str;
+ }
+
+
+}
+