diff --git a/modules/autorotate/helpers/autorotate.php b/modules/autorotate/helpers/autorotate.php
new file mode 100644
index 00000000..55cf2e24
--- /dev/null
+++ b/modules/autorotate/helpers/autorotate.php
@@ -0,0 +1,76 @@
+is_photo() && $item->mime_type == "image/jpeg") {
+ $exif_data = exif_read_data($item->file_path());
+
+ if (isset($exif_data["Orientation"]) && $exif_data["Orientation"]) {
+ $orientation = $exif_data["Orientation"];
+ $degrees = 0;
+
+ if ($orientation == '3') {
+ $degrees = 180;
+ }
+ else if ($orientation == '8') {
+ $degrees = -90;
+ }
+ else if ($orientation == '6') {
+ $degrees = 90;
+ }
+
+ if($degrees) {
+ $tmpfile = tempnam(TMPPATH, "rotate");
+ gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $degrees));
+
+ // Update EXIF info
+ $data = new PelDataWindow(file_get_contents($tmpfile));
+ if (PelJpeg::isValid($data)) {
+ $jpeg = $file = new PelJpeg();
+ $jpeg->load($data);
+ $exif = $jpeg->getExif();
+
+ if($exif !== null) {
+ $tiff = $exif->getTiff();
+ $ifd0 = $tiff->getIfd();
+ $orientation = $ifd0->getEntry(PelTag::ORIENTATION);
+ $orientation->setValue(1);
+ file_put_contents($tmpfile, $file->getBytes());
+ }
+ }
+
+ $item->set_data_file($tmpfile);
+ $item->save();
+ unlink($tmpfile);
+ }
+ }
+ }
+ return;
+ }
+}
diff --git a/modules/autorotate/helpers/autorotate_event.php b/modules/autorotate/helpers/autorotate_event.php
new file mode 100644
index 00000000..c2fd04b3
--- /dev/null
+++ b/modules/autorotate/helpers/autorotate_event.php
@@ -0,0 +1,32 @@
+getMessage() . "\n" . $e->getTraceAsString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/autorotate/helpers/autorotate_installer.php b/modules/autorotate/helpers/autorotate_installer.php
new file mode 100644
index 00000000..a357ac5c
--- /dev/null
+++ b/modules/autorotate/helpers/autorotate_installer.php
@@ -0,0 +1,45 @@
+
+ * @package PEL
+ */
+class Pel
+{
+
+ /**
+ * Flag that controls if dgettext can be used.
+ * Is set to true or fals at the first access
+ *
+ * @var boolean|NULL
+ */
+ private static $hasdgetext = null;
+
+ /**
+ * Flag for controlling debug information.
+ *
+ * The methods producing debug information ({@link debug()} and
+ * {@link warning()}) will only output something if this variable is
+ * set to true.
+ *
+ * @var boolean
+ */
+ private static $debug = false;
+
+ /**
+ * Flag for strictness of parsing.
+ *
+ * If this variable is set to true, then most errors while loading
+ * images will result in exceptions being thrown. Otherwise a
+ * warning will be emitted (using {@link Pel::warning}) and the
+ * exceptions will be appended to {@link Pel::$exceptions}.
+ *
+ * Some errors will still be fatal and result in thrown exceptions,
+ * but an effort will be made to skip over as much garbage as
+ * possible.
+ *
+ * @var boolean
+ */
+ private static $strict = false;
+
+ /**
+ * Stored exceptions.
+ *
+ * When {@link Pel::$strict} is set to false exceptions will be
+ * accumulated here instead of being thrown.
+ */
+ private static $exceptions = array();
+
+ /**
+ * Quality setting for encoding JPEG images.
+ *
+ * This controls the quality used then PHP image resources are
+ * encoded into JPEG images. This happens when you create a
+ * {@link PelJpeg} object based on an image resource.
+ *
+ * The default is 75 for average quality images, but you can change
+ * this to an integer between 0 and 100.
+ *
+ * @var int
+ */
+ private static $quality = 75;
+
+ /**
+ * Set the JPEG encoding quality.
+ *
+ * @param int $quality
+ * an integer between 0 and 100 with 75 being
+ * average quality and 95 very good quality.
+ */
+ public static function setJPEGQuality($quality)
+ {
+ self::$quality = $quality;
+ }
+
+ /**
+ * Get current setting for JPEG encoding quality.
+ *
+ * @return int the quality.
+ */
+ public static function getJPEGQuality()
+ {
+ return self::$quality;
+ }
+
+ /**
+ * Return list of stored exceptions.
+ *
+ * When PEL is parsing in non-strict mode, it will store most
+ * exceptions instead of throwing them. Use this method to get hold
+ * of them when a call returns.
+ *
+ * Code for using this could look like this:
+ *
+ *
+ * Pel::setStrictParsing(true);
+ * Pel::clearExceptions();
+ *
+ * $jpeg = new PelJpeg($file);
+ *
+ * // Check for exceptions.
+ * foreach (Pel::getExceptions() as $e) {
+ * printf("Exception: %s\n", $e->getMessage());
+ * if ($e instanceof PelEntryException) {
+ * // Warn about entries that couldn't be loaded.
+ * printf("Warning: Problem with %s.\n",
+ * PelTag::getName($e->getType(), $e->getTag()));
+ * }
+ * }
+ *
+ *
+ * This gives applications total control over the amount of error
+ * messages shown and (hopefully) provides the necessary information
+ * for proper error recovery.
+ *
+ * @return array the exceptions.
+ */
+ public static function getExceptions()
+ {
+ return self::$exceptions;
+ }
+
+ /**
+ * Clear list of stored exceptions.
+ *
+ * Use this function before a call to some method if you intend to
+ * check for exceptions afterwards.
+ */
+ public static function clearExceptions()
+ {
+ self::$exceptions = array();
+ }
+
+ /**
+ * Conditionally throw an exception.
+ *
+ * This method will throw the passed exception when strict parsing
+ * in effect (see {@link setStrictParsing()}). Otherwise the
+ * exception is stored (it can be accessed with {@link
+ * getExceptions()}) and a warning is issued (with {@link
+ * Pel::warning}).
+ *
+ * @param PelException $e
+ * the exceptions.
+ */
+ public static function maybeThrow(PelException $e)
+ {
+ if (self::$strict) {
+ throw $e;
+ } else {
+ self::$exceptions[] = $e;
+ self::warning('%s (%s:%s)', $e->getMessage(), basename($e->getFile()), $e->getLine());
+ }
+ }
+
+ /**
+ * Enable/disable strict parsing.
+ *
+ * If strict parsing is enabled, then most errors while loading
+ * images will result in exceptions being thrown. Otherwise a
+ * warning will be emitted (using {@link Pel::warning}) and the
+ * exceptions will be stored for later use via {@link
+ * getExceptions()}.
+ *
+ * Some errors will still be fatal and result in thrown exceptions,
+ * but an effort will be made to skip over as much garbage as
+ * possible.
+ *
+ * @param boolean $flag
+ * use true to enable strict parsing, false to
+ * diable.
+ */
+ public static function setStrictParsing($flag)
+ {
+ self::$strict = $flag;
+ }
+
+ /**
+ * Get current setting for strict parsing.
+ *
+ * @return boolean true if strict parsing is in effect, false
+ * otherwise.
+ */
+ public static function getStrictParsing()
+ {
+ return self::$strict;
+ }
+
+ /**
+ * Enable/disable debugging output.
+ *
+ * @param boolean $flag
+ * use true to enable debug output, false to
+ * diable.
+ */
+ public static function setDebug($flag)
+ {
+ self::$debug = $flag;
+ }
+
+ /**
+ * Get current setting for debug output.
+ *
+ * @return boolean true if debug is enabled, false otherwise.
+ */
+ public static function getDebug()
+ {
+ return self::$debug;
+ }
+
+ /**
+ * Conditionally output debug information.
+ *
+ * This method works just like printf() except that it always
+ * terminates the output with a newline, and that it only outputs
+ * something if the {@link Pel::$debug} is true.
+ *
+ * @param string $format
+ * the format string.
+ *
+ * @param mixed ...$args
+ * any number of arguments can be given. The
+ * arguments will be available for the format string as usual with
+ * sprintf().
+ */
+ public static function debug($format)
+ {
+ if (self::$debug) {
+ $args = func_get_args();
+ $str = array_shift($args);
+ vprintf($str . "\n", $args);
+ }
+ }
+
+ /**
+ * Conditionally output a warning.
+ *
+ * This method works just like printf() except that it prepends the
+ * output with the string 'Warning: ', terminates the output with a
+ * newline, and that it only outputs something if the PEL_DEBUG
+ * defined to some true value.
+ *
+ * @param string $format
+ * the format string.
+ *
+ * @param mixed ...$args
+ * any number of arguments can be given. The
+ * arguments will be available for the format string as usual with
+ * sprintf().
+ */
+ public static function warning($format)
+ {
+ if (self::$debug) {
+ $args = func_get_args();
+ $str = array_shift($args);
+ vprintf('Warning: ' . $str . "\n", $args);
+ }
+ }
+
+ /**
+ * Translate a string.
+ *
+ * This static function will use Gettext to translate a string. By
+ * always using this function for static string one is assured that
+ * the translation will be taken from the correct text domain.
+ * Dynamic strings should be passed to {@link fmt} instead.
+ *
+ * @param string $str
+ * the string that should be translated.
+ *
+ * @return string the translated string, or the original string if
+ * no translation could be found.
+ */
+ public static function tra($str)
+ {
+ return self::dgettextWrapper('pel', $str);
+ }
+
+ /**
+ * Translate and format a string.
+ *
+ * This static function will first use Gettext to translate a format
+ * string, which will then have access to any extra arguments. By
+ * always using this function for dynamic string one is assured that
+ * the translation will be taken from the correct text domain. If
+ * the string is static, use {@link tra} instead as it will be
+ * faster.
+ *
+ * @param string $format
+ * the format string. This will be translated
+ * before being used as a format string.
+ *
+ * @param mixed ...$args
+ * any number of arguments can be given. The
+ * arguments will be available for the format string as usual with
+ * sprintf().
+ *
+ * @return string the translated string, or the original string if
+ * no translation could be found.
+ */
+ public static function fmt($format)
+ {
+ $args = func_get_args();
+ $str = array_shift($args);
+ return vsprintf(self::dgettextWrapper('pel', $str), $args);
+ }
+
+ /**
+ * Warapper for dgettext.
+ * The untranslated stub will be return in the case that dgettext is not available.
+ *
+ * @param string $domain
+ * @param string $str
+ * @return string
+ */
+ private static function dgettextWrapper($domain, $str)
+ {
+ if (self::$hasdgetext === null) {
+ self::$hasdgetext = function_exists('dgettext');
+ if (self::$hasdgetext === true) {
+ bindtextdomain('pel', __DIR__ . '/locale');
+ }
+ }
+ if (self::$hasdgetext) {
+ return dgettext($domain, $str);
+ } else {
+ return $str;
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelConvert.php b/modules/autorotate/lib/pel-master/src/PelConvert.php
new file mode 100644
index 00000000..474c48a6
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelConvert.php
@@ -0,0 +1,418 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Conversion functions to and from bytes and integers.
+ *
+ * The functions found in this class are used to convert bytes into
+ * integers of several sizes ({@link bytesToShort}, {@link
+ * bytesToLong}, and {@link bytesToRational}) and convert integers of
+ * several sizes into bytes ({@link shortToBytes} and {@link
+ * longToBytes}).
+ *
+ * All the methods are static and they all rely on an argument that
+ * specifies the byte order to be used, this must be one of the class
+ * constants {@link LITTLE_ENDIAN} or {@link BIG_ENDIAN}. These
+ * constants will be referred to as the pseudo type PelByteOrder
+ * throughout the documentation.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelConvert
+{
+
+ /**
+ * Little-endian (Intel) byte order.
+ *
+ * Data stored in little-endian byte order store the least
+ * significant byte first, so the number 0x12345678 becomes 0x78
+ * 0x56 0x34 0x12 when stored with little-endian byte order.
+ */
+ const LITTLE_ENDIAN = true;
+
+ /**
+ * Big-endian (Motorola) byte order.
+ *
+ * Data stored in big-endian byte order store the most significant
+ * byte first, so the number 0x12345678 becomes 0x12 0x34 0x56 0x78
+ * when stored with big-endian byte order.
+ */
+ const BIG_ENDIAN = false;
+
+ /**
+ * Convert an unsigned short into two bytes.
+ *
+ * @param integer $value
+ * the unsigned short that will be converted. The lower
+ * two bytes will be extracted regardless of the actual size passed.
+ *
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ *
+ * @return string the bytes representing the unsigned short.
+ */
+ public static function shortToBytes($value, $endian)
+ {
+ if ($endian == self::LITTLE_ENDIAN) {
+ return chr($value) . chr($value >> 8);
+ } else {
+ return chr($value >> 8) . chr($value);
+ }
+ }
+
+ /**
+ * Convert a signed short into two bytes.
+ *
+ * @param integer $value
+ * the signed short that will be converted. The lower
+ * two bytes will be extracted regardless of the actual size passed.
+ *
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ *
+ * @return string the bytes representing the signed short.
+ */
+ public static function sShortToBytes($value, $endian)
+ {
+ /*
+ * We can just use shortToBytes, since signed shorts fits well
+ * within the 32 bit signed integers used in PHP.
+ */
+ return self::shortToBytes($value, $endian);
+ }
+
+ /**
+ * Convert an unsigned long into four bytes.
+ *
+ * Because PHP limits the size of integers to 32 bit signed, one
+ * cannot really have an unsigned integer in PHP. But integers
+ * larger than 2^31-1 will be promoted to 64 bit signed floating
+ * point numbers, and so such large numbers can be handled too.
+ *
+ * @param integer $value
+ * the unsigned long that will be converted. The
+ * argument will be treated as an unsigned 32 bit integer and the
+ * lower four bytes will be extracted. Treating the argument as an
+ * unsigned integer means that the absolute value will be used. Use
+ * {@link sLongToBytes} to convert signed integers.
+ *
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ *
+ * @return string the bytes representing the unsigned long.
+ */
+ public static function longToBytes($value, $endian)
+ {
+ /*
+ * We cannot convert the number to bytes in the normal way (using
+ * shifts and modulo calculations) because the PHP operator >> and
+ * function chr() clip their arguments to 2^31-1, which is the
+ * largest signed integer known to PHP. But luckily base_convert
+ * handles such big numbers.
+ */
+ $hex = str_pad(base_convert($value, 10, 16), 8, '0', STR_PAD_LEFT);
+ if ($endian == self::LITTLE_ENDIAN) {
+ return (chr(hexdec($hex{6} . $hex{7})) . chr(hexdec($hex{4} . $hex{5})) . chr(hexdec($hex{2} . $hex{3})) .
+ chr(hexdec($hex{0} . $hex{1})));
+ } else {
+ return (chr(hexdec($hex{0} . $hex{1})) . chr(hexdec($hex{2} . $hex{3})) . chr(hexdec($hex{4} . $hex{5})) .
+ chr(hexdec($hex{6} . $hex{7})));
+ }
+ }
+
+ /**
+ * Convert a signed long into four bytes.
+ *
+ * @param integer $value
+ * the signed long that will be converted. The argument
+ * will be treated as a signed 32 bit integer, from which the lower
+ * four bytes will be extracted.
+ *
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ *
+ * @return string the bytes representing the signed long.
+ */
+ public static function sLongToBytes($value, $endian)
+ {
+ /*
+ * We can convert the number into bytes in the normal way using
+ * shifts and modulo calculations here (in contrast with
+ * longToBytes) because PHP automatically handles 32 bit signed
+ * integers for us.
+ */
+ if ($endian == self::LITTLE_ENDIAN) {
+ return (chr($value) . chr($value >> 8) . chr($value >> 16) . chr($value >> 24));
+ } else {
+ return (chr($value >> 24) . chr($value >> 16) . chr($value >> 8) . chr($value));
+ }
+ }
+
+ /**
+ * Extract an unsigned byte from a string of bytes.
+ *
+ * @param string $bytes
+ * the bytes.
+ *
+ * @param integer $offset
+ * The byte found at the offset will be
+ * returned as an integer. The must be at least one byte available
+ * at offset.
+ *
+ * @return integer $offset the unsigned byte found at offset, e.g., an integer
+ * in the range 0 to 255.
+ */
+ public static function bytesToByte($bytes, $offset)
+ {
+ return ord($bytes{$offset});
+ }
+
+ /**
+ * Extract a signed byte from bytes.
+ *
+ * @param string $bytes
+ * the bytes.
+ *
+ * @param integer $offset
+ * the offset. The byte found at the offset will be
+ * returned as an integer. The must be at least one byte available
+ * at offset.
+ *
+ * @return integer the signed byte found at offset, e.g., an integer in
+ * the range -128 to 127.
+ */
+ public static function bytesToSByte($bytes, $offset)
+ {
+ $n = self::bytesToByte($bytes, $offset);
+ if ($n > 127) {
+ return $n - 256;
+ } else {
+ return $n;
+ }
+ }
+
+ /**
+ * Extract an unsigned short from bytes.
+ *
+ * @param string $bytes
+ * the bytes.
+ *
+ * @param integer $offset
+ * the offset. The short found at the offset will be
+ * returned as an integer. There must be at least two bytes
+ * available beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ * @return integer the unsigned short found at offset, e.g., an integer
+ * in the range 0 to 65535.
+ *
+ */
+ public static function bytesToShort($bytes, $offset, $endian)
+ {
+ if ($endian == self::LITTLE_ENDIAN) {
+ return (ord($bytes{$offset + 1}) * 256 + ord($bytes{$offset}));
+ } else {
+ return (ord($bytes{$offset}) * 256 + ord($bytes{$offset + 1}));
+ }
+ }
+
+ /**
+ * Extract a signed short from bytes.
+ *
+ * @param string $bytes
+ *
+ * @param integer $offset
+ * The short found at offset will be returned
+ * as an integer. There must be at least two bytes available
+ * beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ * @return integer the signed byte found at offset, e.g., an integer in
+ * the range -32768 to 32767.
+ *
+ */
+ public static function bytesToSShort($bytes, $offset, $endian)
+ {
+ $n = self::bytesToShort($bytes, $offset, $endian);
+ if ($n > 32767) {
+ return $n - 65536;
+ } else {
+ return $n;
+ }
+ }
+
+ /**
+ * Extract an unsigned long from bytes.
+ *
+ * @param string $bytes
+ *
+ * @param integer $offset
+ * The long found at offset will be returned
+ * as an integer. There must be at least four bytes available
+ * beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ * @return integer the unsigned long found at offset, e.g., an integer
+ * in the range 0 to 4294967295.
+ *
+ */
+ public static function bytesToLong($bytes, $offset, $endian)
+ {
+ if ($endian == self::LITTLE_ENDIAN) {
+ return (ord($bytes{$offset + 3}) * 16777216 + ord($bytes{$offset + 2}) * 65536 +
+ ord($bytes{$offset + 1}) * 256 + ord($bytes{$offset}));
+ } else {
+ return (ord($bytes{$offset}) * 16777216 + ord($bytes{$offset + 1}) * 65536 + ord($bytes{$offset + 2}) * 256 +
+ ord($bytes{$offset + 3}));
+ }
+ }
+
+ /**
+ * Extract a signed long from bytes.
+ *
+ * @param string $bytes
+ *
+ * @param integer $offset
+ * The long found at offset will be returned
+ * as an integer. There must be at least four bytes available
+ * beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}. *
+ * @return integer the signed long found at offset, e.g., an integer in
+ * the range -2147483648 to 2147483647.
+ *
+ */
+ public static function bytesToSLong($bytes, $offset, $endian)
+ {
+ $n = self::bytesToLong($bytes, $offset, $endian);
+ if ($n > 2147483647) {
+ return $n - 4294967296;
+ } else {
+ return $n;
+ }
+ }
+
+ /**
+ * Extract an unsigned rational from bytes.
+ *
+ * @param string $bytes
+ *
+ * @param integer $offset
+ * The rational found at offset will be
+ * returned as an array. There must be at least eight bytes
+ * available beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}. *
+ * @return array the unsigned rational found at offset, e.g., an
+ * array with two integers in the range 0 to 4294967295.
+ *
+ */
+ public static function bytesToRational($bytes, $offset, $endian)
+ {
+ return array(
+ self::bytesToLong($bytes, $offset, $endian),
+ self::bytesToLong($bytes, $offset + 4, $endian)
+ );
+ }
+
+ /**
+ * Extract a signed rational from bytes.
+ *
+ * @param string $bytes
+ *
+ * @param integer $offset
+ * The rational found at offset will be
+ * returned as an array. There must be at least eight bytes
+ * available beginning at the offset given.
+ * @param integer $endian
+ * one of {@link LITTLE_ENDIAN} and {@link
+ * BIG_ENDIAN}.
+ * @return array the signed rational found at offset, e.g., an array
+ * with two integers in the range -2147483648 to 2147483647.
+ *
+ */
+ public static function bytesToSRational($bytes, $offset, $endian)
+ {
+ return array(
+ self::bytesToSLong($bytes, $offset, $endian),
+ self::bytesToSLong($bytes, $offset + 4, $endian)
+ );
+ }
+
+ /**
+ * Format bytes for dumping.
+ *
+ * This method is for debug output, it will format a string as a
+ * hexadecimal dump suitable for display on a terminal. The output
+ * is printed directly to standard out.
+ *
+ * @param string $bytes
+ * the bytes that will be dumped.
+ *
+ * @param integer $max
+ * the maximum number of bytes to dump. If this is left
+ * out (or left to the default of 0), then the entire string will be
+ * dumped.
+ * @return void
+ */
+ public static function bytesToDump($bytes, $max = 0)
+ {
+ $s = strlen($bytes);
+
+ if ($max > 0) {
+ $s = min($max, $s);
+ }
+ $line = 24;
+
+ for ($i = 0; $i < $s; $i ++) {
+ printf('%02X ', ord($bytes{$i}));
+
+ if (($i + 1) % $line == 0) {
+ print("\n");
+ }
+ }
+ print("\n");
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelDataWindow.php b/modules/autorotate/lib/pel-master/src/PelDataWindow.php
new file mode 100644
index 00000000..b1d10e5f
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelDataWindow.php
@@ -0,0 +1,567 @@
+data = $data;
+ } elseif (is_resource($data) && get_resource_type($data) == 'gd') {
+ /*
+ * The ImageJpeg() function insists on printing the bytes
+ * instead of returning them in a more civil way as a string, so
+ * we have to buffer the output...
+ */
+ ob_start();
+ ImageJpeg($data, null, Pel::getJPEGQuality());
+ $this->data = ob_get_clean();
+ } else {
+ throw new PelInvalidArgumentException('Bad type for $data: %s', gettype($data));
+ }
+
+ $this->order = $endianess;
+ $this->size = strlen($this->data);
+ }
+
+ /**
+ * Get the size of the data window.
+ *
+ * @return integer the number of bytes covered by the window. The
+ * allowed offsets go from 0 up to this number minus one.
+ *
+ * @see getBytes()
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * Change the byte order of the data.
+ *
+ * @param integer $order
+ * the new byte order. This must be either
+ * {@link PelConvert::LITTLE_ENDIAN} or {@link
+ * PelConvert::BIG_ENDIAN}.
+ */
+ public function setByteOrder($order)
+ {
+ $this->order = $order;
+ }
+
+ /**
+ * Get the currently used byte order.
+ *
+ * @return integer this will be either {@link
+ * PelConvert::LITTLE_ENDIAN} or {@link PelConvert::BIG_ENDIAN}.
+ */
+ public function getByteOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Move the start of the window forward.
+ *
+ * @param integer $start
+ * the new start of the window. All new offsets will be
+ * calculated from this new start offset, and the size of the window
+ * will shrink to keep the end of the window in place.
+ */
+ public function setWindowStart($start)
+ {
+ if ($start < 0 || $start > $this->size) {
+ throw new PelDataWindowWindowException(
+ 'Window [%d, %d] does ' . 'not fit in window [0, %d]',
+ $start,
+ $this->size,
+ $this->size);
+ }
+ $this->start += $start;
+ $this->size -= $start;
+ }
+
+ /**
+ * Adjust the size of the window.
+ * The size can only be made smaller.
+ *
+ * @param integer $size
+ * the desired size of the window. If the argument is
+ * negative, the window will be shrunk by the argument.
+ */
+ public function setWindowSize($size)
+ {
+ if ($size < 0) {
+ $size += $this->size;
+ }
+ if ($size < 0 || $size > $this->size) {
+ throw new PelDataWindowWindowException(
+ 'Window [0, %d] ' . 'does not fit in window [0, %d]',
+ $size,
+ $this->size);
+ }
+ $this->size = $size;
+ }
+
+ /**
+ * Make a new data window with the same data as the this window.
+ *
+ * @param integer|NULL $start
+ * if an integer is supplied, then it will be the start
+ * of the window in the clone. If left unspecified, then the clone
+ * will inherit the start from this object.
+ *
+ * @param integer|NULL $size
+ * if an integer is supplied, then it will be the size
+ * of the window in the clone. If left unspecified, then the clone
+ * will inherit the size from this object.
+ *
+ * @return PelDataWindow a new window that operates on the same data
+ * as this window, but (optionally) with a smaller window size.
+ */
+ public function getClone($start = null, $size = null)
+ {
+ $c = clone $this;
+
+ if (is_int($start)) {
+ $c->setWindowStart($start);
+ }
+ if (is_int($size)) {
+ $c->setWindowSize($size);
+ }
+ return $c;
+ }
+
+ /**
+ * Validate an offset against the current window.
+ *
+ * @param integer $offset
+ * the offset to be validated. If the offset is negative
+ * or if it is greater than or equal to the current window size,
+ * then a {@link PelDataWindowOffsetException} is thrown.
+ *
+ * @return void if the offset is valid nothing is returned, if it is
+ * invalid a new {@link PelDataWindowOffsetException} is thrown.
+ * @throws PelDataWindowOffsetException
+ */
+ private function validateOffset($offset)
+ {
+ if ($offset < 0 || $offset >= $this->size) {
+ throw new PelDataWindowOffsetException('Offset %d not within [%d, %d]', $offset, 0, $this->size - 1);
+ }
+ }
+
+ /**
+ * Return some or all bytes visible in the window.
+ *
+ * This method works just like the standard {@link substr()}
+ * function in PHP with the exception that it works within the
+ * window of accessible bytes and does strict range checking.
+ *
+ * @param integer|NULL $start
+ * the offset to the first byte returned. If a negative
+ * number is given, then the counting will be from the end of the
+ * window. Invalid offsets will result in a {@link
+ * PelDataWindowOffsetException} being thrown.
+ *
+ * @param integer|NUL $size
+ * the size of the sub-window. If a negative number is
+ * given, then that many bytes will be omitted from the result.
+ *
+ * @return string a subset of the bytes in the window. This will
+ * always return no more than {@link getSize()} bytes.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getBytes($start = null, $size = null)
+ {
+ if (is_int($start)) {
+ if ($start < 0) {
+ $start += $this->size;
+ }
+
+ $this->validateOffset($start);
+ } else {
+ $start = 0;
+ }
+
+ if (is_int($size)) {
+ if ($size <= 0) {
+ $size += $this->size - $start;
+ }
+
+ $this->validateOffset($start + $size);
+ } else {
+ $size = $this->size - $start;
+ }
+
+ return substr($this->data, $this->start + $start, $size);
+ }
+
+ /**
+ * Return an unsigned byte from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first byte in the current allowed window. The last
+ * valid offset is equal to {@link getSize()}-1. Invalid offsets
+ * will result in a {@link PelDataWindowOffsetException} being
+ * thrown.
+ *
+ * @return integer the unsigned byte found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getByte($offset = 0)
+ {
+ /*
+ * Validate the offset --- this throws an exception if offset is
+ * out of range.
+ */
+ $this->validateOffset($offset);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return an unsigned byte. */
+ return PelConvert::bytesToByte($this->data, $offset);
+ }
+
+ /**
+ * Return a signed byte from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first byte in the current allowed window. The last
+ * valid offset is equal to {@link getSize()}-1. Invalid offsets
+ * will result in a {@link PelDataWindowOffsetException} being
+ * thrown.
+ *
+ * @return integer the signed byte found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getSByte($offset = 0)
+ {
+ /*
+ * Validate the offset --- this throws an exception if offset is
+ * out of range.
+ */
+ $this->validateOffset($offset);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return a signed byte. */
+ return PelConvert::bytesToSByte($this->data, $offset);
+ }
+
+ /**
+ * Return an unsigned short read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first short available in the current allowed window.
+ * The last valid offset is equal to {@link getSize()}-2. Invalid
+ * offsets will result in a {@link PelDataWindowOffsetException}
+ * being thrown.
+ *
+ * @return integer the unsigned short found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getShort($offset = 0)
+ {
+ /*
+ * Validate the offset+1 to see if we can safely get two bytes ---
+ * this throws an exception if offset is out of range.
+ */
+ $this->validateOffset($offset);
+ $this->validateOffset($offset + 1);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return an unsigned short. */
+ return PelConvert::bytesToShort($this->data, $offset, $this->order);
+ }
+
+ /**
+ * Return a signed short read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first short available in the current allowed window.
+ * The last valid offset is equal to {@link getSize()}-2. Invalid
+ * offsets will result in a {@link PelDataWindowOffsetException}
+ * being thrown.
+ *
+ * @return integer the signed short found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getSShort($offset = 0)
+ {
+ /*
+ * Validate the offset+1 to see if we can safely get two bytes ---
+ * this throws an exception if offset is out of range.
+ */
+ $this->validateOffset($offset);
+ $this->validateOffset($offset + 1);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return a signed short. */
+ return PelConvert::bytesToSShort($this->data, $offset, $this->order);
+ }
+
+ /**
+ * Return an unsigned long read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first long available in the current allowed window.
+ * The last valid offset is equal to {@link getSize()}-4. Invalid
+ * offsets will result in a {@link PelDataWindowOffsetException}
+ * being thrown.
+ *
+ * @return integer the unsigned long found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getLong($offset = 0)
+ {
+ /*
+ * Validate the offset+3 to see if we can safely get four bytes
+ * --- this throws an exception if offset is out of range.
+ */
+ $this->validateOffset($offset);
+ $this->validateOffset($offset + 3);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return an unsigned long. */
+ return PelConvert::bytesToLong($this->data, $offset, $this->order);
+ }
+
+ /**
+ * Return a signed long read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first long available in the current allowed window.
+ * The last valid offset is equal to {@link getSize()}-4. Invalid
+ * offsets will result in a {@link PelDataWindowOffsetException}
+ * being thrown.
+ *
+ * @return integer the signed long found at offset.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getSLong($offset = 0)
+ {
+ /*
+ * Validate the offset+3 to see if we can safely get four bytes
+ * --- this throws an exception if offset is out of range.
+ */
+ $this->validateOffset($offset);
+ $this->validateOffset($offset + 3);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Return a signed long. */
+ return PelConvert::bytesToSLong($this->data, $offset, $this->order);
+ }
+
+ /**
+ * Return an unsigned rational read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first rational available in the current allowed
+ * window. The last valid offset is equal to {@link getSize()}-8.
+ * Invalid offsets will result in a {@link
+ * PelDataWindowOffsetException} being thrown.
+ *
+ * @return array the unsigned rational found at offset. A rational
+ * number is represented as an array of two numbers: the enumerator
+ * and denominator. Both of these numbers will be unsigned longs.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getRational($offset = 0)
+ {
+ return array(
+ $this->getLong($offset),
+ $this->getLong($offset + 4)
+ );
+ }
+
+ /**
+ * Return a signed rational read from the data.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will
+ * return the first rational available in the current allowed
+ * window. The last valid offset is equal to {@link getSize()}-8.
+ * Invalid offsets will result in a {@link
+ * PelDataWindowOffsetException} being thrown.
+ *
+ * @return array the signed rational found at offset. A rational
+ * number is represented as an array of two numbers: the enumerator
+ * and denominator. Both of these numbers will be signed longs.
+ * @throws PelDataWindowOffsetException
+ */
+ public function getSRational($offset = 0)
+ {
+ return array(
+ $this->getSLong($offset),
+ $this->getSLong($offset + 4)
+ );
+ }
+
+ /**
+ * String comparison on substrings.
+ *
+ * @param integer $offset
+ * the offset into the data. An offset of zero will make
+ * the comparison start with the very first byte available in the
+ * window. The last valid offset is equal to {@link getSize()}
+ * minus the length of the string. If the string is too long, then
+ * a {@link PelDataWindowOffsetException} will be thrown.
+ *
+ * @param string $str
+ * the string to compare with.
+ *
+ * @return boolean true if the string given matches the data in the
+ * window, at the specified offset, false otherwise. The comparison
+ * will stop as soon as a mismatch if found.
+ * @throws PelDataWindowOffsetException
+ */
+ public function strcmp($offset, $str)
+ {
+ /*
+ * Validate the offset of the final character we might have to
+ * check.
+ */
+ $s = strlen($str);
+ $this->validateOffset($offset);
+ $this->validateOffset($offset + $s - 1);
+
+ /* Translate the offset into an offset into the data. */
+ $offset += $this->start;
+
+ /* Check each character, return as soon as the answer is known. */
+ for ($i = 0; $i < $s; $i ++) {
+ if ($this->data{$offset + $i} != $str{$i}) {
+ return false;
+ }
+ }
+
+ /* All characters matches each other, return true. */
+ return true;
+ }
+
+ /**
+ * Return a string representation of the data window.
+ *
+ * @return string a description of the window with information about
+ * the number of bytes accessible, the total number of bytes, and
+ * the window start and stop.
+ */
+ public function __toString()
+ {
+ return Pel::fmt(
+ 'DataWindow: %d bytes in [%d, %d] of %d bytes',
+ $this->size,
+ $this->start,
+ $this->start + $this->size,
+ strlen($this->data));
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelDataWindowOffsetException.php b/modules/autorotate/lib/pel-master/src/PelDataWindowOffsetException.php
new file mode 100644
index 00000000..7ec3d8da
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelDataWindowOffsetException.php
@@ -0,0 +1,35 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL)
+ * @package PEL
+ */
+
+/**
+ * An exception thrown when an invalid window is encountered.
+ *
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelDataWindowWindowException extends PelException
+{
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntry.php b/modules/autorotate/lib/pel-master/src/PelEntry.php
new file mode 100644
index 00000000..ec7a94b1
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntry.php
@@ -0,0 +1,253 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Common ancestor class of all {@link PelIfd} entries.
+ *
+ * As this class is abstract you cannot instantiate objects from it.
+ * It only serves as a common ancestor to define the methods common to
+ * all entries. The most important methods are {@link getValue()} and
+ * {@link setValue()}, both of which is abstract in this class. The
+ * descendants will give concrete implementations for them.
+ *
+ * If you have some data coming from an image (some raw bytes), then
+ * the static method {@link newFromData()} is helpful --- it will look
+ * at the data and give you a proper decendent of {@link PelEntry}
+ * back.
+ *
+ * If you instead want to have an entry for some data which take the
+ * form of an integer, a string, a byte, or some other PHP type, then
+ * don't use this class. You should instead create an object of the
+ * right subclass ({@link PelEntryShort} for short integers, {@link
+ * PelEntryAscii} for strings, and so on) directly.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+abstract class PelEntry
+{
+
+ /**
+ * Type of IFD containing this tag.
+ *
+ * This must be one of the constants defined in {@link PelIfd}:
+ * {@link PelIfd::IFD0} for the main image IFD, {@link PelIfd::IFD1}
+ * for the thumbnail image IFD, {@link PelIfd::EXIF} for the Exif
+ * sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or {@link
+ * PelIfd::INTEROPERABILITY} for the interoperability sub-IFD.
+ *
+ * @var int
+ */
+ protected $ifd_type;
+
+ /**
+ * The bytes representing this entry.
+ *
+ * Subclasses must either override {@link getBytes()} or, if
+ * possible, maintain this property so that it always contains a
+ * true representation of the entry.
+ *
+ * @var string
+ */
+ protected $bytes = '';
+
+ /**
+ * The {@link PelTag} of this entry.
+ *
+ * @var int
+ */
+ protected $tag;
+
+ /**
+ * The {@link PelFormat} of this entry.
+ *
+ * @var PelFormat
+ */
+ protected $format;
+
+ /**
+ * The number of components of this entry.
+ *
+ * @var int
+ */
+ protected $components;
+
+ /**
+ * Return the tag of this entry.
+ *
+ * @return int the tag of this entry.
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * Return the type of IFD which holds this entry.
+ *
+ * @return int one of the constants defined in {@link PelIfd}:
+ * {@link PelIfd::IFD0} for the main image IFD, {@link PelIfd::IFD1}
+ * for the thumbnail image IFD, {@link PelIfd::EXIF} for the Exif
+ * sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or {@link
+ * PelIfd::INTEROPERABILITY} for the interoperability sub-IFD.
+ */
+ public function getIfdType()
+ {
+ return $this->ifd_type;
+ }
+
+ /**
+ * Update the IFD type.
+ *
+ * @param
+ * int must be one of the constants defined in {@link
+ * PelIfd}: {@link PelIfd::IFD0} for the main image IFD, {@link
+ * PelIfd::IFD1} for the thumbnail image IFD, {@link PelIfd::EXIF}
+ * for the Exif sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or
+ * {@link PelIfd::INTEROPERABILITY} for the interoperability
+ * sub-IFD.
+ */
+ public function setIfdType($type)
+ {
+ $this->ifd_type = $type;
+ }
+
+ /**
+ * Return the format of this entry.
+ *
+ * @return PelFormat the format of this entry.
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Return the number of components of this entry.
+ *
+ * @return int the number of components of this entry.
+ */
+ public function getComponents()
+ {
+ return $this->components;
+ }
+
+ /**
+ * Turn this entry into bytes.
+ *
+ * @param
+ * PelByteOrder the desired byte order, which must be either
+ * {@link Convert::LITTLE_ENDIAN} or {@link Convert::BIG_ENDIAN}.
+ *
+ * @return string bytes representing this entry.
+ */
+ public function getBytes($o)
+ {
+ return $this->bytes;
+ }
+
+ /**
+ * Get the value of this entry as text.
+ *
+ * The value will be returned in a format suitable for presentation,
+ * e.g., rationals will be returned as 'x/y', ASCII strings will be
+ * returned as themselves etc.
+ *
+ * @param
+ * boolean some values can be returned in a long or more
+ * brief form, and this parameter controls that.
+ *
+ * @return string the value as text.
+ */
+ abstract public function getText($brief = false);
+
+ /**
+ * Get the value of this entry.
+ *
+ * The value returned will generally be the same as the one supplied
+ * to the constructor or with {@link setValue()}. For a formatted
+ * version of the value, one should use {@link getText()} instead.
+ *
+ * @return mixed the unformatted value.
+ */
+ abstract public function getValue();
+
+ /**
+ * Set the value of this entry.
+ *
+ * The value should be in the same format as for the constructor.
+ *
+ * @param
+ * mixed the new value.
+ *
+ * @abstract
+ *
+ */
+ public function setValue($value)
+ {
+ /*
+ * This (fake) abstract method is here to make it possible for the
+ * documentation to refer to PelEntry::setValue().
+ * It cannot declared abstract in the proper PHP way, for then PHP
+ * wont allow subclasses to define it with two arguments (which is
+ * what PelEntryCopyright does).
+ */
+ throw new PelException('setValue() is abstract.');
+ }
+
+ /**
+ * Turn this entry into a string.
+ *
+ * @return string a string representation of this entry. This is
+ * mostly for debugging.
+ */
+ public function __toString()
+ {
+ $str = Pel::fmt(" Tag: 0x%04X (%s)\n", $this->tag, PelTag::getName($this->ifd_type, $this->tag));
+ $str .= Pel::fmt(" Format : %d (%s)\n", $this->format, PelFormat::getName($this->format));
+ $str .= Pel::fmt(" Components: %d\n", $this->components);
+ if ($this->getTag() != PelTag::MAKER_NOTE && $this->getTag() != PelTag::PRINT_IM) {
+ $str .= Pel::fmt(" Value : %s\n", print_r($this->getValue(), true));
+ }
+ $str .= Pel::fmt(" Text : %s\n", $this->getText());
+ return $str;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryAscii.php b/modules/autorotate/lib/pel-master/src/PelEntryAscii.php
new file mode 100644
index 00000000..39d84fb4
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryAscii.php
@@ -0,0 +1,142 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding a plain ASCII string.
+ *
+ * This class can hold a single ASCII string, and it will be used as in
+ *
+ * $entry = $ifd->getEntry(PelTag::IMAGE_DESCRIPTION);
+ * print($entry->getValue());
+ * $entry->setValue('This is my image. I like it.');
+ *
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryAscii extends PelEntry
+{
+
+ /**
+ * The string hold by this entry.
+ *
+ * This is the string that was given to the {@link __construct
+ * constructor} or later to {@link setValue}, without any final NULL
+ * character.
+ *
+ * @var string
+ */
+ private $str;
+
+ /**
+ * Make a new PelEntry that can hold an ASCII string.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This should be
+ * one of the constants defined in {@link PelTag}, e.g., {@link
+ * PelTag::IMAGE_DESCRIPTION}, {@link PelTag::MODEL}, or any other
+ * tag with format {@link PelFormat::ASCII}.
+ *
+ * @param string $str
+ * the string that this entry will represent. The
+ * string must obey the same rules as the string argument to {@link
+ * setValue}, namely that it should be given without any trailing
+ * NULL character and that it must be plain 7-bit ASCII.
+ */
+ public function __construct($tag, $str = '')
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::ASCII;
+ self::setValue($str);
+ }
+
+ /**
+ * Give the entry a new ASCII value.
+ *
+ * This will overwrite the previous value. The value can be
+ * retrieved later with the {@link getValue} method.
+ *
+ * @param
+ * string the new value of the entry. This should be given
+ * without any trailing NULL character. The string must be plain
+ * 7-bit ASCII, the string should contain no high bytes.
+ *
+ * @todo Implement check for high bytes?
+ */
+ public function setValue($str)
+ {
+ $this->components = strlen($str) + 1;
+ $this->str = $str;
+ $this->bytes = $str . chr(0x00);
+ }
+
+ /**
+ * Return the ASCII string of the entry.
+ *
+ * @return string the string held, without any final NULL character.
+ * The string will be the same as the one given to {@link setValue}
+ * or to the {@link __construct constructor}.
+ */
+ public function getValue()
+ {
+ return $this->str;
+ }
+
+ /**
+ * Return the ASCII string of the entry.
+ *
+ * This methods returns the same as {@link getValue}.
+ *
+ * @param
+ * boolean not used with ASCII entries.
+ *
+ * @return string the string held, without any final NULL character.
+ * The string will be the same as the one given to {@link setValue}
+ * or to the {@link __construct constructor}.
+ */
+ public function getText($brief = false)
+ {
+ return $this->str;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryByte.php b/modules/autorotate/lib/pel-master/src/PelEntryByte.php
new file mode 100644
index 00000000..a487b745
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryByte.php
@@ -0,0 +1,99 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding unsigned bytes.
+ *
+ * This class can hold bytes, either just a single byte or an array of
+ * bytes. The class will be used to manipulate any of the Exif tags
+ * which has format {@link PelFormat::BYTE}.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryByte extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold an unsigned byte.
+ *
+ * The method accept several integer arguments. The {@link
+ * getValue} method will always return an array except for when a
+ * single integer argument is given here.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This
+ * should be one of the constants defined in {@link PelTag}
+ * which has format {@link PelFormat::BYTE}.
+ *
+ * @param int $value...
+ * the byte(s) that this entry will represent.
+ * The argument passed must obey the same rules as the argument to
+ * {@link setValue}, namely that it should be within range of an
+ * unsigned byte, that is between 0 and 255 (inclusive). If not,
+ * then a {@link PelOverflowException} will be thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = 0;
+ $this->max = 255;
+ $this->format = PelFormat::BYTE;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param int $number
+ * the number that should be converted.
+ *
+ * @param PelByteOrder $order
+ * one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return chr($number);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryCopyright.php b/modules/autorotate/lib/pel-master/src/PelEntryCopyright.php
new file mode 100644
index 00000000..eb8f2098
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryCopyright.php
@@ -0,0 +1,191 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding copyright information.
+ *
+ * The Exif standard specifies a certain format for copyright
+ * information where the one {@link PelTag::COPYRIGHT copyright
+ * tag} holds both the photographer and editor copyrights, separated
+ * by a NULL character.
+ *
+ * This class is used to manipulate that tag so that the format is
+ * kept to the standard. A common use would be to add a new copyright
+ * tag to an image, since most cameras do not add this tag themselves.
+ * This would be done like this:
+ *
+ *
+ * $entry = new PelEntryCopyright('Copyright, Martin Geisler, 2004');
+ * $ifd0->addEntry($entry);
+ *
+ *
+ * Here we only set the photographer copyright, use the optional
+ * second argument to specify the editor copyright. If there is only
+ * an editor copyright, then let the first argument be the empty
+ * string.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryCopyright extends PelEntryAscii
+{
+
+ /**
+ * The photographer copyright.
+ *
+ * @var string
+ */
+ private $photographer;
+
+ /**
+ * The editor copyright.
+ *
+ * @var string
+ */
+ private $editor;
+
+ /**
+ * Make a new entry for holding copyright information.
+ *
+ * @param
+ * string the photographer copyright. Use the empty string
+ * if there is no photographer copyright.
+ *
+ * @param
+ * string the editor copyright. Use the empty string if
+ * there is no editor copyright.
+ */
+ public function __construct($photographer = '', $editor = '')
+ {
+ parent::__construct(PelTag::COPYRIGHT);
+ $this->setValue($photographer, $editor);
+ }
+
+ /**
+ * Update the copyright information.
+ *
+ * @param
+ * string the photographer copyright. Use the empty string
+ * if there is no photographer copyright.
+ *
+ * @param
+ * string the editor copyright. Use the empty string if
+ * there is no editor copyright.
+ */
+ public function setValue($photographer = '', $editor = '')
+ {
+ $this->photographer = $photographer;
+ $this->editor = $editor;
+
+ if ($photographer == '' && $editor != '') {
+ $photographer = ' ';
+ }
+
+ if ($editor == '') {
+ parent::setValue($photographer);
+ } else {
+ parent::setValue($photographer . chr(0x00) . $editor);
+ }
+ }
+
+ /**
+ * Retrive the copyright information.
+ *
+ * The strings returned will be the same as the one used previously
+ * with either {@link __construct the constructor} or with {@link
+ * setValue}.
+ *
+ * @return array an array with two strings, the photographer and
+ * editor copyrights. The two fields will be returned in that
+ * order, so that the first array index will be the photographer
+ * copyright, and the second will be the editor copyright.
+ */
+ public function getValue()
+ {
+ return array(
+ $this->photographer,
+ $this->editor
+ );
+ }
+
+ /**
+ * Return a text string with the copyright information.
+ *
+ * The photographer and editor copyright fields will be returned
+ * with a '-' in between if both copyright fields are present,
+ * otherwise only one of them will be returned.
+ *
+ * @param
+ * boolean if false, then the strings '(Photographer)' and
+ * '(Editor)' will be appended to the photographer and editor
+ * copyright fields (if present), otherwise the fields will be
+ * returned as is.
+ *
+ * @return string the copyright information in a string.
+ */
+ public function getText($brief = false)
+ {
+ if ($brief) {
+ $p = '';
+ $e = '';
+ } else {
+ $p = ' ' . Pel::tra('(Photographer)');
+ $e = ' ' . Pel::tra('(Editor)');
+ }
+
+ if ($this->photographer != '' && $this->editor != '') {
+ return $this->photographer . $p . ' - ' . $this->editor . $e;
+ }
+
+ if ($this->photographer != '') {
+ return $this->photographer . $p;
+ }
+
+ if ($this->editor != '') {
+ return $this->editor . $e;
+ }
+
+ return '';
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryException.php b/modules/autorotate/lib/pel-master/src/PelEntryException.php
new file mode 100644
index 00000000..7ecccdeb
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryException.php
@@ -0,0 +1,87 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception indicating a problem with an entry.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelEntryException extends PelException
+{
+
+ /**
+ * The IFD type (if known).
+ *
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * The tag of the entry (if known).
+ *
+ * @var int
+ */
+ protected $tag;
+
+ /**
+ * Get the IFD type associated with the exception.
+ *
+ * @return int one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1},
+ * {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
+ * PelIfd::INTEROPERABILITY}. If no type is set, null is returned.
+ */
+ public function getIfdType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Get the tag associated with the exception.
+ *
+ * @return int the tag. If no tag is set, null is returned.
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryLong.php b/modules/autorotate/lib/pel-master/src/PelEntryLong.php
new file mode 100644
index 00000000..29f8d512
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryLong.php
@@ -0,0 +1,115 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding unsigned longs.
+ *
+ * This class can hold longs, either just a single long or an array of
+ * longs. The class will be used to manipulate any of the Exif tags
+ * which can have format {@link PelFormat::LONG} like in this
+ * example:
+ *
+ * $w = $ifd->getEntry(PelTag::EXIF_IMAGE_WIDTH);
+ * $w->setValue($w->getValue() / 2);
+ * $h = $ifd->getEntry(PelTag::EXIF_IMAGE_HEIGHT);
+ * $h->setValue($h->getValue() / 2);
+ *
+ * Here the width and height is updated to 50% of their original
+ * values.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryLong extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold an unsigned long.
+ *
+ * The method accept its arguments in two forms: several integer
+ * arguments or a single array argument. The {@link getValue}
+ * method will always return an array except for when a single
+ * integer argument is given here, or when an array with just a
+ * single integer is given.
+ *
+ * This means that one can conveniently use objects like this:
+ *
+ * $a = new PelEntryLong(PelTag::EXIF_IMAGE_WIDTH, 123456);
+ * $b = $a->getValue() - 654321;
+ *
+ * where the call to {@link getValue} will return an integer instead
+ * of an array with one integer element, which would then have to be
+ * extracted.
+ *
+ * @param
+ * int the tag which this entry represents. This
+ * should be one of the constants defined in {@link PelTag},
+ * e.g., {@link PelTag::IMAGE_WIDTH}, or any other tag which can
+ * have format {@link PelFormat::LONG}.
+ * @param int $value...
+ * the long(s) that this entry will
+ * represent or an array of longs. The argument passed must obey
+ * the same rules as the argument to {@link setValue}, namely that
+ * it should be within range of an unsigned long (32 bit), that is
+ * between 0 and 4294967295 (inclusive). If not, then a {@link
+ * PelExifOverflowException} will be thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = 0;
+ $this->max = 4294967295;
+ $this->format = PelFormat::LONG;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param
+ * int the number that should be converted.
+ * @param
+ * PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return PelConvert::longToBytes($number, $order);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryNumber.php b/modules/autorotate/lib/pel-master/src/PelEntryNumber.php
new file mode 100644
index 00000000..e967db0e
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryNumber.php
@@ -0,0 +1,285 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding numbers.
+ *
+ * This class can hold numbers, with range checks.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+abstract class PelEntryNumber extends PelEntry
+{
+
+ /**
+ * The value held by this entry.
+ *
+ * @var array
+ */
+ protected $value = array();
+
+ /**
+ * The minimum allowed value.
+ *
+ * Any attempt to change the value below this variable will result
+ * in a {@link PelOverflowException} being thrown.
+ *
+ * @var int
+ */
+ protected $min;
+
+ /**
+ * The maximum allowed value.
+ *
+ * Any attempt to change the value over this variable will result in
+ * a {@link PelOverflowException} being thrown.
+ *
+ * @var int
+ */
+ protected $max;
+
+ /**
+ * The dimension of the number held.
+ *
+ * Normal numbers have a dimension of one, pairs have a dimension of
+ * two, etc.
+ *
+ * @var int
+ */
+ protected $dimension = 1;
+
+ /**
+ * Change the value.
+ *
+ * This method can change both the number of components and the
+ * value of the components. Range checks will be made on the new
+ * value, and a {@link PelOverflowException} will be thrown if the
+ * value is found to be outside the legal range.
+ *
+ * The method accept several number arguments. The {@link getValue}
+ * method will always return an array except for when a single
+ * number is given here.
+ *
+ * @param int|array $value...
+ * the new value(s). This can be zero or
+ * more numbers, that is, either integers or arrays. The input will
+ * be checked to ensure that the numbers are within the valid range.
+ * If not, then a {@link PelOverflowException} will be thrown.
+ *
+ * @see getValue
+ */
+ public function setValue($value)
+ {
+ $value = func_get_args();
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Change the value.
+ *
+ * This method can change both the number of components and the
+ * value of the components. Range checks will be made on the new
+ * value, and a {@link PelOverflowException} will be thrown if the
+ * value is found to be outside the legal range.
+ *
+ * @param
+ * array the new values. The array must contain the new
+ * numbers.
+ *
+ * @see getValue
+ */
+ public function setValueArray($value)
+ {
+ foreach ($value as $v) {
+ $this->validateNumber($v);
+ }
+
+ $this->components = count($value);
+ $this->value = $value;
+ }
+
+ /**
+ * Return the numeric value held.
+ *
+ * @return int|array this will either be a single number if there is
+ * only one component, or an array of numbers otherwise.
+ */
+ public function getValue()
+ {
+ if ($this->components == 1) {
+ return $this->value[0];
+ } else {
+ return $this->value;
+ }
+ }
+
+ /**
+ * Validate a number.
+ *
+ * This method will check that the number given is within the range
+ * given my {@link getMin()} and {@link getMax()}, inclusive. If
+ * not, then a {@link PelOverflowException} is thrown.
+ *
+ * @param
+ * int|array the number in question.
+ *
+ * @return void nothing, but will throw a {@link
+ * PelOverflowException} if the number is found to be outside the
+ * legal range and {@link Pel::$strict} is true.
+ */
+ public function validateNumber($n)
+ {
+ if ($this->dimension == 1) {
+ if ($n < $this->min || $n > $this->max) {
+ Pel::maybeThrow(new PelOverflowException($n, $this->min, $this->max));
+ }
+ } else {
+ for ($i = 0; $i < $this->dimension; $i ++) {
+ if ($n[$i] < $this->min || $n[$i] > $this->max) {
+ Pel::maybeThrow(new PelOverflowException($n[$i], $this->min, $this->max));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add a number.
+ *
+ * This appends a number to the numbers already held by this entry,
+ * thereby increasing the number of components by one.
+ *
+ * @param
+ * int|array the number to be added.
+ */
+ public function addNumber($n)
+ {
+ $this->validateNumber($n);
+ $this->value[] = $n;
+ $this->components ++;
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * The concrete subclasses will have to implement this method so
+ * that the numbers represented can be turned into bytes.
+ *
+ * The method will be called once for each number held by the entry.
+ *
+ * @param
+ * int the number that should be converted.
+ *
+ * @param
+ * PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ abstract public function numberToBytes($number, $order);
+
+ /**
+ * Turn this entry into bytes.
+ *
+ * @param
+ * PelByteOrder the desired byte order, which must be either
+ * {@link PelConvert::LITTLE_ENDIAN} or {@link
+ * PelConvert::BIG_ENDIAN}.
+ *
+ * @return string bytes representing this entry.
+ */
+ public function getBytes($o)
+ {
+ $bytes = '';
+ for ($i = 0; $i < $this->components; $i ++) {
+ if ($this->dimension == 1) {
+ $bytes .= $this->numberToBytes($this->value[$i], $o);
+ } else {
+ for ($j = 0; $j < $this->dimension; $j ++) {
+ $bytes .= $this->numberToBytes($this->value[$i][$j], $o);
+ }
+ }
+ }
+ return $bytes;
+ }
+
+ /**
+ * Format a number.
+ *
+ * This method is called by {@link getText} to format numbers.
+ * Subclasses should override this method if they need more
+ * sophisticated behavior than the default, which is to just return
+ * the number as is.
+ *
+ * @param
+ * int the number which will be formatted.
+ *
+ * @param
+ * boolean it could be that there is both a verbose and a
+ * brief formatting available, and this argument controls that.
+ *
+ * @return string the number formatted as a string suitable for
+ * display.
+ */
+ public function formatNumber($number, $brief = false)
+ {
+ return $number;
+ }
+
+ /**
+ * Get the numeric value of this entry as text.
+ *
+ * @param
+ * boolean use brief output? The numbers will be separated
+ * by a single space if brief output is requested, otherwise a space
+ * and a comma will be used.
+ *
+ * @return string the numbers(s) held by this entry.
+ */
+ public function getText($brief = false)
+ {
+ if ($this->components == 0) {
+ return '';
+ }
+
+ $str = $this->formatNumber($this->value[0]);
+ for ($i = 1; $i < $this->components; $i ++) {
+ $str .= ($brief ? ' ' : ', ');
+ $str .= $this->formatNumber($this->value[$i]);
+ }
+
+ return $str;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryRational.php b/modules/autorotate/lib/pel-master/src/PelEntryRational.php
new file mode 100644
index 00000000..d1cdaed6
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryRational.php
@@ -0,0 +1,175 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding unsigned rational numbers.
+ *
+ * This class can hold rational numbers, consisting of a numerator and
+ * denominator both of which are of type unsigned long. Each rational
+ * is represented by an array with just two entries: the numerator and
+ * the denominator, in that order.
+ *
+ * The class can hold either just a single rational or an array of
+ * rationals. The class will be used to manipulate any of the Exif
+ * tags which can have format {@link PelFormat::RATIONAL} like in this
+ * example:
+ *
+ *
+ * $resolution = $ifd->getEntry(PelTag::X_RESOLUTION);
+ * $resolution->setValue(array(1, 300));
+ *
+ *
+ * Here the x-resolution is adjusted to 1/300, which will be 300 DPI,
+ * unless the {@link PelTag::RESOLUTION_UNIT resolution unit} is set
+ * to something different than 2 which means inches.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryRational extends PelEntryLong
+{
+
+ /**
+ * Make a new entry that can hold an unsigned rational.
+ *
+ * @param
+ * int the tag which this entry represents. This should
+ * be one of the constants defined in {@link PelTag}, e.g., {@link
+ * PelTag::X_RESOLUTION}, or any other tag which can have format
+ * {@link PelFormat::RATIONAL}.
+ *
+ * @param array $value...
+ * the rational(s) that this entry will
+ * represent. The arguments passed must obey the same rules as the
+ * argument to {@link setValue}, namely that each argument should be
+ * an array with two entries, both of which must be within range of
+ * an unsigned long (32 bit), that is between 0 and 4294967295
+ * (inclusive). If not, then a {@link PelOverflowException} will be
+ * thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::RATIONAL;
+ $this->dimension = 2;
+ $this->min = 0;
+ $this->max = 4294967295;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Format a rational number.
+ *
+ * The rational will be returned as a string with a slash '/'
+ * between the numerator and denominator.
+ *
+ * @param
+ * array the rational which will be formatted.
+ *
+ * @param
+ * boolean not used.
+ *
+ * @return string the rational formatted as a string suitable for
+ * display.
+ */
+ public function formatNumber($number, $brief = false)
+ {
+ return $number[0] . '/' . $number[1];
+ }
+
+ /**
+ * Get the value of an entry as text.
+ *
+ * The value will be returned in a format suitable for presentation,
+ * e.g., rationals will be returned as 'x/y', ASCII strings will be
+ * returned as themselves etc.
+ *
+ * @param
+ * boolean some values can be returned in a long or more
+ * brief form, and this parameter controls that.
+ *
+ * @return string the value as text.
+ */
+ public function getText($brief = false)
+ {
+ if (isset($this->value[0])) {
+ $v = $this->value[0];
+ }
+
+ switch ($this->tag) {
+ case PelTag::FNUMBER:
+ // CC (e->components, 1, v);
+ return Pel::fmt('f/%.01f', $v[0] / $v[1]);
+
+ case PelTag::APERTURE_VALUE:
+ // CC (e->components, 1, v);
+ // if (!v_rat.denominator) return (NULL);
+ return Pel::fmt('f/%.01f', pow(2, $v[0] / $v[1] / 2));
+
+ case PelTag::FOCAL_LENGTH:
+ // CC (e->components, 1, v);
+ // if (!v_rat.denominator) return (NULL);
+ return Pel::fmt('%.1f mm', $v[0] / $v[1]);
+
+ case PelTag::SUBJECT_DISTANCE:
+ // CC (e->components, 1, v);
+ // if (!v_rat.denominator) return (NULL);
+ return Pel::fmt('%.1f m', $v[0] / $v[1]);
+
+ case PelTag::EXPOSURE_TIME:
+ // CC (e->components, 1, v);
+ // if (!v_rat.denominator) return (NULL);
+ if ($v[0] / $v[1] < 1) {
+ return Pel::fmt('1/%d sec.', $v[1] / $v[0]);
+ } else {
+ return Pel::fmt('%d sec.', $v[0] / $v[1]);
+ }
+ break;
+ case PelTag::GPS_LATITUDE:
+ case PelTag::GPS_LONGITUDE:
+ $degrees = $this->value[0][0] / $this->value[0][1];
+ $minutes = $this->value[1][0] / $this->value[1][1];
+ $seconds = $this->value[2][0] / $this->value[2][1];
+
+ return sprintf('%s° %s\' %s" (%.2f°)', $degrees, $minutes, $seconds, $degrees + $minutes / 60 + $seconds / 3600);
+
+ default:
+ return parent::getText($brief);
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntrySByte.php b/modules/autorotate/lib/pel-master/src/PelEntrySByte.php
new file mode 100644
index 00000000..9769df99
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntrySByte.php
@@ -0,0 +1,99 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding signed bytes.
+ *
+ * This class can hold bytes, either just a single byte or an array of
+ * bytes. The class will be used to manipulate any of the Exif tags
+ * which has format {@link PelFormat::BYTE}.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntrySByte extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold a signed byte.
+ *
+ * The method accept several integer arguments. The {@link getValue}
+ * method will always return an array except for when a single
+ * integer argument is given here.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This
+ * should be one of the constants defined in {@link PelTag}
+ * which has format {@link PelFormat::BYTE}.
+ *
+ * @param int $value...
+ * the byte(s) that this entry will represent.
+ * The argument passed must obey the same rules as the argument to
+ * {@link setValue}, namely that it should be within range of a
+ * signed byte, that is between -128 and 127 (inclusive). If not,
+ * then a {@link PelOverflowException} will be thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = - 128;
+ $this->max = 127;
+ $this->format = PelFormat::SBYTE;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param int $number
+ * the number that should be converted.
+ *
+ * @param PelByteOrder $order
+ * one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return chr($number);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntrySLong.php b/modules/autorotate/lib/pel-master/src/PelEntrySLong.php
new file mode 100644
index 00000000..7b43218c
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntrySLong.php
@@ -0,0 +1,99 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding signed longs.
+ *
+ * This class can hold longs, either just a single long or an array of
+ * longs. The class will be used to manipulate any of the Exif tags
+ * which can have format {@link PelFormat::SLONG}.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntrySLong extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold a signed long.
+ *
+ * The method accept its arguments in two forms: several integer
+ * arguments or a single array argument. The {@link getValue}
+ * method will always return an array except for when a single
+ * integer argument is given here, or when an array with just a
+ * single integer is given.
+ *
+ * @param
+ * int the tag which this entry represents. This
+ * should be one of the constants defined in {@link PelTag}
+ * which have format {@link PelFormat::SLONG}.
+ *
+ * @param int $value...
+ * the long(s) that this entry will represent
+ * or an array of longs. The argument passed must obey the same
+ * rules as the argument to {@link setValue}, namely that it should
+ * be within range of a signed long (32 bit), that is between
+ * -2147483648 and 2147483647 (inclusive). If not, then a {@link
+ * PelOverflowException} will be thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = - 2147483648;
+ $this->max = 2147483647;
+ $this->format = PelFormat::SLONG;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param
+ * int the number that should be converted.
+ *
+ * @param
+ * PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return PelConvert::sLongToBytes($number, $order);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntrySRational.php b/modules/autorotate/lib/pel-master/src/PelEntrySRational.php
new file mode 100644
index 00000000..9c7531ae
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntrySRational.php
@@ -0,0 +1,153 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding signed rational numbers.
+ *
+ * This class can hold rational numbers, consisting of a numerator and
+ * denominator both of which are of type unsigned long. Each rational
+ * is represented by an array with just two entries: the numerator and
+ * the denominator, in that order.
+ *
+ * The class can hold either just a single rational or an array of
+ * rationals. The class will be used to manipulate any of the Exif
+ * tags which can have format {@link PelFormat::SRATIONAL}.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntrySRational extends PelEntrySLong
+{
+
+ /**
+ * Make a new entry that can hold a signed rational.
+ *
+ * @param
+ * int the tag which this entry represents. This should
+ * be one of the constants defined in {@link PelTag}, e.g., {@link
+ * PelTag::SHUTTER_SPEED_VALUE}, or any other tag which can have
+ * format {@link PelFormat::SRATIONAL}.
+ *
+ * @param array $value...
+ * the rational(s) that this entry will
+ * represent. The arguments passed must obey the same rules as the
+ * argument to {@link setValue}, namely that each argument should be
+ * an array with two entries, both of which must be within range of
+ * a signed long (32 bit), that is between -2147483648 and
+ * 2147483647 (inclusive). If not, then a {@link
+ * PelOverflowException} will be thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::SRATIONAL;
+ $this->dimension = 2;
+ $this->min = - 2147483648;
+ $this->max = 2147483647;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Format a rational number.
+ *
+ * The rational will be returned as a string with a slash '/'
+ * between the numerator and denominator. Care is taken to display
+ * '-1/2' instead of the ugly but mathematically equivalent '1/-2'.
+ *
+ * @param
+ * array the rational which will be formatted.
+ *
+ * @param
+ * boolean not used.
+ *
+ * @return string the rational formatted as a string suitable for
+ * display.
+ */
+ public function formatNumber($number, $brief = false)
+ {
+ if ($number[1] < 0) {
+ /* Turn output like 1/-2 into -1/2. */
+ return (- $number[0]) . '/' . (- $number[1]);
+ } else {
+ return $number[0] . '/' . $number[1];
+ }
+ }
+
+ /**
+ * Get the value of an entry as text.
+ *
+ * The value will be returned in a format suitable for presentation,
+ * e.g., rationals will be returned as 'x/y', ASCII strings will be
+ * returned as themselves etc.
+ *
+ * @param
+ * boolean some values can be returned in a long or more
+ * brief form, and this parameter controls that.
+ *
+ * @return string the value as text.
+ */
+ public function getText($brief = false)
+ {
+ if (isset($this->value[0])) {
+ $v = $this->value[0];
+ }
+
+ switch ($this->tag) {
+ case PelTag::SHUTTER_SPEED_VALUE:
+ // CC (e->components, 1, v);
+ // if (!v_srat.denominator) return (NULL);
+ return Pel::fmt('%.0f/%.0f sec. (APEX: %d)', $v[0], $v[1], pow(sqrt(2), $v[0] / $v[1]));
+
+ case PelTag::BRIGHTNESS_VALUE:
+ // CC (e->components, 1, v);
+ //
+ // TODO: figure out the APEX thing, or remove this so that it is
+ // handled by the default clause at the bottom.
+ return sprintf('%d/%d', $v[0], $v[1]);
+ // FIXME: How do I calculate the APEX value?
+
+ case PelTag::EXPOSURE_BIAS_VALUE:
+ // CC (e->components, 1, v);
+ // if (!v_srat.denominator) return (NULL);
+ return sprintf('%s%.01f', $v[0] * $v[1] > 0 ? '+' : '', $v[0] / $v[1]);
+
+ default:
+ return parent::getText($brief);
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntrySShort.php b/modules/autorotate/lib/pel-master/src/PelEntrySShort.php
new file mode 100644
index 00000000..fd024578
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntrySShort.php
@@ -0,0 +1,97 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding signed shorts.
+ *
+ * This class can hold shorts, either just a single short or an array
+ * of shorts. The class will be used to manipulate any of the Exif
+ * tags which has format {@link PelFormat::SSHORT}.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntrySShort extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold a signed short.
+ *
+ * The method accept several integer arguments. The {@link
+ * getValue} method will always return an array except for when a
+ * single integer argument is given here.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This
+ * should be one of the constants defined in {@link PelTag}
+ * which has format {@link PelFormat::SSHORT}.
+ *
+ * @param int $value...
+ * the signed short(s) that this entry will
+ * represent. The argument passed must obey the same rules as the
+ * argument to {@link setValue}, namely that it should be within
+ * range of a signed short, that is between -32768 to 32767
+ * (inclusive). If not, then a {@link PelOverFlowException} will be
+ * thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = - 32768;
+ $this->max = 32767;
+ $this->format = PelFormat::SSHORT;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param int $number
+ * the number that should be converted.
+ *
+ * @param PelByteOrder $order
+ * one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return PelConvert::sShortToBytes($number, $order);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryShort.php b/modules/autorotate/lib/pel-master/src/PelEntryShort.php
new file mode 100644
index 00000000..2c623165
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryShort.php
@@ -0,0 +1,534 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding signed shorts.
+ *
+ * This class can hold shorts, either just a single short or an array
+ * of shorts. The class will be used to manipulate any of the Exif
+ * tags which has format {@link PelFormat::SHORT} like in this
+ * example:
+ *
+ *
+ * $w = $ifd->getEntry(PelTag::EXIF_IMAGE_WIDTH);
+ * $w->setValue($w->getValue() / 2);
+ * $h = $ifd->getEntry(PelTag::EXIF_IMAGE_HEIGHT);
+ * $h->setValue($h->getValue() / 2);
+ *
+ *
+ * Here the width and height is updated to 50% of their original
+ * values.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryShort extends PelEntryNumber
+{
+
+ /**
+ * Make a new entry that can hold an unsigned short.
+ *
+ * The method accept several integer arguments. The {@link
+ * getValue} method will always return an array except for when a
+ * single integer argument is given here.
+ *
+ * This means that one can conveniently use objects like this:
+ *
+ * $a = new PelEntryShort(PelTag::EXIF_IMAGE_HEIGHT, 42);
+ * $b = $a->getValue() + 314;
+ *
+ * where the call to {@link getValue} will return an integer
+ * instead of an array with one integer element, which would then
+ * have to be extracted.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This should be
+ * one of the constants defined in {@link PelTag}, e.g., {@link
+ * PelTag::IMAGE_WIDTH}, {@link PelTag::ISO_SPEED_RATINGS},
+ * or any other tag with format {@link PelFormat::SHORT}.
+ *
+ * @param int $value...
+ * the short(s) that this entry will
+ * represent. The argument passed must obey the same rules as the
+ * argument to {@link setValue}, namely that it should be within
+ * range of an unsigned short, that is between 0 and 65535
+ * (inclusive). If not, then a {@link PelOverFlowException} will be
+ * thrown.
+ */
+ public function __construct($tag, $value = null)
+ {
+ $this->tag = $tag;
+ $this->min = 0;
+ $this->max = 65535;
+ $this->format = PelFormat::SHORT;
+
+ $value = func_get_args();
+ array_shift($value);
+ $this->setValueArray($value);
+ }
+
+ /**
+ * Convert a number into bytes.
+ *
+ * @param int $number
+ * the number that should be converted.
+ *
+ * @param PelByteOrder $order
+ * one of {@link PelConvert::LITTLE_ENDIAN} and
+ * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
+ *
+ * @return string bytes representing the number given.
+ */
+ public function numberToBytes($number, $order)
+ {
+ return PelConvert::shortToBytes($number, $order);
+ }
+
+ /**
+ * Get the value of an entry as text.
+ *
+ * The value will be returned in a format suitable for presentation,
+ * e.g., instead of returning '2' for a {@link
+ * PelTag::METERING_MODE} tag, 'Center-Weighted Average' is
+ * returned.
+ *
+ * @param
+ * boolean some values can be returned in a long or more
+ * brief form, and this parameter controls that.
+ *
+ * @return string the value as text.
+ */
+ public function getText($brief = false)
+ {
+ switch ($this->tag) {
+ case PelTag::METERING_MODE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Unknown');
+ case 1:
+ return Pel::tra('Average');
+ case 2:
+ return Pel::tra('Center-Weighted Average');
+ case 3:
+ return Pel::tra('Spot');
+ case 4:
+ return Pel::tra('Multi Spot');
+ case 5:
+ return Pel::tra('Pattern');
+ case 6:
+ return Pel::tra('Partial');
+ case 255:
+ return Pel::tra('Other');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::COMPRESSION:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return Pel::tra('Uncompressed');
+ case 6:
+ return Pel::tra('JPEG compression');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::PLANAR_CONFIGURATION:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return Pel::tra('chunky format');
+ case 2:
+ return Pel::tra('planar format');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::SENSING_METHOD:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return Pel::tra('Not defined');
+ case 2:
+ return Pel::tra('One-chip color area sensor');
+ case 3:
+ return Pel::tra('Two-chip color area sensor');
+ case 4:
+ return Pel::tra('Three-chip color area sensor');
+ case 5:
+ return Pel::tra('Color sequential area sensor');
+ case 7:
+ return Pel::tra('Trilinear sensor');
+ case 8:
+ return Pel::tra('Color sequential linear sensor');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::LIGHT_SOURCE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Unknown');
+ case 1:
+ return Pel::tra('Daylight');
+ case 2:
+ return Pel::tra('Fluorescent');
+ case 3:
+ return Pel::tra('Tungsten (incandescent light)');
+ case 4:
+ return Pel::tra('Flash');
+ case 9:
+ return Pel::tra('Fine weather');
+ case 10:
+ return Pel::tra('Cloudy weather');
+ case 11:
+ return Pel::tra('Shade');
+ case 12:
+ return Pel::tra('Daylight fluorescent');
+ case 13:
+ return Pel::tra('Day white fluorescent');
+ case 14:
+ return Pel::tra('Cool white fluorescent');
+ case 15:
+ return Pel::tra('White fluorescent');
+ case 17:
+ return Pel::tra('Standard light A');
+ case 18:
+ return Pel::tra('Standard light B');
+ case 19:
+ return Pel::tra('Standard light C');
+ case 20:
+ return Pel::tra('D55');
+ case 21:
+ return Pel::tra('D65');
+ case 22:
+ return Pel::tra('D75');
+ case 24:
+ return Pel::tra('ISO studio tungsten');
+ case 255:
+ return Pel::tra('Other');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::FOCAL_PLANE_RESOLUTION_UNIT:
+ case PelTag::RESOLUTION_UNIT:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 2:
+ return Pel::tra('Inch');
+ case 3:
+ return Pel::tra('Centimeter');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::EXPOSURE_PROGRAM:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Not defined');
+ case 1:
+ return Pel::tra('Manual');
+ case 2:
+ return Pel::tra('Normal program');
+ case 3:
+ return Pel::tra('Aperture priority');
+ case 4:
+ return Pel::tra('Shutter priority');
+ case 5:
+ return Pel::tra('Creative program (biased toward depth of field)');
+ case 6:
+ return Pel::tra('Action program (biased toward fast shutter speed)');
+ case 7:
+ return Pel::tra('Portrait mode (for closeup photos with the background out of focus');
+ case 8:
+ return Pel::tra('Landscape mode (for landscape photos with the background in focus');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::ORIENTATION:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return Pel::tra('top - left');
+ case 2:
+ return Pel::tra('top - right');
+ case 3:
+ return Pel::tra('bottom - right');
+ case 4:
+ return Pel::tra('bottom - left');
+ case 5:
+ return Pel::tra('left - top');
+ case 6:
+ return Pel::tra('right - top');
+ case 7:
+ return Pel::tra('right - bottom');
+ case 8:
+ return Pel::tra('left - bottom');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::YCBCR_POSITIONING:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return Pel::tra('centered');
+ case 2:
+ return Pel::tra('co-sited');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::YCBCR_SUB_SAMPLING:
+ // CC (e->components, 2, v);
+ if ($this->value[0] == 2 && $this->value[1] == 1) {
+ return 'YCbCr4:2:2';
+ }
+ if ($this->value[0] == 2 && $this->value[1] == 2) {
+ return 'YCbCr4:2:0';
+ }
+
+ return $this->value[0] . ', ' . $this->value[1];
+ break;
+ case PelTag::PHOTOMETRIC_INTERPRETATION:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 2:
+ return 'RGB';
+ case 6:
+ return 'YCbCr';
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::COLOR_SPACE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 1:
+ return 'sRGB';
+ case 2:
+ return 'Adobe RGB';
+ case 0xffff:
+ return Pel::tra('Uncalibrated');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::FLASH:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0x0000:
+ return Pel::tra('Flash did not fire.');
+ case 0x0001:
+ return Pel::tra('Flash fired.');
+ case 0x0005:
+ return Pel::tra('Strobe return light not detected.');
+ case 0x0007:
+ return Pel::tra('Strobe return light detected.');
+ case 0x0009:
+ return Pel::tra('Flash fired, compulsory flash mode.');
+ case 0x000d:
+ return Pel::tra('Flash fired, compulsory flash mode, return light not detected.');
+ case 0x000f:
+ return Pel::tra('Flash fired, compulsory flash mode, return light detected.');
+ case 0x0010:
+ return Pel::tra('Flash did not fire, compulsory flash mode.');
+ case 0x0018:
+ return Pel::tra('Flash did not fire, auto mode.');
+ case 0x0019:
+ return Pel::tra('Flash fired, auto mode.');
+ case 0x001d:
+ return Pel::tra('Flash fired, auto mode, return light not detected.');
+ case 0x001f:
+ return Pel::tra('Flash fired, auto mode, return light detected.');
+ case 0x0020:
+ return Pel::tra('No flash function.');
+ case 0x0041:
+ return Pel::tra('Flash fired, red-eye reduction mode.');
+ case 0x0045:
+ return Pel::tra('Flash fired, red-eye reduction mode, return light not detected.');
+ case 0x0047:
+ return Pel::tra('Flash fired, red-eye reduction mode, return light detected.');
+ case 0x0049:
+ return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode.');
+ case 0x004d:
+ return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected.');
+ case 0x004f:
+ return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode, return light detected.');
+ case 0x0058:
+ return Pel::tra('Flash did not fire, auto mode, red-eye reduction mode.');
+ case 0x0059:
+ return Pel::tra('Flash fired, auto mode, red-eye reduction mode.');
+ case 0x005d:
+ return Pel::tra('Flash fired, auto mode, return light not detected, red-eye reduction mode.');
+ case 0x005f:
+ return Pel::tra('Flash fired, auto mode, return light detected, red-eye reduction mode.');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::CUSTOM_RENDERED:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Normal process');
+ case 1:
+ return Pel::tra('Custom process');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::EXPOSURE_MODE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Auto exposure');
+ case 1:
+ return Pel::tra('Manual exposure');
+ case 2:
+ return Pel::tra('Auto bracket');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::WHITE_BALANCE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Auto white balance');
+ case 1:
+ return Pel::tra('Manual white balance');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::SCENE_CAPTURE_TYPE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Standard');
+ case 1:
+ return Pel::tra('Landscape');
+ case 2:
+ return Pel::tra('Portrait');
+ case 3:
+ return Pel::tra('Night scene');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::GAIN_CONTROL:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Normal');
+ case 1:
+ return Pel::tra('Low gain up');
+ case 2:
+ return Pel::tra('High gain up');
+ case 3:
+ return Pel::tra('Low gain down');
+ case 4:
+ return Pel::tra('High gain down');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::SATURATION:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Normal');
+ case 1:
+ return Pel::tra('Low saturation');
+ case 2:
+ return Pel::tra('High saturation');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::CONTRAST:
+ case PelTag::SHARPNESS:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Normal');
+ case 1:
+ return Pel::tra('Soft');
+ case 2:
+ return Pel::tra('Hard');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::SUBJECT_DISTANCE_RANGE:
+ // CC (e->components, 1, v);
+ switch ($this->value[0]) {
+ case 0:
+ return Pel::tra('Unknown');
+ case 1:
+ return Pel::tra('Macro');
+ case 2:
+ return Pel::tra('Close view');
+ case 3:
+ return Pel::tra('Distant view');
+ default:
+ return $this->value[0];
+ }
+ break;
+ case PelTag::SUBJECT_AREA:
+ switch ($this->components) {
+ case 2:
+ return Pel::fmt('(x,y) = (%d,%d)', $this->value[0], $this->value[1]);
+ case 3:
+ return Pel::fmt('Within distance %d of (x,y) = (%d,%d)', $this->value[0], $this->value[1], $this->value[2]);
+ case 4:
+ return Pel::fmt('Within rectangle (width %d, height %d) around (x,y) = (%d,%d)', $this->value[0], $this->value[1], $this->value[2], $this->value[3]);
+
+ default:
+ return Pel::fmt('Unexpected number of components (%d, expected 2, 3, or 4).', $this->components);
+ }
+ break;
+ default:
+ return parent::getText($brief);
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryTime.php b/modules/autorotate/lib/pel-master/src/PelEntryTime.php
new file mode 100644
index 00000000..da64eaaa
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryTime.php
@@ -0,0 +1,352 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding a date and time.
+ *
+ * This class can hold a timestamp, and it will be used as
+ * in this example where the time is advanced by one week:
+ *
+ * $entry = $ifd->getEntry(PelTag::DATE_TIME_ORIGINAL);
+ * $time = $entry->getValue();
+ * print('The image was taken on the ' . date('jS', $time));
+ * $entry->setValue($time + 7 * 24 * 3600);
+ *
+ *
+ * The example used a standard UNIX timestamp, which is the default
+ * for this class.
+ *
+ * But the Exif format defines dates outside the range of a UNIX
+ * timestamp (about 1970 to 2038) and so you can also get access to
+ * the timestamp in two other formats: a simple string or a Julian Day
+ * Count. Please see the Calendar extension in the PHP Manual for more
+ * information about the Julian Day Count.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryTime extends PelEntryAscii
+{
+
+ /**
+ * Constant denoting a UNIX timestamp.
+ */
+ const UNIX_TIMESTAMP = 1;
+
+ /**
+ * Constant denoting a Exif string.
+ */
+ const EXIF_STRING = 2;
+
+ /**
+ * Constant denoting a Julian Day Count.
+ */
+ const JULIAN_DAY_COUNT = 3;
+
+ /**
+ * The Julian Day Count of the timestamp held by this entry.
+ *
+ * This is an integer counting the number of whole days since
+ * January 1st, 4713 B.C. The fractional part of the timestamp held
+ * by this entry is stored in {@link $seconds}.
+ *
+ * @var int
+ */
+ private $day_count;
+
+ /**
+ * The number of seconds into the day of the timestamp held by this
+ * entry.
+ *
+ * The number of whole days is stored in {@link $day_count} and the
+ * number of seconds left-over is stored here.
+ *
+ * @var int
+ */
+ private $seconds;
+
+ /**
+ * Make a new entry for holding a timestamp.
+ *
+ * @param integer $tag
+ * the Exif tag which this entry represents. There are
+ * only three standard tags which hold timestamp, so this should be
+ * one of the constants {@link PelTag::DATE_TIME}, {@link
+ * PelTag::DATE_TIME_ORIGINAL}, or {@link
+ * PelTag::DATE_TIME_DIGITIZED}.
+ *
+ * @param integer $timestamp
+ * the timestamp held by this entry in the correct form
+ * as indicated by the third argument. For {@link UNIX_TIMESTAMP}
+ * this is an integer counting the number of seconds since January
+ * 1st 1970, for {@link EXIF_STRING} this is a string of the form
+ * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a
+ * floating point number where the integer part denotes the day
+ * count and the fractional part denotes the time of day (0.25 means
+ * 6:00, 0.75 means 18:00).
+ *
+ * @param integer $type
+ * the type of the timestamp. This must be one of
+ * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or
+ * {@link JULIAN_DAY_COUNT}.
+ */
+ public function __construct($tag, $timestamp, $type = self::UNIX_TIMESTAMP)
+ {
+ parent::__construct($tag);
+ $this->setValue($timestamp, $type);
+ }
+
+ /**
+ * Return the timestamp of the entry.
+ *
+ * The timestamp held by this entry is returned in one of three
+ * formats: as a standard UNIX timestamp (default), as a fractional
+ * Julian Day Count, or as a string.
+ *
+ * @param integer $type
+ * the type of the timestamp. This must be one of
+ * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or
+ * {@link JULIAN_DAY_COUNT}.
+ *
+ * @return integer the timestamp held by this entry in the correct form
+ * as indicated by the type argument. For {@link UNIX_TIMESTAMP}
+ * this is an integer counting the number of seconds since January
+ * 1st 1970, for {@link EXIF_STRING} this is a string of the form
+ * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a
+ * floating point number where the integer part denotes the day
+ * count and the fractional part denotes the time of day (0.25 means
+ * 6:00, 0.75 means 18:00).
+ */
+ public function getValue($type = self::UNIX_TIMESTAMP)
+ {
+ switch ($type) {
+ case self::UNIX_TIMESTAMP:
+ $seconds = $this->convertJdToUnix($this->day_count);
+ if ($seconds === false) {
+ /*
+ * We get false if the Julian Day Count is outside the range
+ * of a UNIX timestamp.
+ */
+ return false;
+ } else {
+ return $seconds + $this->seconds;
+ }
+ break;
+ case self::EXIF_STRING:
+ list ($year, $month, $day) = $this->convertJdToGregorian($this->day_count);
+ $hours = (int) ($this->seconds / 3600);
+ $minutes = (int) ($this->seconds % 3600 / 60);
+ $seconds = $this->seconds % 60;
+ return sprintf('%04d:%02d:%02d %02d:%02d:%02d', $year, $month, $day, $hours, $minutes, $seconds);
+ case self::JULIAN_DAY_COUNT:
+ return $this->day_count + $this->seconds / 86400;
+ default:
+ throw new PelInvalidArgumentException(
+ 'Expected UNIX_TIMESTAMP (%d), ' . 'EXIF_STRING (%d), or ' . 'JULIAN_DAY_COUNT (%d) for $type, ' .
+ 'got %d.',
+ self::UNIX_TIMESTAMP,
+ self::EXIF_STRING,
+ self::JULIAN_DAY_COUNT,
+ $type);
+ }
+ }
+
+ /**
+ * Update the timestamp held by this entry.
+ *
+ * @param integer $timestamp
+ * the timestamp held by this entry in the correct form
+ * as indicated by the third argument. For {@link UNIX_TIMESTAMP}
+ * this is an integer counting the number of seconds since January
+ * 1st 1970, for {@link EXIF_STRING} this is a string of the form
+ * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a
+ * floating point number where the integer part denotes the day
+ * count and the fractional part denotes the time of day (0.25 means
+ * 6:00, 0.75 means 18:00).
+ *
+ * @param integer $type
+ * the type of the timestamp. This must be one of
+ * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or
+ * {@link JULIAN_DAY_COUNT}.
+ */
+ public function setValue($timestamp, $type = self::UNIX_TIMESTAMP)
+ {
+ switch ($type) {
+ case self::UNIX_TIMESTAMP:
+ $this->day_count = $this->convertUnixToJd($timestamp);
+ $this->seconds = $timestamp % 86400;
+ break;
+
+ case self::EXIF_STRING:
+ /* Clean the timestamp: some timestamps are broken other
+ * separators than ':' and ' '. */
+ $d = preg_split('/[^0-9]+/', $timestamp);
+ for ($i = 0; $i < 6; $i ++) {
+ if (empty($d[$i])) {
+ $d[$i] = 0;
+ }
+ }
+ $this->day_count = $this->convertGregorianToJd($d[0], $d[1], $d[2]);
+ $this->seconds = $d[3] * 3600 + $d[4] * 60 + $d[5];
+ break;
+
+ case self::JULIAN_DAY_COUNT:
+ $this->day_count = (int) floor($timestamp);
+ $this->seconds = (int) (86400 * ($timestamp - floor($timestamp)));
+ break;
+
+ default:
+ throw new PelInvalidArgumentException(
+ 'Expected UNIX_TIMESTAMP (%d), ' . 'EXIF_STRING (%d), or ' . 'JULIAN_DAY_COUNT (%d) for $type, ' .
+ 'got %d.',
+ self::UNIX_TIMESTAMP,
+ self::EXIF_STRING,
+ self::JULIAN_DAY_COUNT,
+ $type);
+ }
+
+ /*
+ * Now finally update the string which will be used when this is
+ * turned into bytes.
+ */
+ parent::setValue($this->getValue(self::EXIF_STRING));
+ }
+
+ // The following four functions are used for converting back and
+ // forth between the date formats. They are used in preference to
+ // the ones from the PHP calendar extension to avoid having to
+ // fiddle with timezones and to avoid depending on the extension.
+ //
+ // See http://www.hermetic.ch/cal_stud/jdn.htm#comp for a reference.
+
+ /**
+ * Converts a date in year/month/day format to a Julian Day count.
+ *
+ * @param integer $year
+ * the year.
+ * @param integer $month
+ * the month, 1 to 12.
+ * @param integer $day
+ * the day in the month.
+ * @return integer the Julian Day count.
+ */
+ public function convertGregorianToJd($year, $month, $day)
+ {
+ // Special case mapping 0/0/0 -> 0
+ if ($year == 0 || $month == 0 || $day == 0) {
+ return 0;
+ }
+
+ $m1412 = ($month <= 2) ? - 1 : 0;
+ return floor((1461 * ($year + 4800 + $m1412)) / 4) + floor((367 * ($month - 2 - 12 * $m1412)) / 12) -
+ floor((3 * floor(($year + 4900 + $m1412) / 100)) / 4) + $day - 32075;
+ }
+
+ /**
+ * Converts a Julian Day count to a year/month/day triple.
+ *
+ * @param
+ * int the Julian Day count.
+ * @return array an array with three entries: year, month, day.
+ */
+ public function convertJdToGregorian($jd)
+ {
+ // Special case mapping 0 -> 0/0/0
+ if ($jd == 0) {
+ return array(
+ 0,
+ 0,
+ 0
+ );
+ }
+
+ $l = $jd + 68569;
+ $n = floor((4 * $l) / 146097);
+ $l = $l - floor((146097 * $n + 3) / 4);
+ $i = floor((4000 * ($l + 1)) / 1461001);
+ $l = $l - floor((1461 * $i) / 4) + 31;
+ $j = floor((80 * $l) / 2447);
+ $d = $l - floor((2447 * $j) / 80);
+ $l = floor($j / 11);
+ $m = $j + 2 - (12 * $l);
+ $y = 100 * ($n - 49) + $i + $l;
+ return array(
+ $y,
+ $m,
+ $d
+ );
+ }
+
+ /**
+ * Converts a UNIX timestamp to a Julian Day count.
+ *
+ * @param integer $timestamp
+ * the timestamp.
+ * @return integer the Julian Day count.
+ */
+ public function convertUnixToJd($timestamp)
+ {
+ return (int) (floor($timestamp / 86400) + 2440588);
+ }
+
+ /**
+ * Converts a Julian Day count to a UNIX timestamp.
+ *
+ * @param integer $jd
+ * the Julian Day count.
+ *
+ * @return mixed $timestamp the integer timestamp or false if the
+ * day count cannot be represented as a UNIX timestamp.
+ */
+ public function convertJdToUnix($jd)
+ {
+ if ($jd > 0) {
+ $timestamp = ($jd - 2440588) * 86400;
+ if ($timestamp >= 0) {
+ return $timestamp;
+ }
+ }
+ return false;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryUndefined.php b/modules/autorotate/lib/pel-master/src/PelEntryUndefined.php
new file mode 100644
index 00000000..a2112c37
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryUndefined.php
@@ -0,0 +1,173 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for holding data of any kind.
+ *
+ * This class can hold bytes of undefined format.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryUndefined extends PelEntry
+{
+
+ /**
+ * Make a new PelEntry that can hold undefined data.
+ *
+ * @param integer $tag
+ * which this entry represents. This
+ * should be one of the constants defined in {@link PelTag},
+ * e.g., {@link PelTag::SCENE_TYPE}, {@link
+ * PelTag::MAKER_NOTE} or any other tag with format {@link
+ * PelFormat::UNDEFINED}.
+ *
+ * @param string $data
+ * the data that this entry will be holding. Since
+ * the format is undefined, no checking will be done on the data. If no data are given, a empty string will be stored
+ */
+ public function __construct($tag, $data = '')
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::UNDEFINED;
+ $this->setValue($data);
+ }
+
+ /**
+ * Set the data of this undefined entry.
+ *
+ * @param string $data
+ * the data that this entry will be holding. Since
+ * the format is undefined, no checking will be done on the data.
+ */
+ public function setValue($data)
+ {
+ $this->components = strlen($data);
+ $this->bytes = $data;
+ }
+
+ /**
+ * Get the data of this undefined entry.
+ *
+ * @return string the data that this entry is holding.
+ */
+ public function getValue()
+ {
+ return $this->bytes;
+ }
+
+ /**
+ * Get the value of this entry as text.
+ *
+ * The value will be returned in a format suitable for presentation.
+ *
+ * @param
+ * boolean some values can be returned in a long or more
+ * brief form, and this parameter controls that.
+ *
+ * @return string the value as text.
+ */
+ public function getText($brief = false)
+ {
+ switch ($this->tag) {
+ case PelTag::FILE_SOURCE:
+ // CC (e->components, 1, v);
+ switch (ord($this->bytes{0})) {
+ case 0x03:
+ return 'DSC';
+ default:
+ return sprintf('0x%02X', ord($this->bytes{0}));
+ }
+ break;
+ case PelTag::SCENE_TYPE:
+ // CC (e->components, 1, v);
+ switch (ord($this->bytes{0})) {
+ case 0x01:
+ return 'Directly photographed';
+ default:
+ return sprintf('0x%02X', ord($this->bytes{0}));
+ }
+ break;
+ case PelTag::COMPONENTS_CONFIGURATION:
+ // CC (e->components, 4, v);
+ $v = '';
+ for ($i = 0; $i < 4; $i ++) {
+ switch (ord($this->bytes{$i})) {
+ case 0:
+ $v .= '-';
+ break;
+ case 1:
+ $v .= 'Y';
+ break;
+ case 2:
+ $v .= 'Cb';
+ break;
+ case 3:
+ $v .= 'Cr';
+ break;
+ case 4:
+ $v .= 'R';
+ break;
+ case 5:
+ $v .= 'G';
+ break;
+ case 6:
+ $v .= 'B';
+ break;
+ default:
+ $v .= 'reserved';
+ break;
+ }
+ if ($i < 3) {
+ $v .= ' ';
+ }
+ }
+ return $v;
+ break;
+ case PelTag::MAKER_NOTE:
+ // TODO: handle maker notes.
+ return $this->components . ' bytes unknown MakerNote data';
+ break;
+ default:
+ return '(undefined)';
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryUserComment.php b/modules/autorotate/lib/pel-master/src/PelEntryUserComment.php
new file mode 100644
index 00000000..20dc8bc3
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryUserComment.php
@@ -0,0 +1,156 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for a user comment.
+ *
+ * This class is used to hold user comments, which can come in several
+ * different character encodings. The Exif standard specifies a
+ * certain format of the {@link PelTag::USER_COMMENT user comment
+ * tag}, and this class will make sure that the format is kept.
+ *
+ * The most basic use of this class simply stores an ASCII encoded
+ * string for later retrieval using {@link getValue}:
+ *
+ *
+ * $entry = new PelEntryUserComment('An ASCII string');
+ * echo $entry->getValue();
+ *
+ *
+ * The string can be encoded with a different encoding, and if so, the
+ * encoding must be given using the second argument. The Exif
+ * standard specifies three known encodings: 'ASCII', 'JIS', and
+ * 'Unicode'. If the user comment is encoded using a character
+ * encoding different from the tree known encodings, then the empty
+ * string should be passed as encoding, thereby specifying that the
+ * encoding is undefined.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryUserComment extends PelEntryUndefined
+{
+
+ /**
+ * The user comment.
+ *
+ * @var string
+ */
+ private $comment;
+
+ /**
+ * The encoding.
+ *
+ * This should be one of 'ASCII', 'JIS', 'Unicode', or ''.
+ *
+ * @var string
+ */
+ private $encoding;
+
+ /**
+ * Make a new entry for holding a user comment.
+ *
+ * @param
+ * string the new user comment.
+ *
+ * @param
+ * string the encoding of the comment. This should be either
+ * 'ASCII', 'JIS', 'Unicode', or the empty string specifying an
+ * undefined encoding.
+ */
+ public function __construct($comment = '', $encoding = 'ASCII')
+ {
+ parent::__construct(PelTag::USER_COMMENT);
+ $this->setValue($comment, $encoding);
+ }
+
+ /**
+ * Set the user comment.
+ *
+ * @param
+ * string the new user comment.
+ *
+ * @param
+ * string the encoding of the comment. This should be either
+ * 'ASCII', 'JIS', 'Unicode', or the empty string specifying an
+ * unknown encoding.
+ */
+ public function setValue($comment = '', $encoding = 'ASCII')
+ {
+ $this->comment = $comment;
+ $this->encoding = $encoding;
+ parent::setValue(str_pad($encoding, 8, chr(0)) . $comment);
+ }
+
+ /**
+ * Returns the user comment.
+ *
+ * The comment is returned with the same character encoding as when
+ * it was set using {@link setValue} or {@link __construct the
+ * constructor}.
+ *
+ * @return string the user comment.
+ */
+ public function getValue()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Returns the encoding.
+ *
+ * @return string the encoding of the user comment.
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Returns the user comment.
+ *
+ * @return string the user comment.
+ */
+ public function getText($brief = false)
+ {
+ return $this->comment;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryVersion.php b/modules/autorotate/lib/pel-master/src/PelEntryVersion.php
new file mode 100644
index 00000000..0edd2696
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryVersion.php
@@ -0,0 +1,182 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class to hold version information.
+ *
+ * There are three Exif entries that hold version information: the
+ * {@link PelTag::EXIF_VERSION}, {@link
+ * PelTag::FLASH_PIX_VERSION}, and {@link
+ * PelTag::INTEROPERABILITY_VERSION} tags. This class manages
+ * those tags.
+ *
+ * The class is used in a very straight-forward way:
+ *
+ * $entry = new PelEntryVersion(PelTag::EXIF_VERSION, 2.2);
+ *
+ * This creates an entry for an file complying to the Exif 2.2
+ * standard. It is easy to test for standards level of an unknown
+ * entry:
+ *
+ * if ($entry->getTag() == PelTag::EXIF_VERSION &&
+ * $entry->getValue() > 2.0) {
+ * echo 'Recent Exif version.';
+ * }
+ *
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryVersion extends PelEntry
+{
+
+ /**
+ * The version held by this entry.
+ *
+ * @var float
+ */
+ private $version;
+
+ /**
+ * Make a new entry for holding a version.
+ *
+ * @param integer $tag
+ * This should be one of {@link
+ * PelTag::EXIF_VERSION}, {@link PelTag::FLASH_PIX_VERSION},
+ * or {@link PelTag::INTEROPERABILITY_VERSION}.
+ *
+ * @param float $version
+ * The size of the entries leave room for
+ * exactly four digits: two digits on either side of the decimal
+ * point.
+ */
+ public function __construct($tag, $version = 0.0)
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::UNDEFINED;
+ $this->setValue($version);
+ }
+
+ /**
+ * Set the version held by this entry.
+ *
+ * @param float $version
+ * The size of the entries leave room for
+ * exactly four digits: two digits on either side of the decimal
+ * point.
+ */
+ public function setValue($version = 0.0)
+ {
+ $this->version = $version;
+ $major = floor($version);
+ $minor = ($version - $major) * 100;
+ $strValue = sprintf('%02.0f%02.0f', $major, $minor);
+ $this->components = strlen($strValue);
+ $this->bytes = $strValue;
+ }
+
+ /**
+ * Return the version held by this entry.
+ *
+ * @return float This will be the same as the value
+ * given to {@link setValue} or {@link __construct the
+ * constructor}.
+ */
+ public function getValue()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Return a text string with the version.
+ *
+ * @param boolean $brief
+ * controls if the output should be brief. Brief
+ * output omits the word 'Version' so the result is just 'Exif x.y'
+ * instead of 'Exif Version x.y' if the entry holds information
+ * about the Exif version --- the output for FlashPix is similar.
+ *
+ * @return string the version number with the type of the tag,
+ * either 'Exif' or 'FlashPix'.
+ */
+ public function getText($brief = false)
+ {
+ $v = $this->version;
+
+ /*
+ * Versions numbers like 2.0 would be output as just 2 if we don't
+ * add the '.0' ourselves.
+ */
+ if (floor($this->version) == $this->version) {
+ $v .= '.0';
+ }
+
+ switch ($this->tag) {
+ case PelTag::EXIF_VERSION:
+ if ($brief) {
+ return Pel::fmt('Exif %s', $v);
+ } else {
+ return Pel::fmt('Exif Version %s', $v);
+ }
+ break;
+ case PelTag::FLASH_PIX_VERSION:
+ if ($brief) {
+ return Pel::fmt('FlashPix %s', $v);
+ } else {
+ return Pel::fmt('FlashPix Version %s', $v);
+ }
+ break;
+ case PelTag::INTEROPERABILITY_VERSION:
+ if ($brief) {
+ return Pel::fmt('Interoperability %s', $v);
+ } else {
+ return Pel::fmt('Interoperability Version %s', $v);
+ }
+ break;
+ }
+
+ if ($brief) {
+ return $v;
+ } else {
+ return Pel::fmt('Version %s', $v);
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelEntryWindowsString.php b/modules/autorotate/lib/pel-master/src/PelEntryWindowsString.php
new file mode 100644
index 00000000..2f043493
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelEntryWindowsString.php
@@ -0,0 +1,161 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class used to manipulate strings in the format Windows XP uses.
+ *
+ * When examining the file properties of an image in Windows XP one
+ * can fill in title, comment, author, keyword, and subject fields.
+ * Filling those fields and pressing OK will result in the data being
+ * written into the Exif data in the image.
+ *
+ * The data is written in a non-standard format and can thus not be
+ * loaded directly --- this class is needed to translate it into
+ * normal strings.
+ *
+ * It is important that entries from this class are only created with
+ * the {@link PelTag::XP_TITLE}, {@link PelTag::XP_COMMENT}, {@link
+ * PelTag::XP_AUTHOR}, {@link PelTag::XP_KEYWORD}, and {@link
+ * PelTag::XP_SUBJECT} tags. If another tag is used the data will no
+ * longer be correctly decoded when reloaded with PEL. (The data will
+ * be loaded as an {@link PelEntryByte} entry, which isn't as useful.)
+ *
+ * This class is to be used as in
+ *
+ * $title = $ifd->getEntry(PelTag::XP_TITLE);
+ * print($title->getValue());
+ * $title->setValue('My favorite cat');
+ *
+ * or if no entry is present one can add a new one with
+ *
+ * $title = new PelEntryWindowsString(PelTag::XP_TITLE, 'A cute dog.');
+ * $ifd->addEntry($title);
+ *
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelEntryWindowsString extends PelEntry
+{
+
+ /**
+ * The string hold by this entry.
+ *
+ * This is the string that was given to the {@link __construct
+ * constructor} or later to {@link setValue}, without any extra NULL
+ * characters or any such nonsense.
+ *
+ * @var string
+ */
+ private $str;
+
+ /**
+ * Make a new PelEntry that can hold a Windows XP specific string.
+ *
+ * @param int $tag
+ * the tag which this entry represents. This should be
+ * one of {@link PelTag::XP_TITLE}, {@link PelTag::XP_COMMENT},
+ * {@link PelTag::XP_AUTHOR}, {@link PelTag::XP_KEYWORD}, and {@link
+ * PelTag::XP_SUBJECT} tags. If another tag is used, then this
+ * entry will be incorrectly reloaded as a {@link PelEntryByte}.
+ *
+ * @param string $str
+ * the string that this entry will represent. It will
+ * be passed to {@link setValue} and thus has to obey its
+ * requirements.
+ */
+ public function __construct($tag, $str = '')
+ {
+ $this->tag = $tag;
+ $this->format = PelFormat::BYTE;
+ $this->setValue($str);
+ }
+
+ /**
+ * Give the entry a new value.
+ *
+ * This will overwrite the previous value. The value can be
+ * retrieved later with the {@link getValue} method.
+ *
+ * @param string $str
+ * the new value of the entry. This should be use the
+ * Latin-1 encoding and be given without any extra NULL characters.
+ */
+ public function setValue($str)
+ {
+ $l = strlen($str);
+
+ $this->components = 2 * ($l + 1);
+ $this->str = $str;
+ $this->bytes = '';
+ for ($i = 0; $i < $l; $i ++) {
+ $this->bytes .= $str{$i} . chr(0x00);
+ }
+
+ $this->bytes .= chr(0x00) . chr(0x00);
+ }
+
+ /**
+ * Return the string of the entry.
+ *
+ * @return string the string held, without any extra NULL
+ * characters. The string will be the same as the one given to
+ * {@link setValue} or to the {@link __construct constructor}.
+ */
+ public function getValue()
+ {
+ return $this->str;
+ }
+
+ /**
+ * Return the string of the entry.
+ *
+ * This methods returns the same as {@link getValue}.
+ *
+ * @param boolean $brief
+ * not used.
+ *
+ * @return string the string held, without any extra NULL
+ * characters. The string will be the same as the one given to
+ * {@link setValue} or to the {@link __construct constructor}.
+ */
+ public function getText($brief = false)
+ {
+ return $this->str;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelException.php b/modules/autorotate/lib/pel-master/src/PelException.php
new file mode 100644
index 00000000..3b663e3c
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelException.php
@@ -0,0 +1,62 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ *
+ * @subpackage PelException
+ */
+class PelException extends \Exception
+{
+
+ /**
+ * Construct a new PEL exception.
+ *
+ * @param string $fmt
+ * an optional format string can be given. It
+ * will be used as a format string for vprintf(). The remaining
+ * arguments will be available for the format string as usual with
+ * vprintf().
+ *
+ * @param mixed ...$args
+ * any number of arguments to be used with
+ * the format string.
+ */
+ public function __construct($fmt, $args = null)
+ {
+ $args = func_get_args();
+ $fmt = array_shift($args);
+ parent::__construct(vsprintf($fmt, $args));
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelExif.php b/modules/autorotate/lib/pel-master/src/PelExif.php
new file mode 100644
index 00000000..c7deb2a4
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelExif.php
@@ -0,0 +1,159 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class representing Exif data.
+ *
+ * Exif data resides as {@link PelJpegContent data} and consists of a
+ * header followed by a number of {@link PelJpegIfd IFDs}.
+ *
+ * The interesting method in this class is {@link getTiff()} which
+ * will return the {@link PelTiff} object which really holds the data
+ * which one normally think of when talking about Exif data. This is
+ * because Exif data is stored as an extension of the TIFF file
+ * format.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelExif extends PelJpegContent
+{
+
+ /**
+ * Exif header.
+ *
+ * The Exif data must start with these six bytes to be considered
+ * valid.
+ */
+ const EXIF_HEADER = "Exif\0\0";
+
+ /**
+ * The PelTiff object contained within.
+ *
+ * @var PelTiff
+ */
+ private $tiff = null;
+
+ /**
+ * Construct a new Exif object.
+ *
+ * The new object will be empty --- use the {@link load()} method to
+ * load Exif data from a {@link PelDataWindow} object, or use the
+ * {@link setTiff()} to change the {@link PelTiff} object, which is
+ * the true holder of the Exif {@link PelEntry entries}.
+ */
+ public function __construct()
+ {
+ // nothing to be done
+ }
+
+ /**
+ * Load and parse Exif data.
+ *
+ * This will populate the object with Exif data, contained as a
+ * {@link PelTiff} object. This TIFF object can be accessed with
+ * the {@link getTiff()} method.
+ *
+ * @param PelDataWindow $d
+ */
+ public function load(PelDataWindow $d)
+ {
+ Pel::debug('Parsing %d bytes of Exif data...', $d->getSize());
+
+ /* There must be at least 6 bytes for the Exif header. */
+ if ($d->getSize() < 6) {
+ throw new PelInvalidDataException('Expected at least 6 bytes of Exif ' . 'data, found just %d bytes.', $d->getSize());
+ }
+ /* Verify the Exif header */
+ if ($d->strcmp(0, self::EXIF_HEADER)) {
+ $d->setWindowStart(strlen(self::EXIF_HEADER));
+ } else {
+ throw new PelInvalidDataException('Exif header not found.');
+ }
+
+ /* The rest of the data is TIFF data. */
+ $this->tiff = new PelTiff();
+ $this->tiff->load($d);
+ }
+
+ /**
+ * Change the TIFF information.
+ *
+ * Exif data is really stored as TIFF data, and this method can be
+ * used to change this data from one {@link PelTiff} object to
+ * another.
+ *
+ * @param PelTiff $tiff
+ * the new TIFF object.
+ */
+ public function setTiff(PelTiff $tiff)
+ {
+ $this->tiff = $tiff;
+ }
+
+ /**
+ * Get the underlying TIFF object.
+ *
+ * The actual Exif data is stored in a {@link PelTiff} object, and
+ * this method provides access to it.
+ *
+ * @return PelTiff the TIFF object with the Exif data.
+ */
+ public function getTiff()
+ {
+ return $this->tiff;
+ }
+
+ /**
+ * Produce bytes for the Exif data.
+ *
+ * @return string bytes representing this object.
+ */
+ public function getBytes()
+ {
+ return self::EXIF_HEADER . $this->tiff->getBytes();
+ }
+
+ /**
+ * Return a string representation of this object.
+ *
+ * @return string a string describing this object. This is mostly
+ * useful for debugging.
+ */
+ public function __toString()
+ {
+ return Pel::tra("Dumping Exif data...\n") . $this->tiff->__toString();
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelFormat.php b/modules/autorotate/lib/pel-master/src/PelFormat.php
new file mode 100644
index 00000000..aa9caa3c
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelFormat.php
@@ -0,0 +1,227 @@
+
+ * @author Johannes Weberhofer
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package
+ *
+ */
+class PelFormat
+{
+
+ /**
+ * Unsigned byte.
+ *
+ * Each component will be an unsigned 8-bit integer with a value
+ * between 0 and 255.
+ *
+ * Modelled with the {@link PelEntryByte} class.
+ */
+ const BYTE = 1;
+
+ /**
+ * ASCII string.
+ *
+ * Each component will be an ASCII character.
+ *
+ * Modelled with the {@link PelEntryAscii} class.
+ */
+ const ASCII = 2;
+
+ /**
+ * Unsigned short.
+ *
+ * Each component will be an unsigned 16-bit integer with a value
+ * between 0 and 65535.
+ *
+ * Modelled with the {@link PelEntryShort} class.
+ */
+ const SHORT = 3;
+
+ /**
+ * Unsigned long.
+ *
+ * Each component will be an unsigned 32-bit integer with a value
+ * between 0 and 4294967295.
+ *
+ * Modelled with the {@link PelEntryLong} class.
+ */
+ const LONG = 4;
+
+ /**
+ * Unsigned rational number.
+ *
+ * Each component will consist of two unsigned 32-bit integers
+ * denoting the enumerator and denominator. Each integer will have
+ * a value between 0 and 4294967295.
+ *
+ * Modelled with the {@link PelEntryRational} class.
+ */
+ const RATIONAL = 5;
+
+ /**
+ * Signed byte.
+ *
+ * Each component will be a signed 8-bit integer with a value
+ * between -128 and 127.
+ *
+ * Modelled with the {@link PelEntrySByte} class.
+ */
+ const SBYTE = 6;
+
+ /**
+ * Undefined byte.
+ *
+ * Each component will be a byte with no associated interpretation.
+ *
+ * Modelled with the {@link PelEntryUndefined} class.
+ */
+ const UNDEFINED = 7;
+
+ /**
+ * Signed short.
+ *
+ * Each component will be a signed 16-bit integer with a value
+ * between -32768 and 32767.
+ *
+ * Modelled with the {@link PelEntrySShort} class.
+ */
+ const SSHORT = 8;
+
+ /**
+ * Signed long.
+ *
+ * Each component will be a signed 32-bit integer with a value
+ * between -2147483648 and 2147483647.
+ *
+ * Modelled with the {@link PelEntrySLong} class.
+ */
+ const SLONG = 9;
+
+ /**
+ * Signed rational number.
+ *
+ * Each component will consist of two signed 32-bit integers
+ * denoting the enumerator and denominator. Each integer will have
+ * a value between -2147483648 and 2147483647.
+ *
+ * Modelled with the {@link PelEntrySRational} class.
+ */
+ const SRATIONAL = 10;
+
+ /**
+ * Floating point number.
+ *
+ * Entries with this format are not currently implemented.
+ */
+ const FLOAT = 11;
+
+ /**
+ * Double precision floating point number.
+ *
+ * Entries with this format are not currently implemented.
+ */
+ const DOUBLE = 12;
+
+ /**
+ * Values for format's short names
+ */
+ protected static $formatName = array(
+ self::ASCII => 'Ascii',
+ self::BYTE => 'Byte',
+ self::SHORT => 'Short',
+ self::LONG => 'Long',
+ self::RATIONAL => 'Rational',
+ self::SBYTE => 'SByte',
+ self::SSHORT => 'SShort',
+ self::SLONG => 'SLong',
+ self::SRATIONAL => 'SRational',
+ self::FLOAT => 'Float',
+ self::DOUBLE => 'Double',
+ self::UNDEFINED => 'Undefined'
+ );
+
+ protected static $formatLength = array(
+ self::ASCII => 1,
+ self::BYTE => 1,
+ self::SHORT => 2,
+ self::LONG => 4,
+ self::RATIONAL => 8,
+ self::SBYTE => 1,
+ self::SSHORT => 2,
+ self::SLONG => 4,
+ self::SRATIONAL => 8,
+ self::FLOAT => 4,
+ self::DOUBLE => 8,
+ self::UNDEFINED => 1
+ );
+
+ /**
+ * Returns the name of a format like 'Ascii' for the {@link ASCII} format
+ *
+ * @param integer $type
+ * as defined in {@link PelFormat}
+ * @return string
+ */
+ public static function getName($type)
+ {
+ if (array_key_exists($type, self::$formatName)) {
+ return self::$formatName[$type];
+ } else {
+ return Pel::fmt('Unknown format: 0x%X', $type);
+ }
+ }
+
+ /**
+ * Return the size of components in a given format in bytes needed to store one component with the
+ * given format.
+ *
+ * @param integer $type
+ * as defined in {@link PelFormat}
+ * @return integer|string
+ */
+ public static function getSize($type)
+ {
+ if (array_key_exists($type, self::$formatLength)) {
+ return self::$formatLength[$type];
+ } else {
+ return Pel::fmt('Unknown format: 0x%X', $type);
+ }
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelIfd.php b/modules/autorotate/lib/pel-master/src/PelIfd.php
new file mode 100644
index 00000000..d0ac6e88
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelIfd.php
@@ -0,0 +1,1225 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * 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
+ * @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.
+ */
+ public 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 $d
+ * the data window that will provide the data.
+ *
+ * @param int $offset
+ * the offset within the window where the directory will
+ * be found.
+ */
+ public 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 integer $tag
+ * the tag of the entry as defined in {@link PelTag}.
+ *
+ * @param integer $format
+ * the format of the entry as defined in {@link PelFormat}.
+ *
+ * @param int $components
+ * the components in the entry.
+ *
+ * @param PelDataWindow $data
+ * the data which will be used to construct the
+ * entry.
+ *
+ * @return PelEntry a newly created entry, holding the data given.
+ */
+ public 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(), ' '));
+ if (! isset($v[1])) {
+ Pel::maybeThrow(new PelException('Invalid copyright: %s', $data->getBytes()));
+ // when not in strict mode, set empty copyright and continue
+ $v[1] = '';
+ }
+ 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)));
+ }
+ // this point can not be reached
+ 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);
+ }
+ // This point can be reached! Continue with default.
+ 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, rtrim($data->getBytes(0), "\0"));
+
+ 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 $d
+ * the data from which the thumbnail will be
+ * extracted.
+ *
+ * @param int $offset
+ * the offset into the data.
+ *
+ * @param int $length
+ * 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 $d
+ * the thumbnail data.
+ */
+ public 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}.
+ */
+ public 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 int $tag
+ * the tag.
+ *
+ * @return boolean true if the tag is considered valid in this IFD,
+ * false otherwise.
+ *
+ * @see getValidTags()
+ */
+ public 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.
+ */
+ public 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::DOCUMENT_NAME,
+ 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,
+ PelTag::RATING,
+ PelTag::RATING_PERCENT
+ );
+
+ 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::OFFSET_TIME,
+ PelTag::OFFSET_TIME_ORIGINAL,
+ PelTag::OFFSET_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::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 $type
+ * one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1},
+ * {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
+ * PelIfd::INTEROPERABILITY}.
+ *
+ * @return string the name of type.
+ */
+ public 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.
+ */
+ public function getName()
+ {
+ return $this->getTypeName($this->type);
+ }
+
+ /**
+ * Adds an entry to the directory.
+ *
+ * @param PelEntry $e
+ * 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?
+ */
+ public 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:
+ *
+ *
+ * if (isset($ifd[PelTag::FNUMBER]))
+ * // ... do something with the F-number.
+ *
+ *
+ * @param int $tag
+ * the offset to check.
+ *
+ * @return boolean whether the tag exists.
+ */
+ public 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:
+ *
+ *
+ * $entry = $ifd[PelTag::FNUMBER];
+ *
+ *
+ * @param int $tag
+ * 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.
+ */
+ public 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:
+ *
+ *
+ * $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 int $tag
+ * unused.
+ *
+ * @param PelEntry $e
+ * the new value.
+ */
+ public 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:
+ *
+ *
+ * unset($ifd[PelTag::EXPOSURE_BIAS_VALUE])
+ *
+ *
+ * @param int $tag
+ * the offset to delete.
+ */
+ public function offsetUnset($tag)
+ {
+ unset($this->entries[$tag]);
+ }
+
+ /**
+ * Retrieve an entry.
+ *
+ * @param int $tag
+ * the tag identifying the entry.
+ *
+ * @return PelEntry the entry associated with the tag, or null if no
+ * such entry exists.
+ */
+ public 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
+ */
+ public function getEntries()
+ {
+ return $this->entries;
+ }
+
+ /**
+ * 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.
+ */
+ public 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?
+ */
+ public function getThumbnailData()
+ {
+ if ($this->thumb_data !== null) {
+ return $this->thumb_data->getBytes();
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Make this directory point to a new directory.
+ *
+ * @param PelIfd $i
+ * the IFD that this directory will point to.
+ */
+ public 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.
+ */
+ public function getNextIfd()
+ {
+ return $this->next;
+ }
+
+ /**
+ * Check if this is the last IFD.
+ *
+ * @return boolean true if there are no following IFD, false
+ * otherwise.
+ */
+ public function isLastIfd()
+ {
+ return $this->next === null;
+ }
+
+ /**
+ * Add a sub-IFD.
+ *
+ * Any previous sub-IFD of the same type will be overwritten.
+ *
+ * @param PelIfd $sub
+ * the sub IFD. The type of must be one of {@link
+ * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link
+ * PelIfd::INTEROPERABILITY}.
+ */
+ public function addSubIfd(PelIfd $sub)
+ {
+ $this->sub[$sub->type] = $sub;
+ }
+
+ /**
+ * Return a sub IFD.
+ *
+ * @param int $type
+ * 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.
+ */
+ public 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.
+ */
+ public 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 $offset
+ * the offset of the first byte of this directory.
+ *
+ * @param PelByteOrder $order
+ * 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}.
+ */
+ public 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;
+ } else {
+ // PelConvert::BIG_ENDIAN is the default used by PelConvert
+ $tag = PelConvert::BIG_ENDIAN;
+ }
+ /* 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.
+ */
+ public 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;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelIfdException.php b/modules/autorotate/lib/pel-master/src/PelIfdException.php
new file mode 100644
index 00000000..e50d1e5b
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelIfdException.php
@@ -0,0 +1,46 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception indicating a general problem with the IFD.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelIfdException extends PelException
+{
+ // do nothing
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelInvalidArgumentException.php b/modules/autorotate/lib/pel-master/src/PelInvalidArgumentException.php
new file mode 100644
index 00000000..e72759d9
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelInvalidArgumentException.php
@@ -0,0 +1,47 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception throw if an invalid argument is passed.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelInvalidArgumentException extends PelException
+{
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelInvalidDataException.php b/modules/autorotate/lib/pel-master/src/PelInvalidDataException.php
new file mode 100644
index 00000000..66a48258
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelInvalidDataException.php
@@ -0,0 +1,47 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception throw if invalid data is found.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelInvalidDataException extends PelException
+{
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelJpeg.php b/modules/autorotate/lib/pel-master/src/PelJpeg.php
new file mode 100644
index 00000000..5eff887d
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelJpeg.php
@@ -0,0 +1,669 @@
+
+ * $jpeg = new PelJpeg($filename);
+ * $exif = $jpeg->getExif();
+ * $tiff = $exif->getTiff();
+ * $ifd0 = $tiff->getIfd();
+ * $exif = $ifd0->getSubIfd(PelIfd::EXIF);
+ * $ifd1 = $ifd0->getNextIfd();
+ *
+ *
+ * The $idf0 and $ifd1 variables will then be two {@link PelTiff TIFF}
+ * {@link PelIfd Image File Directories}, in which the data is stored
+ * under the keys found in {@link PelTag}.
+ *
+ * Should one have some image data (in the form of a {@link
+ * PelDataWindow}) of an unknown type, then the {@link
+ * PelJpeg::isValid()} function is handy: it will quickly test if the
+ * data could be valid JPEG data. The {@link PelTiff::isValid()}
+ * function does the same for TIFF images.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelJpeg
+{
+
+ /**
+ * The sections in the JPEG data.
+ *
+ * A JPEG file is built up as a sequence of sections, each section
+ * is identified with a {@link PelJpegMarker}. Some sections can
+ * occur more than once in the JPEG stream (the {@link
+ * PelJpegMarker::DQT DQT} and {@link PelJpegMarker::DHT DTH}
+ * markers for example) and so this is an array of ({@link
+ * PelJpegMarker}, {@link PelJpegContent}) pairs.
+ *
+ * The content can be either generic {@link PelJpegContent JPEG
+ * content} or {@link PelExif Exif data}.
+ *
+ * @var array
+ */
+ protected $sections = array();
+
+ /**
+ * The JPEG image data.
+ *
+ * @var PelDataWindow
+ */
+ private $jpeg_data = null;
+
+ /**
+ * Construct a new JPEG object.
+ *
+ * The new object will be empty unless an argument is given from
+ * which it can initialize itself. This can either be the filename
+ * of a JPEG image, a {@link PelDataWindow} object or a PHP image
+ * resource handle.
+ *
+ * New Exif data (in the form of a {@link PelExif} object) can be
+ * inserted with the {@link setExif()} method:
+ *
+ *
+ * $jpeg = new PelJpeg($data);
+ * // Create container for the Exif information:
+ * $exif = new PelExif();
+ * // Now Add a PelTiff object with a PelIfd object with one or more
+ * // PelEntry objects to $exif... Finally add $exif to $jpeg:
+ * $jpeg->setExif($exif);
+ *
+ *
+ * @param
+ * mixed the data that this JPEG. This can either be a
+ * filename, a {@link PelDataWindow} object, or a PHP image resource
+ * handle.
+ */
+ public function __construct($data = false)
+ {
+ if ($data === false) {
+ return;
+ }
+
+ if (is_string($data)) {
+ Pel::debug('Initializing PelJpeg object from %s', $data);
+ $this->loadFile($data);
+ } elseif ($data instanceof PelDataWindow) {
+ Pel::debug('Initializing PelJpeg object from PelDataWindow.');
+ $this->load($data);
+ } elseif (is_resource($data) && get_resource_type($data) == 'gd') {
+ Pel::debug('Initializing PelJpeg object from image resource.');
+ $this->load(new PelDataWindow($data));
+ } else {
+ throw new PelInvalidArgumentException('Bad type for $data: %s', gettype($data));
+ }
+ }
+
+ /**
+ * JPEG sections start with 0xFF. The first byte that is not
+ * 0xFF is a marker (hopefully).
+ *
+ * @param PelDataWindow $d
+ *
+ * @return integer
+ */
+ protected static function getJpgSectionStart($d)
+ {
+ for ($i = 0; $i < 7; $i ++) {
+ if ($d->getByte($i) != 0xFF) {
+ break;
+ }
+ }
+ return $i;
+ }
+
+ /**
+ * Load data into a JPEG object.
+ *
+ * The data supplied will be parsed and turned into an object
+ * structure representing the image. This structure can then be
+ * manipulated and later turned back into an string of bytes.
+ *
+ * This methods can be called at any time after a JPEG object has
+ * been constructed, also after the {@link appendSection()} has been
+ * called to append custom sections. Loading several JPEG images
+ * into one object will accumulate the sections, but there will only
+ * be one {@link PelJpegMarker::SOS} section at any given time.
+ *
+ * @param
+ * PelDataWindow the data that will be turned into JPEG
+ * sections.
+ */
+ public function load(PelDataWindow $d)
+ {
+ Pel::debug('Parsing %d bytes...', $d->getSize());
+
+ /* JPEG data is stored in big-endian format. */
+ $d->setByteOrder(PelConvert::BIG_ENDIAN);
+
+ /*
+ * Run through the data to read the sections in the image. After
+ * each section is read, the start of the data window will be
+ * moved forward, and after the last section we'll terminate with
+ * no data left in the window.
+ */
+ while ($d->getSize() > 0) {
+ $i = $this->getJpgSectionStart($d);
+
+ $marker = $d->getByte($i);
+
+ if (!PelJpegMarker::isValid($marker)) {
+ throw new PelJpegInvalidMarkerException($marker, $i);
+ }
+
+ /*
+ * Move window so first byte becomes first byte in this
+ * section.
+ */
+ $d->setWindowStart($i + 1);
+
+ if ($marker == PelJpegMarker::SOI || $marker == PelJpegMarker::EOI) {
+ $content = new PelJpegContent(new PelDataWindow());
+ $this->appendSection($marker, $content);
+ } else {
+ /*
+ * Read the length of the section. The length includes the
+ * two bytes used to store the length.
+ */
+ $len = $d->getShort(0) - 2;
+
+ Pel::debug('Found %s section of length %d', PelJpegMarker::getName($marker), $len);
+
+ /* Skip past the length. */
+ $d->setWindowStart(2);
+
+ if ($marker == PelJpegMarker::APP1) {
+ try {
+ $content = new PelExif();
+ $content->load($d->getClone(0, $len));
+ } catch (PelInvalidDataException $e) {
+ /*
+ * We store the data as normal JPEG content if it could
+ * not be parsed as Exif data.
+ */
+ $content = new PelJpegContent($d->getClone(0, $len));
+ }
+
+ $this->appendSection($marker, $content);
+ /* Skip past the data. */
+ $d->setWindowStart($len);
+ } elseif ($marker == PelJpegMarker::COM) {
+ $content = new PelJpegComment();
+ $content->load($d->getClone(0, $len));
+ $this->appendSection($marker, $content);
+ $d->setWindowStart($len);
+ } else {
+ $content = new PelJpegContent($d->getClone(0, $len));
+ $this->appendSection($marker, $content);
+ /* Skip past the data. */
+ $d->setWindowStart($len);
+
+ /* In case of SOS, image data will follow. */
+ if ($marker == PelJpegMarker::SOS) {
+ /*
+ * Some images have some trailing (garbage?) following the
+ * EOI marker. To handle this we seek backwards until we
+ * find the EOI marker. Any trailing content is stored as
+ * a PelJpegContent object.
+ */
+
+ $length = $d->getSize();
+ while ($d->getByte($length - 2) != 0xFF || $d->getByte($length - 1) != PelJpegMarker::EOI) {
+ $length --;
+ }
+
+ $this->jpeg_data = $d->getClone(0, $length - 2);
+ Pel::debug('JPEG data: ' . $this->jpeg_data->__toString());
+
+ /* Append the EOI. */
+ $this->appendSection(PelJpegMarker::EOI, new PelJpegContent(new PelDataWindow()));
+
+ /* Now check to see if there are any trailing data. */
+ if ($length != $d->getSize()) {
+ Pel::maybeThrow(new PelException('Found trailing content ' . 'after EOI: %d bytes', $d->getSize() - $length));
+ $content = new PelJpegContent($d->getClone($length));
+ /*
+ * We don't have a proper JPEG marker for trailing
+ * garbage, so we just use 0x00...
+ */
+ $this->appendSection(0x00, $content);
+ }
+
+ /* Done with the loop. */
+ break;
+ }
+ }
+ }
+ } /* while ($d->getSize() > 0) */
+ }
+
+ /**
+ * Load data from a file into a JPEG object.
+ *
+ * @param
+ * string the filename. This must be a readable file.
+ */
+ public function loadFile($filename)
+ {
+ $this->load(new PelDataWindow(file_get_contents($filename)));
+ }
+
+ /**
+ * Set Exif data.
+ *
+ * Use this to set the Exif data in the image. This will overwrite
+ * any old Exif information in the image.
+ *
+ * @param
+ * PelExif the Exif data.
+ */
+ public function setExif(PelExif $exif)
+ {
+ $app0_offset = 1;
+ $app1_offset = - 1;
+
+ /* Search through all sections looking for APP0 or APP1. */
+ $sections_count = count($this->sections);
+ for ($i = 0; $i < $sections_count; $i ++) {
+ if (! empty($this->sections[$i][0])) {
+ $section = $this->sections[$i];
+ if ($section[0] == PelJpegMarker::APP0) {
+ $app0_offset = $i;
+ } elseif (($section[0] == PelJpegMarker::APP1) && ($section[1] instanceof PelExif)) {
+ $app1_offset = $i;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Store the Exif data at the appropriate place, either where the
+ * old Exif data was stored ($app1_offset) or right after APP0
+ * ($app0_offset+1).
+ */
+ if ($app1_offset > 0) {
+ $this->sections[$app1_offset][1] = $exif;
+ } else {
+ $this->insertSection(PelJpegMarker::APP1, $exif, $app0_offset + 1);
+ }
+ }
+
+ /**
+ * Set ICC data.
+ *
+ * Use this to set the ICC data in the image. This will overwrite
+ * any old ICC information in the image.
+ *
+ * @param
+ * PelJpegContent the ICC data.
+ */
+ public function setICC(PelJpegContent $icc)
+ {
+ $app1_offset = 1;
+ $app2_offset = - 1;
+
+ /* Search through all sections looking for APP0 or APP1. */
+ $count_sections = count($this->sections);
+ for ($i = 0; $i < $count_sections; $i ++) {
+ if (! empty($this->sections[$i][0])) {
+ if ($this->sections[$i][0] == PelJpegMarker::APP1) {
+ $app1_offset = $i;
+ } elseif ($this->sections[$i][0] == PelJpegMarker::APP2) {
+ $app2_offset = $i;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Store the Exif data at the appropriate place, either where the
+ * old Exif data was stored ($app1_offset) or right after APP0
+ * ($app0_offset+1).
+ */
+ if ($app2_offset > 0) {
+ $this->sections[$app1_offset][1] = $icc;
+ } else {
+ $this->insertSection(PelJpegMarker::APP2, $icc, $app1_offset + 1);
+ }
+ }
+
+ /**
+ * Get first valid APP1 Exif section data.
+ *
+ * Use this to get the @{link PelExif Exif data} stored.
+ *
+ * @return PelExif the Exif data found or null if the image has no
+ * Exif data.
+ */
+ public function getExif()
+ {
+ $sections_count = count($this->sections);
+ for ($i = 0; $i < $sections_count; $i ++) {
+ $section = $this->getSection(PelJpegMarker::APP1, $i);
+ if ($section instanceof PelExif) {
+ return $section;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get ICC data.
+ *
+ * Use this to get the @{link PelJpegContent ICC data} stored.
+ *
+ * @return PelJpegContent the ICC data found or null if the image has no
+ * ICC data.
+ */
+ public function getICC()
+ {
+ $icc = $this->getSection(PelJpegMarker::APP2);
+ if ($icc instanceof PelJpegContent) {
+ return $icc;
+ }
+ return null;
+ }
+
+ /**
+ * Clear any Exif data.
+ *
+ * This method will only clear @{link PelJpegMarker::APP1} EXIF sections found.
+ */
+ public function clearExif()
+ {
+ $idx = 0;
+ while ($idx < count($this->sections)) {
+ $s = $this->sections[$idx];
+ if (($s[0] == PelJpegMarker::APP1) && ($s[1] instanceof PelExif)) {
+ array_splice($this->sections, $idx, 1);
+ $idx--;
+ } else {
+ ++ $idx;
+ }
+ }
+ }
+
+ /**
+ * Append a new section.
+ *
+ * Used only when loading an image. If it used again later, then the
+ * section will end up after the @{link PelJpegMarker::EOI EOI
+ * marker} and will probably not be useful.
+ *
+ * Please use @{link setExif()} instead if you intend to add Exif
+ * information to an image as that function will know the right
+ * place to insert the data.
+ *
+ * @param
+ * PelJpegMarker the marker identifying the new section.
+ *
+ * @param
+ * PelJpegContent the content of the new section.
+ */
+ public function appendSection($marker, PelJpegContent $content)
+ {
+ $this->sections[] = array(
+ $marker,
+ $content
+ );
+ }
+
+ /**
+ * Insert a new section.
+ *
+ * Please use @{link setExif()} instead if you intend to add Exif
+ * information to an image as that function will know the right
+ * place to insert the data.
+ *
+ * @param
+ * PelJpegMarker the marker for the new section.
+ *
+ * @param
+ * PelJpegContent the content of the new section.
+ *
+ * @param
+ * int the offset where the new section will be inserted ---
+ * use 0 to insert it at the very beginning, use 1 to insert it
+ * between sections 1 and 2, etc.
+ */
+ public function insertSection($marker, PelJpegContent $content, $offset)
+ {
+ array_splice($this->sections, $offset, 0, array(
+ array(
+ $marker,
+ $content
+ )
+ ));
+ }
+
+ /**
+ * Get a section corresponding to a particular marker.
+ *
+ * Please use the {@link getExif()} if you just need the Exif data.
+ *
+ * This will search through the sections of this JPEG object,
+ * looking for a section identified with the specified {@link
+ * PelJpegMarker marker}. The {@link PelJpegContent content} will
+ * then be returned. The optional argument can be used to skip over
+ * some of the sections. So if one is looking for the, say, third
+ * {@link PelJpegMarker::DHT DHT} section one would do:
+ *
+ *
+ * $dht3 = $jpeg->getSection(PelJpegMarker::DHT, 2);
+ *
+ *
+ * @param
+ * PelJpegMarker the marker identifying the section.
+ *
+ * @param
+ * int the number of sections to be skipped. This must be a
+ * non-negative integer.
+ *
+ * @return PelJpegContent the content found, or null if there is no
+ * content available.
+ */
+ public function getSection($marker, $skip = 0)
+ {
+ foreach ($this->sections as $s) {
+ if ($s[0] == $marker) {
+ if ($skip > 0) {
+ $skip --;
+ } else {
+ return $s[1];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all sections.
+ *
+ * @return array an array of ({@link PelJpegMarker}, {@link
+ * PelJpegContent}) pairs. Each pair is an array with the {@link
+ * PelJpegMarker} as the first element and the {@link
+ * PelJpegContent} as the second element, so the return type is an
+ * array of arrays.
+ *
+ * So to loop through all the sections in a given JPEG image do
+ * this:
+ *
+ *
+ * foreach ($jpeg->getSections() as $section) {
+ * $marker = $section[0];
+ * $content = $section[1];
+ * // Use $marker and $content here.
+ * }
+ *
+ *
+ * instead of this:
+ *
+ *
+ * foreach ($jpeg->getSections() as $marker => $content) {
+ * // Does not work the way you would think...
+ * }
+ *
+ *
+ * The problem is that there could be several sections with the same
+ * marker, and thus a simple associative array does not suffice.
+ */
+ public function getSections()
+ {
+ return $this->sections;
+ }
+
+ /**
+ * Turn this JPEG object into bytes.
+ *
+ * The bytes returned by this method is ready to be stored in a file
+ * as a valid JPEG image. Use the {@link saveFile()} convenience
+ * method to do this.
+ *
+ * @return string bytes representing this JPEG object, including all
+ * its sections and their associated data.
+ */
+ public function getBytes()
+ {
+ $bytes = '';
+
+ foreach ($this->sections as $section) {
+ $m = $section[0];
+ $c = $section[1];
+
+ /* Write the marker */
+ $bytes .= "\xFF" . PelJpegMarker::getBytes($m);
+ /* Skip over empty markers. */
+ if ($m == PelJpegMarker::SOI || $m == PelJpegMarker::EOI) {
+ continue;
+ }
+
+ $data = $c->getBytes();
+ $size = strlen($data) + 2;
+
+ $bytes .= PelConvert::shortToBytes($size, PelConvert::BIG_ENDIAN);
+ $bytes .= $data;
+
+ /* In case of SOS, we need to write the JPEG data. */
+ if ($m == PelJpegMarker::SOS) {
+ $bytes .= $this->jpeg_data->getBytes();
+ }
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Save the JPEG object as a JPEG image in a file.
+ *
+ * @param
+ * string the filename to save in. An existing file with the
+ * same name will be overwritten!
+ *
+ * @return integer|FALSE The number of bytes that were written to the
+ * file, or FALSE on failure.
+ */
+ public function saveFile($filename)
+ {
+ return file_put_contents($filename, $this->getBytes());
+ }
+
+ /**
+ * Make a string representation of this JPEG object.
+ *
+ * This is mainly usefull for debugging. It will show the structure
+ * of the image, and its sections.
+ *
+ * @return string debugging information about this JPEG object.
+ */
+ public function __toString()
+ {
+ $str = Pel::tra("Dumping JPEG data...\n");
+ $count_sections = count($this->sections);
+ for ($i = 0; $i < $count_sections; $i ++) {
+ $m = $this->sections[$i][0];
+ $c = $this->sections[$i][1];
+ $str .= Pel::fmt("Section %d (marker 0x%02X - %s):\n", $i, $m, PelJpegMarker::getName($m));
+ $str .= Pel::fmt(" Description: %s\n", PelJpegMarker::getDescription($m));
+
+ if ($m == PelJpegMarker::SOI || $m == PelJpegMarker::EOI) {
+ continue;
+ }
+
+ if ($c instanceof PelExif) {
+ $str .= Pel::tra(" Content : Exif data\n");
+ $str .= $c->__toString() . "\n";
+ } elseif ($c instanceof PelJpegComment) {
+ $str .= Pel::fmt(" Content : %s\n", $c->getValue());
+ } else {
+ $str .= Pel::tra(" Content : Unknown\n");
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Test data to see if it could be a valid JPEG image.
+ *
+ * The function will only look at the first few bytes of the data,
+ * and try to determine if it could be a valid JPEG image based on
+ * those bytes. This means that the check is more like a heuristic
+ * than a rigorous check.
+ *
+ * @param
+ * PelDataWindow the bytes that will be checked.
+ *
+ * @return boolean true if the bytes look like the beginning of a
+ * JPEG image, false otherwise.
+ *
+ * @see PelTiff::isValid()
+ */
+ public static function isValid(PelDataWindow $d)
+ {
+ /* JPEG data is stored in big-endian format. */
+ $d->setByteOrder(PelConvert::BIG_ENDIAN);
+
+ $i = self::getJpgSectionStart($d);
+
+ return $d->getByte($i) == PelJpegMarker::SOI;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelJpegComment.php b/modules/autorotate/lib/pel-master/src/PelJpegComment.php
new file mode 100644
index 00000000..6d04d1a9
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelJpegComment.php
@@ -0,0 +1,118 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class representing JPEG comments.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelJpegComment extends PelJpegContent
+{
+
+ /**
+ * The comment.
+ *
+ * @var string
+ */
+ private $comment = '';
+
+ /**
+ * Construct a new JPEG comment.
+ *
+ * The new comment will contain the string given.
+ *
+ * @param string $comment
+ */
+ public function __construct($comment = '')
+ {
+ $this->comment = $comment;
+ }
+
+ /**
+ * Load and parse data.
+ *
+ * This will load the comment from the data window passed.
+ *
+ * @param PelDataWindow $d
+ */
+ public function load(PelDataWindow $d)
+ {
+ $this->comment = $d->getBytes();
+ }
+
+ /**
+ * Update the value with a new comment.
+ *
+ * Any old comment will be overwritten.
+ *
+ * @param string $comment
+ * the new comment.
+ */
+ public function setValue($comment)
+ {
+ $this->comment = $comment;
+ }
+
+ /**
+ * Get the comment.
+ *
+ * @return string the comment.
+ */
+ public function getValue()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Turn this comment into bytes.
+ *
+ * @return string bytes representing this comment.
+ */
+ public function getBytes()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Return a string representation of this object.
+ *
+ * @return string the same as {@link getValue()}.
+ */
+ public function __toString()
+ {
+ return $this->getValue();
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelJpegContent.php b/modules/autorotate/lib/pel-master/src/PelJpegContent.php
new file mode 100644
index 00000000..c2822771
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelJpegContent.php
@@ -0,0 +1,78 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class representing content in a JPEG file.
+ *
+ * A JPEG file consists of a sequence of each of which has an
+ * associated {@link PelJpegMarker marker} and some content. This
+ * class represents the content, and this basic type is just a simple
+ * holder of such content, represented by a {@link PelDataWindow}
+ * object. The {@link PelExif} class is an example of more
+ * specialized JPEG content.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelJpegContent
+{
+
+ private $data = null;
+
+ /**
+ * Make a new piece of JPEG content.
+ *
+ * @param PelDataWindow $data
+ * the content.
+ */
+ public function __construct(PelDataWindow $data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Return the bytes of the content.
+ *
+ * @return string bytes representing this JPEG content. These bytes
+ * will match the bytes given to {@link __construct the
+ * constructor}.
+ */
+ public function getBytes()
+ {
+ return $this->data->getBytes();
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelJpegInvalidMarkerException.php b/modules/autorotate/lib/pel-master/src/PelJpegInvalidMarkerException.php
new file mode 100644
index 00000000..5eb7637f
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelJpegInvalidMarkerException.php
@@ -0,0 +1,55 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL)
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelJpegInvalidMarkerException extends PelException
+{
+
+ /**
+ * Construct a new invalid marker exception.
+ * The exception will contain a message describing the error,
+ * including the byte found and the offset of the offending byte.
+ *
+ * @param int $marker
+ * the byte found.
+ *
+ * @param int $offset
+ * the offset in the data.
+ */
+ public function __construct($marker, $offset)
+ {
+ parent::__construct('Invalid marker found at offset %d: 0x%2X', $offset, $marker);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelJpegMarker.php b/modules/autorotate/lib/pel-master/src/PelJpegMarker.php
new file mode 100644
index 00000000..4d1bd33c
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelJpegMarker.php
@@ -0,0 +1,542 @@
+
+ * @author Johannes Weberhofer
+ *
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+class PelJpegMarker
+{
+
+ /**
+ * Encoding (baseline)
+ */
+ const SOF0 = 0xC0;
+
+ /**
+ * Encoding (extended sequential)
+ */
+ const SOF1 = 0xC1;
+
+ /**
+ * Encoding (progressive)
+ */
+ const SOF2 = 0xC2;
+
+ /**
+ * Encoding (lossless)
+ */
+ const SOF3 = 0xC3;
+
+ /**
+ * Define Huffman table
+ */
+ const DHT = 0xC4;
+
+ /**
+ * Encoding (differential sequential)
+ */
+ const SOF5 = 0xC5;
+
+ /**
+ * Encoding (differential progressive)
+ */
+ const SOF6 = 0xC6;
+
+ /**
+ * Encoding (differential lossless)
+ */
+ const SOF7 = 0xC7;
+
+ /**
+ * Extension
+ */
+ const JPG = 0xC8;
+
+ /**
+ * Encoding (extended sequential, arithmetic)
+ */
+ const SOF9 = 0xC9;
+
+ /**
+ * Encoding (progressive, arithmetic)
+ */
+ const SOF10 = 0xCA;
+
+ /**
+ * Encoding (lossless, arithmetic)
+ */
+ const SOF11 = 0xCB;
+
+ /**
+ * Define arithmetic coding conditioning
+ */
+ const DAC = 0xCC;
+
+ /**
+ * Encoding (differential sequential, arithmetic)
+ */
+ const SOF13 = 0xCD;
+
+ /**
+ * Encoding (differential progressive, arithmetic)
+ */
+ const SOF14 = 0xCE;
+
+ /**
+ * Encoding (differential lossless, arithmetic)
+ */
+ const SOF15 = 0xCF;
+
+ /**
+ * Restart 0
+ */
+ const RST0 = 0xD0;
+
+ /**
+ * Restart 1
+ */
+ const RST1 = 0xD1;
+
+ /**
+ * Restart 2
+ */
+ const RST2 = 0xD2;
+
+ /**
+ * Restart 3
+ */
+ const RST3 = 0xD3;
+
+ /**
+ * Restart 4
+ */
+ const RST4 = 0xD4;
+
+ /**
+ * Restart 5
+ */
+ const RST5 = 0xD5;
+
+ /**
+ * Restart 6
+ */
+ const RST6 = 0xD6;
+
+ /**
+ * Restart 7
+ */
+ const RST7 = 0xD7;
+
+ /**
+ * Start of image
+ */
+ const SOI = 0xD8;
+
+ /**
+ * End of image
+ */
+ const EOI = 0xD9;
+
+ /**
+ * Start of scan
+ */
+ const SOS = 0xDA;
+
+ /**
+ * Define quantization table
+ */
+ const DQT = 0xDB;
+
+ /**
+ * Define number of lines
+ */
+ const DNL = 0xDC;
+
+ /**
+ * Define restart interval
+ */
+ const DRI = 0xDD;
+
+ /**
+ * Define hierarchical progression
+ */
+ const DHP = 0xDE;
+
+ /**
+ * Expand reference component
+ */
+ const EXP = 0xDF;
+
+ /**
+ * Application segment 0
+ */
+ const APP0 = 0xE0;
+
+ /**
+ * Application segment 1
+ *
+ * When a JPEG image contains Exif data, the data will normally be
+ * stored in this section and a call to {@link PelJpeg::getExif()}
+ * will return a {@link PelExif} object representing it.
+ */
+ const APP1 = 0xE1;
+
+ /**
+ * Application segment 2
+ */
+ const APP2 = 0xE2;
+
+ /**
+ * Application segment 3
+ */
+ const APP3 = 0xE3;
+
+ /**
+ * Application segment 4
+ */
+ const APP4 = 0xE4;
+
+ /**
+ * Application segment 5
+ */
+ const APP5 = 0xE5;
+
+ /**
+ * Application segment 6
+ */
+ const APP6 = 0xE6;
+
+ /**
+ * Application segment 7
+ */
+ const APP7 = 0xE7;
+
+ /**
+ * Application segment 8
+ */
+ const APP8 = 0xE8;
+
+ /**
+ * Application segment 9
+ */
+ const APP9 = 0xE9;
+
+ /**
+ * Application segment 10
+ */
+ const APP10 = 0xEA;
+
+ /**
+ * Application segment 11
+ */
+ const APP11 = 0xEB;
+
+ /**
+ * Application segment 12
+ */
+ const APP12 = 0xEC;
+
+ /**
+ * Application segment 13
+ */
+ const APP13 = 0xED;
+
+ /**
+ * Application segment 14
+ */
+ const APP14 = 0xEE;
+
+ /**
+ * Application segment 15
+ */
+ const APP15 = 0xEF;
+
+ /**
+ * Extension 0
+ */
+ const JPG0 = 0xF0;
+
+ /**
+ * Extension 1
+ */
+ const JPG1 = 0xF1;
+
+ /**
+ * Extension 2
+ */
+ const JPG2 = 0xF2;
+
+ /**
+ * Extension 3
+ */
+ const JPG3 = 0xF3;
+
+ /**
+ * Extension 4
+ */
+ const JPG4 = 0xF4;
+
+ /**
+ * Extension 5
+ */
+ const JPG5 = 0xF5;
+
+ /**
+ * Extension 6
+ */
+ const JPG6 = 0xF6;
+
+ /**
+ * Extension 7
+ */
+ const JPG7 = 0xF7;
+
+ /**
+ * Extension 8
+ */
+ const JPG8 = 0xF8;
+
+ /**
+ * Extension 9
+ */
+ const JPG9 = 0xF9;
+
+ /**
+ * Extension 10
+ */
+ const JPG10 = 0xFA;
+
+ /**
+ * Extension 11
+ */
+ const JPG11 = 0xFB;
+
+ /**
+ * Extension 12
+ */
+ const JPG12 = 0xFC;
+
+ /**
+ * Extension 13
+ */
+ const JPG13 = 0xFD;
+
+ /**
+ * Comment
+ */
+ const COM = 0xFE;
+
+ /**
+ * Values for marker's short names
+ */
+ protected static $jpegMarkerShort = array(
+ self::SOF0 => 'SOF0',
+ self::SOF1 => 'SOF1',
+ self::SOF2 => 'SOF2',
+ self::SOF3 => 'SOF3',
+ self::SOF5 => 'SOF5',
+ self::SOF6 => 'SOF6',
+ self::SOF7 => 'SOF7',
+ self::SOF9 => 'SOF9',
+ self::SOF10 => 'SOF10',
+ self::SOF11 => 'SOF11',
+ self::SOF13 => 'SOF13',
+ self::SOF14 => 'SOF14',
+ self::SOF15 => 'SOF15',
+ self::SOI => 'SOI',
+ self::EOI => 'EOI',
+ self::SOS => 'SOS',
+ self::COM => 'COM',
+ self::DHT => 'DHT',
+ self::JPG => 'JPG',
+ self::DAC => 'DAC',
+ self::RST0 => 'RST0',
+ self::RST1 => 'RST1',
+ self::RST2 => 'RST2',
+ self::RST3 => 'RST3',
+ self::RST4 => 'RST4',
+ self::RST5 => 'RST5',
+ self::RST6 => 'RST6',
+ self::RST7 => 'RST7',
+ self::DQT => 'DQT',
+ self::DNL => 'DNL',
+ self::DRI => 'DRI',
+ self::DHP => 'DHP',
+ self::EXP => 'EXP',
+ self::APP0 => 'APP0',
+ self::APP1 => 'APP1',
+ self::APP2 => 'APP2',
+ self::APP3 => 'APP3',
+ self::APP4 => 'APP4',
+ self::APP5 => 'APP5',
+ self::APP6 => 'APP6',
+ self::APP7 => 'APP7',
+ self::APP8 => 'APP8',
+ self::APP9 => 'APP9',
+ self::APP10 => 'APP10',
+ self::APP11 => 'APP11',
+ self::APP12 => 'APP12',
+ self::APP13 => 'APP13',
+ self::APP14 => 'APP14',
+ self::APP15 => 'APP15',
+ self::JPG0 => 'JPG0',
+ self::JPG1 => 'JPG1',
+ self::JPG2 => 'JPG2',
+ self::JPG3 => 'JPG3',
+ self::JPG4 => 'JPG4',
+ self::JPG5 => 'JPG5',
+ self::JPG6 => 'JPG6',
+ self::JPG7 => 'JPG7',
+ self::JPG8 => 'JPG8',
+ self::JPG9 => 'JPG9',
+ self::JPG10 => 'JPG10',
+ self::JPG11 => 'JPG11',
+ self::JPG12 => 'JPG12',
+ self::JPG13 => 'JPG13',
+ self::COM => 'COM'
+ );
+
+ /**
+ * Values for marker's descriptions names.
+ */
+ protected static $jpegMarkerDescriptions = array(
+ self::SOF0 => 'Encoding (baseline)',
+ self::SOF1 => 'Encoding (extended sequential)',
+ self::SOF2 => 'Encoding (progressive)',
+ self::SOF3 => 'Encoding (lossless)',
+ self::SOF5 => 'Encoding (differential sequential)',
+ self::SOF6 => 'Encoding (differential progressive)',
+ self::SOF7 => 'Encoding (differential lossless)',
+ self::SOF9 => 'Encoding (extended sequential, arithmetic)',
+ self::SOF10 => 'Encoding (progressive, arithmetic)',
+ self::SOF11 => 'Encoding (lossless, arithmetic)',
+ self::SOF13 => 'Encoding (differential sequential, arithmetic)',
+ self::SOF14 => 'Encoding (differential progressive, arithmetic)',
+ self::SOF15 => 'Encoding (differential lossless, arithmetic)',
+ self::SOI => 'Start of image',
+ self::EOI => 'End of image',
+ self::SOS => 'Start of scan',
+ self::COM => 'Comment',
+ self::DHT => 'Define Huffman table',
+ self::JPG => 'Extension',
+ self::DAC => 'Define arithmetic coding conditioning',
+ 'RST' => 'Restart %d',
+ self::DQT => 'Define quantization table',
+ self::DNL => 'Define number of lines',
+ self::DRI => 'Define restart interval',
+ self::DHP => 'Define hierarchical progression',
+ self::EXP => 'Expand reference component',
+ 'APP' => 'Application segment %d',
+ 'JPG' => 'Extension %d',
+ self::COM => 'Comment'
+ );
+
+ /**
+ * Check if a byte is a valid JPEG marker.
+ * If the byte is recognized true is returned, otherwise false will be returned.
+ *
+ * @param integer $marker
+ * the marker as defined in {@link PelJpegMarker}
+ *
+ * @return boolean
+ */
+ public static function isValid($marker)
+ {
+ return ($marker >= self::SOF0 && $marker <= self::COM);
+ }
+
+ /**
+ * Turn a JPEG marker into bytes.
+ * This will be a string with just a single byte since all JPEG markers are simply single bytes.
+ *
+ * @param integer $marker
+ * the marker as defined in {@link PelJpegMarker}
+ *
+ * @return string
+ */
+ public static function getBytes($marker)
+ {
+ return chr($marker);
+ }
+
+ /**
+ * Return the short name for a marker, e.g., 'SOI' for the Start
+ * of Image marker.
+ *
+ * @param integer $marker
+ * the marker as defined in {@link PelJpegMarker}
+ *
+ * @return string
+ */
+ public static function getName($marker)
+ {
+ if (array_key_exists($marker, self::$jpegMarkerShort)) {
+ return self::$jpegMarkerShort[$marker];
+ } else {
+ return Pel::fmt('Unknown marker: 0x%02X', $marker);
+ }
+ }
+
+ /**
+ * Returns a description of a JPEG marker.
+ *
+ * @param integer $marker
+ * the marker as defined in {@link PelJpegMarker}
+ *
+ * @return string
+ */
+ public static function getDescription($marker)
+ {
+ if (array_key_exists($marker, self::$jpegMarkerShort)) {
+ if (array_key_exists($marker, self::$jpegMarkerDescriptions)) {
+ return self::$jpegMarkerDescriptions[$marker];
+ } else {
+ $splitted = preg_split(
+ "/(\d+)/",
+ self::$jpegMarkerShort[$marker],
+ - 1,
+ PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ if ((count($splitted) == 2) && array_key_exists($splitted[0], self::$jpegMarkerDescriptions)) {
+ return Pel::fmt(self::$jpegMarkerDescriptions[$splitted[0]], $splitted[1]);
+ }
+ }
+ }
+ return Pel::fmt('Unknown marker: 0x%02X', $marker);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelOverflowException.php b/modules/autorotate/lib/pel-master/src/PelOverflowException.php
new file mode 100644
index 00000000..634985b1
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelOverflowException.php
@@ -0,0 +1,68 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception cast when numbers overflow.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelOverflowException extends PelException
+{
+
+ /**
+ * Construct a new overflow exception.
+ *
+ * @param int $v
+ * the value that is out of range.
+ *
+ * @param int $min
+ * the minimum allowed value.
+ *
+ * @param int $max
+ * the maximum allowed value.
+ */
+ public function __construct($v, $min, $max)
+ {
+ parent::__construct('Value %.0f out of range [%.0f, %.0f]', $v, $min, $max);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelTag.php b/modules/autorotate/lib/pel-master/src/PelTag.php
new file mode 100644
index 00000000..eb6766ed
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelTag.php
@@ -0,0 +1,1798 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class with static methods for Exif tags.
+ *
+ * This class defines the constants that represents the Exif tags
+ * known to PEL. They are supposed to be used whenever one needs to
+ * specify an Exif tag, and they will be denoted by the pseudo-type
+ * {@link PelTag} throughout the documentation.
+ *
+ * Please note that the constrains on the format and number of
+ * components given here are advisory only. To follow the Exif
+ * specification one should obey them, but there is nothing that
+ * prevents you from creating an {@link IMAGE_LENGTH} entry with two
+ * or more components, even though the standard says that there should
+ * be exactly one component.
+ *
+ * All the methods in this class are static and should be called with
+ * the Exif tag on which they should operate.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelTag
+{
+
+ /**
+ * Interoperability index.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 4.
+ */
+ const INTEROPERABILITY_INDEX = 0x0001;
+
+ /**
+ * Interoperability version.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: 4.
+ */
+ const INTEROPERABILITY_VERSION = 0x0002;
+
+ /**
+ * Image width.
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const IMAGE_WIDTH = 0x0100;
+
+ /**
+ * Image length.
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const IMAGE_LENGTH = 0x0101;
+
+ /**
+ * Number of bits per component.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 3.
+ */
+ const BITS_PER_SAMPLE = 0x0102;
+
+ /**
+ * Compression scheme.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const COMPRESSION = 0x0103;
+
+ /**
+ * Pixel composition.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const PHOTOMETRIC_INTERPRETATION = 0x0106;
+
+ /**
+ * Fill Order
+ *
+ * Format: Unknown.
+ *
+ * Components: Unknown.
+ */
+ const FILL_ORDER = 0x010A;
+
+ /**
+ * Document Name
+ *
+ * Format: {@link PelEntryAscii}.
+ *
+ * Components: any number.
+ */
+ const DOCUMENT_NAME = 0x010D;
+
+ /**
+ * Image Description
+ *
+ * Format: {@link PelEntryAscii}.
+ *
+ * Components: any number.
+ */
+ const IMAGE_DESCRIPTION = 0x010E;
+
+ /**
+ * Manufacturer
+ *
+ * Format: {@link PelEntryAscii}.
+ *
+ * Components: any number.
+ */
+ const MAKE = 0x010F;
+
+ /**
+ * Model
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const MODEL = 0x0110;
+
+ /**
+ * Strip Offsets
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: any number.
+ */
+ const STRIP_OFFSETS = 0x0111;
+
+ /**
+ * Orientation of image.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const ORIENTATION = 0x0112;
+
+ /**
+ * Number of components.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SAMPLES_PER_PIXEL = 0x0115;
+
+ /**
+ * Rows per Strip
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const ROWS_PER_STRIP = 0x0116;
+
+ /**
+ * Strip Byte Count
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: any number.
+ */
+ const STRIP_BYTE_COUNTS = 0x0117;
+
+ /**
+ * Image resolution in width direction.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const X_RESOLUTION = 0x011A;
+
+ /**
+ * Image resolution in height direction.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const Y_RESOLUTION = 0x011B;
+
+ /**
+ * Image data arrangement.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const PLANAR_CONFIGURATION = 0x011C;
+
+ /**
+ * Unit of X and Y resolution.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const RESOLUTION_UNIT = 0x0128;
+
+ /**
+ * Transfer function.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 3.
+ */
+ const TRANSFER_FUNCTION = 0x012D;
+
+ /**
+ * Software used.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const SOFTWARE = 0x0131;
+
+ /**
+ * File change date and time.
+ *
+ * Format: {@link PelFormat::ASCII}, modelled by the {@link
+ * PelEntryTime} class.
+ *
+ * Components: 20.
+ */
+ const DATE_TIME = 0x0132;
+
+ /**
+ * Person who created the image.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const ARTIST = 0x013B;
+
+ /**
+ * White point chromaticity.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 2.
+ */
+ const WHITE_POINT = 0x013E;
+
+ /**
+ * Chromaticities of primaries.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 6.
+ */
+ const PRIMARY_CHROMATICITIES = 0x013F;
+
+ /**
+ * Transfer Range
+ *
+ * Format: Unknown.
+ *
+ * Components: Unknown.
+ */
+ const TRANSFER_RANGE = 0x0156;
+
+ /**
+ * JPEGProc
+ *
+ * Format: Unknown.
+ *
+ * Components: Unknown.
+ */
+ const JPEG_PROC = 0x0200;
+
+ /**
+ * Offset to JPEG SOI.
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const JPEG_INTERCHANGE_FORMAT = 0x0201;
+
+ /**
+ * Bytes of JPEG data.
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const JPEG_INTERCHANGE_FORMAT_LENGTH = 0x0202;
+
+ /**
+ * Color space transformation matrix coefficients.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const YCBCR_COEFFICIENTS = 0x0211;
+
+ /**
+ * Subsampling ratio of Y to C.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 2.
+ */
+ const YCBCR_SUB_SAMPLING = 0x0212;
+
+ /**
+ * Y and C positioning.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const YCBCR_POSITIONING = 0x0213;
+
+ /**
+ * Pair of black and white reference values.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 6.
+ */
+ const REFERENCE_BLACK_WHITE = 0x0214;
+
+ /**
+ * Related Image File Format
+ *
+ * Format: Unknown.
+ *
+ * Components: Unknown.
+ */
+ const RELATED_IMAGE_FILE_FORMAT = 0x1000;
+
+ /**
+ * Related Image Width
+ *
+ * Format: Unknown, probably {@link PelFormat::SHORT}?
+ *
+ * Components: Unknown, probably 1.
+ */
+ const RELATED_IMAGE_WIDTH = 0x1001;
+
+ /**
+ * Related Image Length
+ *
+ * Format: Unknown, probably {@link PelFormat::SHORT}?
+ *
+ * Components: Unknown, probably 1.
+ */
+ const RELATED_IMAGE_LENGTH = 0x1002;
+
+ /**
+ * Rating
+ *
+ * Format: {@link PelFormat::SHORT}
+ *
+ * Components: 1.
+ */
+ const RATING = 0x4746;
+
+ /**
+ * Rating percent
+ *
+ * Format: {@link PelFormat::SHORT}
+ *
+ * Components: 1.
+ */
+ const RATING_PERCENT = 0x4749;
+
+ /**
+ * CFA Repeat Pattern Dim.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 2.
+ */
+ const CFA_REPEAT_PATTERN_DIM = 0x828D;
+
+ /**
+ * Battery level.
+ *
+ * Format: Unknown.
+ *
+ * Components: Unknown.
+ */
+ const BATTERY_LEVEL = 0x828F;
+
+ /**
+ * Copyright holder.
+ *
+ * Format: {@link PelFormat::ASCII}, modelled by the {@link
+ * PelEntryCopyright} class.
+ *
+ * Components: any number.
+ */
+ const COPYRIGHT = 0x8298;
+
+ /**
+ * Exposure Time
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const EXPOSURE_TIME = 0x829A;
+
+ /**
+ * FNumber
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FNUMBER = 0x829D;
+
+ /**
+ * IPTC/NAA
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: any number.
+ */
+ const IPTC_NAA = 0x83BB;
+
+ /**
+ * Exif IFD Pointer
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const EXIF_IFD_POINTER = 0x8769;
+
+ /**
+ * Inter Color Profile
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: any number.
+ */
+ const INTER_COLOR_PROFILE = 0x8773;
+
+ /**
+ * Exposure Program
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const EXPOSURE_PROGRAM = 0x8822;
+
+ /**
+ * Spectral Sensitivity
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const SPECTRAL_SENSITIVITY = 0x8824;
+
+ /**
+ * GPS Info IFD Pointer
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const GPS_INFO_IFD_POINTER = 0x8825;
+
+ /**
+ * ISO Speed Ratings
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 2.
+ */
+ const ISO_SPEED_RATINGS = 0x8827;
+
+ /**
+ * OECF
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: any number.
+ */
+ const OECF = 0x8828;
+
+ /**
+ * Exif version.
+ *
+ * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link
+ * PelEntryVersion} class.
+ *
+ * Components: 4.
+ */
+ const EXIF_VERSION = 0x9000;
+
+ /**
+ * Date and time of original data generation.
+ *
+ * Format: {@link PelFormat::ASCII}, modelled by the {@link
+ * PelEntryTime} class.
+ *
+ * Components: 20.
+ */
+ const DATE_TIME_ORIGINAL = 0x9003;
+
+ /**
+ * Date and time of digital data generation.
+ *
+ * Format: {@link PelFormat::ASCII}, modelled by the {@link
+ * PelEntryTime} class.
+ *
+ * Components: 20.
+ */
+ const DATE_TIME_DIGITIZED = 0x9004;
+
+ /**
+ * Offset time (timezone) of file change time.
+ *
+ * Format: {@link PelFormat::ASCII}
+ *
+ * Components: 7.
+ */
+ const OFFSET_TIME = 0x9010;
+
+ /**
+ * Offset time (timezone) of original data generation.
+ *
+ * Format: {@link PelFormat::ASCII}
+ *
+ * Components: 7.
+ */
+ const OFFSET_TIME_ORIGINAL = 0x9011;
+
+ /**
+ * Offset time (timezone) of digital data generation.
+ *
+ * Format: {@link PelFormat::ASCII}
+ *
+ * Components: 7.
+ */
+ const OFFSET_TIME_DIGITIZED = 0x9012;
+
+ /**
+ * Meaning of each component.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: 4.
+ */
+ const COMPONENTS_CONFIGURATION = 0x9101;
+
+ /**
+ * Image compression mode.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const COMPRESSED_BITS_PER_PIXEL = 0x9102;
+
+ /**
+ * Shutter speed
+ *
+ * Format: {@link PelFormat::SRATIONAL}.
+ *
+ * Components: 1.
+ */
+ const SHUTTER_SPEED_VALUE = 0x9201;
+
+ /**
+ * Aperture
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const APERTURE_VALUE = 0x9202;
+
+ /**
+ * Brightness
+ *
+ * Format: {@link PelFormat::SRATIONAL}.
+ *
+ * Components: 1.
+ */
+ const BRIGHTNESS_VALUE = 0x9203;
+
+ /**
+ * Exposure Bias
+ *
+ * Format: {@link PelFormat::SRATIONAL}.
+ *
+ * Components: 1.
+ */
+ const EXPOSURE_BIAS_VALUE = 0x9204;
+
+ /**
+ * Max Aperture Value
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const MAX_APERTURE_VALUE = 0x9205;
+
+ /**
+ * Subject Distance
+ *
+ * Format: {@link PelFormat::SRATIONAL}.
+ *
+ * Components: 1.
+ */
+ const SUBJECT_DISTANCE = 0x9206;
+
+ /**
+ * Metering Mode
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const METERING_MODE = 0x9207;
+
+ /**
+ * Light Source
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const LIGHT_SOURCE = 0x9208;
+
+ /**
+ * Flash
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const FLASH = 0x9209;
+
+ /**
+ * Focal Length
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FOCAL_LENGTH = 0x920A;
+
+ /**
+ * Subject Area
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 4.
+ */
+ const SUBJECT_AREA = 0x9214;
+
+ /**
+ * Maker Note
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: any number.
+ */
+ const MAKER_NOTE = 0x927C;
+
+ /**
+ * User Comment
+ *
+ * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link
+ * PelEntryUserComment} class.
+ *
+ * Components: any number.
+ */
+ const USER_COMMENT = 0x9286;
+
+ /**
+ * SubSec Time
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const SUB_SEC_TIME = 0x9290;
+
+ /**
+ * SubSec Time Original
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const SUB_SEC_TIME_ORIGINAL = 0x9291;
+
+ /**
+ * SubSec Time Digitized
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const SUB_SEC_TIME_DIGITIZED = 0x9292;
+
+ /**
+ * Windows XP Title
+ *
+ * Format: {@link PelFormat::BYTE}, modelled by the
+ * {@link PelEntryWindowsString} class.
+ *
+ * Components: any number.
+ */
+ const XP_TITLE = 0x9C9B;
+
+ /**
+ * Windows XP Comment
+ *
+ * Format: {@link PelFormat::BYTE}, modelled by the
+ * {@link PelEntryWindowsString} class.
+ *
+ * Components: any number.
+ */
+ const XP_COMMENT = 0x9C9C;
+
+ /**
+ * Windows XP Author
+ *
+ * Format: {@link PelFormat::BYTE}, modelled by the
+ * {@link PelEntryWindowsString} class.
+ *
+ * Components: any number.
+ */
+ const XP_AUTHOR = 0x9C9D;
+
+ /**
+ * Windows XP Keywords
+ *
+ * Format: {@link PelFormat::BYTE}, modelled by the
+ * {@link PelEntryWindowsString} class.
+ *
+ * Components: any number.
+ */
+ const XP_KEYWORDS = 0x9C9E;
+
+ /**
+ * Windows XP Subject
+ *
+ * Format: {@link PelFormat::BYTE}, modelled by the
+ * {@link PelEntryWindowsString} class.
+ *
+ * Components: any number.
+ */
+ const XP_SUBJECT = 0x9C9F;
+
+ /**
+ * Supported Flashpix version
+ *
+ * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link
+ * PelEntryVersion} class.
+ *
+ * Components: 4.
+ */
+ const FLASH_PIX_VERSION = 0xA000;
+
+ /**
+ * Color space information.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const COLOR_SPACE = 0xA001;
+
+ /**
+ * Valid image width.
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const PIXEL_X_DIMENSION = 0xA002;
+
+ /**
+ * Valid image height.
+ *
+ * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const PIXEL_Y_DIMENSION = 0xA003;
+
+ /**
+ * Related audio file.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: any number.
+ */
+ const RELATED_SOUND_FILE = 0xA004;
+
+ /**
+ * Interoperability IFD Pointer
+ *
+ * Format: {@link PelFormat::LONG}.
+ *
+ * Components: 1.
+ */
+ const INTEROPERABILITY_IFD_POINTER = 0xA005;
+
+ /**
+ * Flash energy.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FLASH_ENERGY = 0xA20B;
+
+ /**
+ * Spatial frequency response.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: any number.
+ */
+ const SPATIAL_FREQUENCY_RESPONSE = 0xA20C;
+
+ /**
+ * Focal plane X resolution.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FOCAL_PLANE_X_RESOLUTION = 0xA20E;
+
+ /**
+ * Focal plane Y resolution.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FOCAL_PLANE_Y_RESOLUTION = 0xA20F;
+
+ /**
+ * Focal plane resolution unit.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const FOCAL_PLANE_RESOLUTION_UNIT = 0xA210;
+
+ /**
+ * Subject location.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SUBJECT_LOCATION = 0xA214;
+
+ /**
+ * Exposure index.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const EXPOSURE_INDEX = 0xA215;
+
+ /**
+ * Sensing method.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SENSING_METHOD = 0xA217;
+
+ /**
+ * File source.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: 1.
+ */
+ const FILE_SOURCE = 0xA300;
+
+ /**
+ * Scene type.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: 1.
+ */
+ const SCENE_TYPE = 0xA301;
+
+ /**
+ * CFA pattern.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: any number.
+ */
+ const CFA_PATTERN = 0xA302;
+
+ /**
+ * Custom image processing.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const CUSTOM_RENDERED = 0xA401;
+
+ /**
+ * Exposure mode.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const EXPOSURE_MODE = 0xA402;
+
+ /**
+ * White balance.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const WHITE_BALANCE = 0xA403;
+
+ /**
+ * Digital zoom ratio.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const DIGITAL_ZOOM_RATIO = 0xA404;
+
+ /**
+ * Focal length in 35mm film.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const FOCAL_LENGTH_IN_35MM_FILM = 0xA405;
+
+ /**
+ * Scene capture type.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SCENE_CAPTURE_TYPE = 0xA406;
+
+ /**
+ * Gain control.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const GAIN_CONTROL = 0xA407;
+
+ /**
+ * Contrast.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const CONTRAST = 0xA408;
+
+ /**
+ * Saturation.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SATURATION = 0xA409;
+
+ /**
+ * Sharpness.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SHARPNESS = 0xA40A;
+
+ /**
+ * Device settings description.
+ *
+ * This tag indicates information on the picture-taking conditions
+ * of a particular camera model. The tag is used only to indicate
+ * the picture-taking conditions in the reader.
+ */
+ const DEVICE_SETTING_DESCRIPTION = 0xA40B;
+
+ /**
+ * Subject distance range.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const SUBJECT_DISTANCE_RANGE = 0xA40C;
+
+ /**
+ * Image unique ID.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 32.
+ */
+ const IMAGE_UNIQUE_ID = 0xA420;
+
+ /**
+ * Gamma.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GAMMA = 0xA500;
+
+ /**
+ * PrintIM
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: unknown.
+ */
+ const PRINT_IM = 0xC4A5;
+
+ /**
+ * GPS tag version.
+ *
+ * Format: {@link PelFormat::BYTE}.
+ *
+ * Components: 4.
+ */
+ const GPS_VERSION_ID = 0x0000;
+
+ /**
+ * North or South Latitude.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_LATITUDE_REF = 0x0001;
+
+ /**
+ * Latitude.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const GPS_LATITUDE = 0x0002;
+
+ /**
+ * East or West Longitude.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_LONGITUDE_REF = 0x0003;
+
+ /**
+ * Longitude.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const GPS_LONGITUDE = 0x0004;
+
+ /**
+ * Altitude reference.
+ *
+ * Format: {@link PelFormat::BYTE}.
+ *
+ * Components: 1.
+ */
+ const GPS_ALTITUDE_REF = 0x0005;
+
+ /**
+ * Altitude.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_ALTITUDE = 0x0006;
+
+ /**
+ * GPS time (atomic clock).
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const GPS_TIME_STAMP = 0x0007;
+
+ /**
+ * GPS satellites used for measurement.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: Any.
+ */
+ const GPS_SATELLITES = 0x0008;
+
+ /**
+ * GPS receiver status.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_STATUS = 0x0009;
+
+ /**
+ * GPS measurement mode.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_MEASURE_MODE = 0x000A;
+
+ /**
+ * Measurement precision.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_DOP = 0x000B;
+
+ /**
+ * Speed unit.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_SPEED_REF = 0x000C;
+
+ /**
+ * Speed of GPS receiver.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_SPEED = 0x000D;
+
+ /**
+ * Reference for direction of movement.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_TRACK_REF = 0x000E;
+
+ /**
+ * Direction of movement.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_TRACK = 0x000F;
+
+ /**
+ * Reference for direction of image.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_IMG_DIRECTION_REF = 0x0010;
+
+ /**
+ * Direction of image.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_IMG_DIRECTION = 0x0011;
+
+ /**
+ * Geodetic survey data used.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: Any.
+ */
+ const GPS_MAP_DATUM = 0x0012;
+
+ /**
+ * Reference for latitude of destination.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_DEST_LATITUDE_REF = 0x0013;
+
+ /**
+ * Latitude of destination.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const GPS_DEST_LATITUDE = 0x0014;
+
+ /**
+ * Reference for longitude of destination.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_DEST_LONGITUDE_REF = 0x0015;
+
+ /**
+ * Longitude of destination.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 3.
+ */
+ const GPS_DEST_LONGITUDE = 0x0016;
+
+ /**
+ * Reference for bearing of destination.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_DEST_BEARING_REF = 0x0017;
+
+ /**
+ * Bearing of destination.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_DEST_BEARING = 0x0018;
+
+ /**
+ * Reference for distance to destination.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 2.
+ */
+ const GPS_DEST_DISTANCE_REF = 0x0019;
+
+ /**
+ * Distance to destination.
+ *
+ * Format: {@link PelFormat::RATIONAL}.
+ *
+ * Components: 1.
+ */
+ const GPS_DEST_DISTANCE = 0x001A;
+
+ /**
+ * Name of GPS processing method.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: Any.
+ */
+ const GPS_PROCESSING_METHOD = 0x001B;
+
+ /**
+ * Name of GPS area.
+ *
+ * Format: {@link PelFormat::UNDEFINED}.
+ *
+ * Components: Any.
+ */
+ const GPS_AREA_INFORMATION = 0x001C;
+
+ /**
+ * GPS date.
+ *
+ * Format: {@link PelFormat::ASCII}.
+ *
+ * Components: 11.
+ */
+ const GPS_DATE_STAMP = 0x001D;
+
+ /**
+ * GPS differential correction.
+ *
+ * Format: {@link PelFormat::SHORT}.
+ *
+ * Components: 1.
+ */
+ const GPS_DIFFERENTIAL = 0x001E;
+
+ /**
+ * Values for tags short names.
+ */
+ protected static $exifTagsShort= array(
+ self::INTEROPERABILITY_INDEX => 'InteroperabilityIndex',
+ self::INTEROPERABILITY_VERSION => 'InteroperabilityVersion',
+ self::IMAGE_WIDTH => 'ImageWidth',
+ self::IMAGE_LENGTH => 'ImageLength',
+ self::BITS_PER_SAMPLE => 'BitsPerSample',
+ self::COMPRESSION => 'Compression',
+ self::PHOTOMETRIC_INTERPRETATION => 'PhotometricInterpretation',
+ self::FILL_ORDER => 'FillOrder',
+ self::DOCUMENT_NAME => 'DocumentName',
+ self::IMAGE_DESCRIPTION => 'ImageDescription',
+ self::MAKE => 'Make',
+ self::MODEL => 'Model',
+ self::STRIP_OFFSETS => 'StripOffsets',
+ self::ORIENTATION => 'Orientation',
+ self::SAMPLES_PER_PIXEL => 'SamplesPerPixel',
+ self::ROWS_PER_STRIP => 'RowsPerStrip',
+ self::STRIP_BYTE_COUNTS => 'StripByteCounts',
+ self::X_RESOLUTION => 'XResolution',
+ self::Y_RESOLUTION => 'YResolution',
+ self::PLANAR_CONFIGURATION => 'PlanarConfiguration',
+ self::RESOLUTION_UNIT => 'ResolutionUnit',
+ self::TRANSFER_FUNCTION => 'TransferFunction',
+ self::SOFTWARE => 'Software',
+ self::DATE_TIME => 'DateTime',
+ self::ARTIST => 'Artist',
+ self::WHITE_POINT => 'WhitePoint',
+ self::PRIMARY_CHROMATICITIES => 'PrimaryChromaticities',
+ self::TRANSFER_RANGE => 'TransferRange',
+ self::JPEG_PROC => 'JPEGProc',
+ self::JPEG_INTERCHANGE_FORMAT => 'JPEGInterchangeFormat',
+ self::JPEG_INTERCHANGE_FORMAT_LENGTH => 'JPEGInterchangeFormatLength',
+ self::YCBCR_COEFFICIENTS => 'YCbCrCoefficients',
+ self::YCBCR_SUB_SAMPLING => 'YCbCrSubSampling',
+ self::YCBCR_POSITIONING => 'YCbCrPositioning',
+ self::REFERENCE_BLACK_WHITE => 'ReferenceBlackWhite',
+ self::RELATED_IMAGE_FILE_FORMAT => 'RelatedImageFileFormat',
+ self::RELATED_IMAGE_WIDTH => 'RelatedImageWidth',
+ self::RELATED_IMAGE_LENGTH => 'RelatedImageLength',
+ self::RATING => 'Rating',
+ self::RATING_PERCENT => 'RatingPercent',
+ self::CFA_REPEAT_PATTERN_DIM => 'CFARepeatPatternDim',
+ self::CFA_PATTERN => 'CFAPattern',
+ self::BATTERY_LEVEL => 'BatteryLevel',
+ self::COPYRIGHT => 'Copyright',
+ self::EXPOSURE_TIME => 'ExposureTime',
+ self::FNUMBER => 'FNumber',
+ self::IPTC_NAA => 'IPTC/NAA',
+ self::EXIF_IFD_POINTER => 'ExifIFDPointer',
+ self::INTER_COLOR_PROFILE => 'InterColorProfile',
+ self::EXPOSURE_PROGRAM => 'ExposureProgram',
+ self::SPECTRAL_SENSITIVITY => 'SpectralSensitivity',
+ self::GPS_INFO_IFD_POINTER => 'GPSInfoIFDPointer',
+ self::ISO_SPEED_RATINGS => 'ISOSpeedRatings',
+ self::OECF => 'OECF',
+ self::EXIF_VERSION => 'ExifVersion',
+ self::DATE_TIME_ORIGINAL => 'DateTimeOriginal',
+ self::DATE_TIME_DIGITIZED => 'DateTimeDigitized',
+ self::OFFSET_TIME => 'OffsetTime',
+ self::OFFSET_TIME_ORIGINAL => 'OffsetTimeOriginal',
+ self::OFFSET_TIME_DIGITIZED => 'OffsetTimeDigitized',
+ self::COMPONENTS_CONFIGURATION => 'ComponentsConfiguration',
+ self::COMPRESSED_BITS_PER_PIXEL => 'CompressedBitsPerPixel',
+ self::SHUTTER_SPEED_VALUE => 'ShutterSpeedValue',
+ self::APERTURE_VALUE => 'ApertureValue',
+ self::BRIGHTNESS_VALUE => 'BrightnessValue',
+ self::EXPOSURE_BIAS_VALUE => 'ExposureBiasValue',
+ self::MAX_APERTURE_VALUE => 'MaxApertureValue',
+ self::SUBJECT_DISTANCE => 'SubjectDistance',
+ self::METERING_MODE => 'MeteringMode',
+ self::LIGHT_SOURCE => 'LightSource',
+ self::FLASH => 'Flash',
+ self::FOCAL_LENGTH => 'FocalLength',
+ self::MAKER_NOTE => 'MakerNote',
+ self::USER_COMMENT => 'UserComment',
+ self::SUB_SEC_TIME => 'SubSecTime',
+ self::SUB_SEC_TIME_ORIGINAL => 'SubSecTimeOriginal',
+ self::SUB_SEC_TIME_DIGITIZED => 'SubSecTimeDigitized',
+ self::XP_TITLE => 'WindowsXPTitle',
+ self::XP_COMMENT => 'WindowsXPComment',
+ self::XP_AUTHOR => 'WindowsXPAuthor',
+ self::XP_KEYWORDS => 'WindowsXPKeywords',
+ self::XP_SUBJECT => 'WindowsXPSubject',
+ self::FLASH_PIX_VERSION => 'FlashPixVersion',
+ self::COLOR_SPACE => 'ColorSpace',
+ self::PIXEL_X_DIMENSION => 'PixelXDimension',
+ self::PIXEL_Y_DIMENSION => 'PixelYDimension',
+ self::RELATED_SOUND_FILE => 'RelatedSoundFile',
+ self::INTEROPERABILITY_IFD_POINTER => 'InteroperabilityIFDPointer',
+ self::FLASH_ENERGY => 'FlashEnergy',
+ self::SPATIAL_FREQUENCY_RESPONSE => 'SpatialFrequencyResponse',
+ self::FOCAL_PLANE_X_RESOLUTION => 'FocalPlaneXResolution',
+ self::FOCAL_PLANE_Y_RESOLUTION => 'FocalPlaneYResolution',
+ self::FOCAL_PLANE_RESOLUTION_UNIT => 'FocalPlaneResolutionUnit',
+ self::SUBJECT_LOCATION => 'SubjectLocation',
+ self::EXPOSURE_INDEX => 'ExposureIndex',
+ self::SENSING_METHOD => 'SensingMethod',
+ self::FILE_SOURCE => 'FileSource',
+ self::SCENE_TYPE => 'SceneType',
+ self::SUBJECT_AREA => 'SubjectArea',
+ self::CUSTOM_RENDERED => 'CustomRendered',
+ self::EXPOSURE_MODE => 'ExposureMode',
+ self::WHITE_BALANCE => 'WhiteBalance',
+ self::DIGITAL_ZOOM_RATIO => 'DigitalZoomRatio',
+ self::FOCAL_LENGTH_IN_35MM_FILM => 'FocalLengthIn35mmFilm',
+ self::SCENE_CAPTURE_TYPE => 'SceneCaptureType',
+ self::GAIN_CONTROL => 'GainControl',
+ self::CONTRAST => 'Contrast',
+ self::SATURATION => 'Saturation',
+ self::SHARPNESS => 'Sharpness',
+ self::DEVICE_SETTING_DESCRIPTION => 'DeviceSettingDescription',
+ self::SUBJECT_DISTANCE_RANGE => 'SubjectDistanceRange',
+ self::IMAGE_UNIQUE_ID => 'ImageUniqueID',
+ self::GAMMA => 'Gamma',
+ self::PRINT_IM => 'PrintIM',
+ );
+
+ /**
+ * Values for tags titles.
+ */
+ protected static $exifTagsTitle = array(
+ self::INTEROPERABILITY_INDEX => 'Interoperability Index',
+ self::INTEROPERABILITY_VERSION => 'Interoperability Version',
+ self::IMAGE_WIDTH => 'Image Width',
+ self::IMAGE_LENGTH => 'Image Length',
+ self::BITS_PER_SAMPLE => 'Bits per Sample',
+ self::COMPRESSION => 'Compression',
+ self::PHOTOMETRIC_INTERPRETATION => 'Photometric Interpretation',
+ self::FILL_ORDER => 'Fill Order',
+ self::DOCUMENT_NAME => 'Document Name',
+ self::IMAGE_DESCRIPTION => 'Image Description',
+ self::MAKE => 'Manufacturer',
+ self::MODEL => 'Model',
+ self::STRIP_OFFSETS => 'Strip Offsets',
+ self::ORIENTATION => 'Orientation',
+ self::SAMPLES_PER_PIXEL => 'Samples per Pixel',
+ self::ROWS_PER_STRIP => 'Rows per Strip',
+ self::STRIP_BYTE_COUNTS => 'Strip Byte Count',
+ self::X_RESOLUTION => 'x-Resolution',
+ self::Y_RESOLUTION => 'y-Resolution',
+ self::PLANAR_CONFIGURATION => 'Planar Configuration',
+ self::RESOLUTION_UNIT => 'Resolution Unit',
+ self::TRANSFER_FUNCTION => 'Transfer Function',
+ self::SOFTWARE => 'Software',
+ self::DATE_TIME => 'Date and Time',
+ self::ARTIST => 'Artist',
+ self::WHITE_POINT => 'White Point',
+ self::PRIMARY_CHROMATICITIES => 'Primary Chromaticities',
+ self::TRANSFER_RANGE => 'Transfer Range',
+ self::JPEG_PROC => 'JPEG Process',
+ self::JPEG_INTERCHANGE_FORMAT => 'JPEG Interchange Format',
+ self::JPEG_INTERCHANGE_FORMAT_LENGTH => 'JPEG Interchange Format Length',
+ self::YCBCR_COEFFICIENTS => 'YCbCr Coefficients',
+ self::YCBCR_SUB_SAMPLING => 'YCbCr Sub-Sampling',
+ self::YCBCR_POSITIONING => 'YCbCr Positioning',
+ self::REFERENCE_BLACK_WHITE => 'Reference Black/White',
+ self::RELATED_IMAGE_FILE_FORMAT => 'Related Image File Format',
+ self::RELATED_IMAGE_WIDTH => 'Related Image Width',
+ self::RELATED_IMAGE_LENGTH => 'Related Image Length',
+ self::RATING => 'Star Rating',
+ self::RATING_PERCENT => 'Percent Rating',
+ self::CFA_REPEAT_PATTERN_DIM => 'CFA Repeat Pattern Dim',
+ self::CFA_PATTERN => 'CFA Pattern',
+ self::BATTERY_LEVEL => 'Battery Level',
+ self::COPYRIGHT => 'Copyright',
+ self::EXPOSURE_TIME => 'Exposure Time',
+ self::FNUMBER => 'FNumber',
+ self::IPTC_NAA => 'IPTC/NAA',
+ self::EXIF_IFD_POINTER => 'Exif IFD Pointer',
+ self::INTER_COLOR_PROFILE => 'Inter Color Profile',
+ self::EXPOSURE_PROGRAM => 'Exposure Program',
+ self::SPECTRAL_SENSITIVITY => 'Spectral Sensitivity',
+ self::GPS_INFO_IFD_POINTER => 'GPS Info IFD Pointer',
+ self::ISO_SPEED_RATINGS => 'ISO Speed Ratings',
+ self::OECF => 'OECF',
+ self::EXIF_VERSION => 'Exif Version',
+ self::DATE_TIME_ORIGINAL => 'Date and Time (original)',
+ self::DATE_TIME_DIGITIZED => 'Date and Time (digitized)',
+ self::OFFSET_TIME => 'Timezone',
+ self::OFFSET_TIME_ORIGINAL => 'Timezone (original)',
+ self::OFFSET_TIME_DIGITIZED => 'Timezone (digitized)',
+ self::COMPONENTS_CONFIGURATION => 'Components Configuration',
+ self::COMPRESSED_BITS_PER_PIXEL => 'Compressed Bits per Pixel',
+ self::SHUTTER_SPEED_VALUE => 'Shutter speed',
+ self::APERTURE_VALUE => 'Aperture',
+ self::BRIGHTNESS_VALUE => 'Brightness',
+ self::EXPOSURE_BIAS_VALUE => 'Exposure Bias',
+ self::MAX_APERTURE_VALUE => 'Max Aperture Value',
+ self::SUBJECT_DISTANCE => 'Subject Distance',
+ self::METERING_MODE => 'Metering Mode',
+ self::LIGHT_SOURCE => 'Light Source',
+ self::FLASH => 'Flash',
+ self::FOCAL_LENGTH => 'Focal Length',
+ self::MAKER_NOTE => 'Maker Note',
+ self::USER_COMMENT => 'User Comment',
+ self::SUB_SEC_TIME => 'SubSec Time',
+ self::SUB_SEC_TIME_ORIGINAL => 'SubSec Time Original',
+ self::SUB_SEC_TIME_DIGITIZED => 'SubSec Time Digitized',
+ self::XP_TITLE => 'Windows XP Title',
+ self::XP_COMMENT => 'Windows XP Comment',
+ self::XP_AUTHOR => 'Windows XP Author',
+ self::XP_KEYWORDS => 'Windows XP Keywords',
+ self::XP_SUBJECT => 'Windows XP Subject',
+ self::FLASH_PIX_VERSION => 'FlashPix Version',
+ self::COLOR_SPACE => 'Color Space',
+ self::PIXEL_X_DIMENSION => 'Pixel x-Dimension',
+ self::PIXEL_Y_DIMENSION => 'Pixel y-Dimension',
+ self::RELATED_SOUND_FILE => 'Related Sound File',
+ self::INTEROPERABILITY_IFD_POINTER => 'Interoperability IFD Pointer',
+ self::FLASH_ENERGY => 'Flash Energy',
+ self::SPATIAL_FREQUENCY_RESPONSE => 'Spatial Frequency Response',
+ self::FOCAL_PLANE_X_RESOLUTION => 'Focal Plane x-Resolution',
+ self::FOCAL_PLANE_Y_RESOLUTION => 'Focal Plane y-Resolution',
+ self::FOCAL_PLANE_RESOLUTION_UNIT => 'Focal Plane Resolution Unit',
+ self::SUBJECT_LOCATION => 'Subject Location',
+ self::EXPOSURE_INDEX => 'Exposure index',
+ self::SENSING_METHOD => 'Sensing Method',
+ self::FILE_SOURCE => 'File Source',
+ self::SCENE_TYPE => 'Scene Type',
+ self::SUBJECT_AREA => 'Subject Area',
+ self::CUSTOM_RENDERED => 'Custom Rendered',
+ self::EXPOSURE_MODE => 'Exposure Mode',
+ self::WHITE_BALANCE => 'White Balance',
+ self::DIGITAL_ZOOM_RATIO => 'Digital Zoom Ratio',
+ self::FOCAL_LENGTH_IN_35MM_FILM => 'Focal Length In 35mm Film',
+ self::SCENE_CAPTURE_TYPE => 'Scene Capture Type',
+ self::GAIN_CONTROL => 'Gain Control',
+ self::CONTRAST => 'Contrast',
+ self::SATURATION => 'Saturation',
+ self::SHARPNESS => 'Sharpness',
+ self::DEVICE_SETTING_DESCRIPTION => 'Device Setting Description',
+ self::SUBJECT_DISTANCE_RANGE => 'Subject Distance Range',
+ self::IMAGE_UNIQUE_ID => 'Image Unique ID',
+ self::GAMMA => 'Gamma',
+ self::PRINT_IM => 'Print IM',
+ );
+
+ /**
+ * Values for gps tags short names.
+ */
+ protected static $gpsTagsShort = array(
+ self::GPS_VERSION_ID => 'GPSVersionID',
+ self::GPS_LATITUDE_REF => 'GPSLatitudeRef',
+ self::GPS_LATITUDE => 'GPSLatitude',
+ self::GPS_LONGITUDE_REF => 'GPSLongitudeRef',
+ self::GPS_LONGITUDE => 'GPSLongitude',
+ self::GPS_ALTITUDE_REF => 'GPSAltitudeRef',
+ self::GPS_ALTITUDE => 'GPSAltitude',
+ self::GPS_TIME_STAMP => 'GPSTimeStamp',
+ self::GPS_SATELLITES => 'GPSSatellites',
+ self::GPS_STATUS => 'GPSStatus',
+ self::GPS_MEASURE_MODE => 'GPSMeasureMode',
+ self::GPS_DOP => 'GPSDOP',
+ self::GPS_SPEED_REF => 'GPSSpeedRef',
+ self::GPS_SPEED => 'GPSSpeed',
+ self::GPS_TRACK_REF => 'GPSTrackRef',
+ self::GPS_TRACK => 'GPSTrack',
+ self::GPS_IMG_DIRECTION_REF => 'GPSImgDirectionRef',
+ self::GPS_IMG_DIRECTION => 'GPSImgDirection',
+ self::GPS_MAP_DATUM => 'GPSMapDatum',
+ self::GPS_DEST_LATITUDE_REF => 'GPSDestLatitudeRef',
+ self::GPS_DEST_LATITUDE => 'GPSDestLatitude',
+ self::GPS_DEST_LONGITUDE_REF => 'GPSDestLongitudeRef',
+ self::GPS_DEST_LONGITUDE => 'GPSDestLongitude',
+ self::GPS_DEST_BEARING_REF => 'GPSDestBearingRef',
+ self::GPS_DEST_BEARING => 'GPSDestBearing',
+ self::GPS_DEST_DISTANCE_REF => 'GPSDestDistanceRef',
+ self::GPS_DEST_DISTANCE => 'GPSDestDistance',
+ self::GPS_PROCESSING_METHOD => 'GPSProcessingMethod',
+ self::GPS_AREA_INFORMATION => 'GPSAreaInformation',
+ self::GPS_DATE_STAMP => 'GPSDateStamp',
+ self::GPS_DIFFERENTIAL => 'GPSDifferential',
+ );
+
+ /**
+ * Returns a string from container with key $tag and subcontainer index of $idx
+ *
+ * @param array $container
+ * {@link PelTag::EXIF_TAGS_SHORT}, {@link PelTag::EXIF_TAGS_TITLE},
+ * {@link PelTag::GPS_TAGS_SHORT} or {@link PelTag::GPS_TAGS_TITLE} container.
+ * @param int $tag
+ * the tag.
+ *
+ * @return string short name or long name of the tag.
+ */
+ public static function getValue($container, $tag)
+ {
+ if (isset($container[ $tag ])) {
+ return $container[ $tag ];
+ }
+
+ return self::unknownTag($tag);
+ }
+
+ /**
+ * Reverse lookup of a tag id by its short name. Return false for the unknown tag name.
+ *
+ * @deprecated Use getExifTagByName() and getGpsTagByName() to distinct the type of tag.
+ *
+ * @param string $name
+ * tag short name.
+ *
+ * @return mixed (bool|int)
+ * the tag.
+ */
+ public static function getTagByName($name)
+ {
+ $k = array_search($name, self::$exifTagsShort);
+ if ($k !== false) {
+ return $k;
+ }
+
+ return array_search($name, static::$gpsTagsShort);
+ }
+
+ /**
+ * Reverse lookup of a EXIF related tag id by its short name. Return false for the unknown tag name.
+ *
+ * @param string $name
+ * tag short name.
+ *
+ * @return mixed (bool|int)
+ * the tag.
+ */
+ public static function getExifTagByName($name)
+ {
+ return array_search($name, static::$exifTagsShort);
+ }
+
+ /**
+ * Reverse lookup of a GPS related tag id by its short name. Return false for the unknown tag name.
+ *
+ * @param string $name
+ * tag short name.
+ *
+ * @return mixed (bool|int)
+ * the tag.
+ */
+ public static function getGpsTagByName($name)
+ {
+ return array_search($name, static::$gpsTagsShort);
+ }
+
+ /**
+ * Returns string defining unknown tag.
+ *
+ * @param int $tag
+ * the tag.
+ *
+ * @return string
+ * description string.
+ */
+ protected static function unknownTag($tag)
+ {
+ return Pel::fmt('Unknown: 0x%04X', $tag);
+ }
+
+ /**
+ * Returns a short name for an Exif tag.
+ *
+ * @param int $type
+ * the IFD type of the tag, one of {@link PelIfd::IFD0},
+ * {@link PelIfd::IFD1}, {@link PelIfd::EXIF}, {@link PelIfd::GPS},
+ * or {@link PelIfd::INTEROPERABILITY}.
+ *
+ * @param int $tag
+ * the tag.
+ *
+ * @return string the short name of the tag, e.g., 'ImageWidth' for
+ * the {@link IMAGE_WIDTH} tag. If the tag is not known, the string
+ * 'Unknown:0xTTTT' will be returned where 'TTTT' is the hexadecimal
+ * representation of the tag.
+ */
+ public static function getName($type, $tag)
+ {
+ switch ($type) {
+ case PelIfd::IFD0:
+ case PelIfd::IFD1:
+ case PelIfd::EXIF:
+ case PelIfd::INTEROPERABILITY:
+ return self::getValue(self::$exifTagsShort, $tag);
+ case PelIfd::GPS:
+ return self::getValue(self::$gpsTagsShort, $tag);
+ }
+
+ return self::unknownTag($tag);
+ }
+
+ /**
+ * Returns a title for an Exif tag.
+ *
+ * @param int $type
+ * the IFD type of the tag, one of {@link PelIfd::IFD0},
+ * {@link PelIfd::IFD1}, {@link PelIfd::EXIF}, {@link PelIfd::GPS},
+ * or {@link PelIfd::INTEROPERABILITY}.
+ *
+ * @param int $tag
+ * the tag.
+ *
+ * @return string the title of the tag, e.g., 'Image Width' for the
+ * {@link IMAGE_WIDTH} tag. If the tag isn't known, the string
+ * 'Unknown Tag: 0xTT' will be returned where 'TT' is the
+ * hexadecimal representation of the tag.
+ */
+ public function getTitle($type, $tag)
+ {
+ switch ($type) {
+ case PelIfd::IFD0:
+ case PelIfd::IFD1:
+ case PelIfd::EXIF:
+ case PelIfd::INTEROPERABILITY:
+ return Pel::tra(self::getValue(self::$exifTagsTitle, $tag));
+ case PelIfd::GPS:
+ return Pel::tra(self::getValue(self::$gpsTagsShort, $tag));
+ }
+
+ return self::unknownTag($tag);
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelTiff.php b/modules/autorotate/lib/pel-master/src/PelTiff.php
new file mode 100644
index 00000000..bd060235
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelTiff.php
@@ -0,0 +1,314 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Class for handling TIFF data.
+ *
+ * Exif data is actually an extension of the TIFF file format. TIFF
+ * images consist of a number of {@link PelIfd Image File Directories}
+ * (IFDs), each containing a number of {@link PelEntry entries}. The
+ * IFDs are linked to each other --- one can get hold of the first one
+ * with the {@link getIfd()} method.
+ *
+ * To parse a TIFF image for Exif data one would do:
+ *
+ *
+ * $tiff = new PelTiff($data);
+ * $ifd0 = $tiff->getIfd();
+ * $exif = $ifd0->getSubIfd(PelIfd::EXIF);
+ * $ifd1 = $ifd0->getNextIfd();
+ *
+ *
+ * Should one have some image data of an unknown type, then the {@link
+ * PelTiff::isValid()} function is handy: it will quickly test if the
+ * data could be valid TIFF data. The {@link PelJpeg::isValid()}
+ * function does the same for JPEG images.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ */
+class PelTiff
+{
+
+ /**
+ * TIFF header.
+ *
+ * This must follow after the two bytes indicating the byte order.
+ */
+ const TIFF_HEADER = 0x002A;
+
+ /**
+ * The first Image File Directory, if any.
+ *
+ * If set, then the type of the IFD must be {@link PelIfd::IFD0}.
+ *
+ * @var PelIfd
+ */
+ private $ifd = null;
+
+ /**
+ * Construct a new object for holding TIFF data.
+ *
+ * The new object will be empty (with no {@link PelIfd}) unless an
+ * argument is given from which it can initialize itself. This can
+ * either be the filename of a TIFF image or a {@link PelDataWindow}
+ * object.
+ *
+ * Use {@link setIfd()} to explicitly set the IFD.
+ *
+ * @param boolean|string|PelDataWindow $data;
+ */
+ public function __construct($data = false)
+ {
+ if ($data === false) {
+ return;
+ }
+ if (is_string($data)) {
+ Pel::debug('Initializing PelTiff object from %s', $data);
+ $this->loadFile($data);
+ } elseif ($data instanceof PelDataWindow) {
+ Pel::debug('Initializing PelTiff object from PelDataWindow.');
+ $this->load($data);
+ } else {
+ throw new PelInvalidArgumentException('Bad type for $data: %s', gettype($data));
+ }
+ }
+
+ /**
+ * Load TIFF data.
+ *
+ * The data given will be parsed and an internal tree representation
+ * will be built. If the data cannot be parsed correctly, a {@link
+ * PelInvalidDataException} is thrown, explaining the problem.
+ *
+ * @param
+ * d
+ * PelDataWindow the data from which the object will be
+ * constructed. This should be valid TIFF data, coming either
+ * directly from a TIFF image or from the Exif data in a JPEG image.
+ */
+ public function load(PelDataWindow $d)
+ {
+ Pel::debug('Parsing %d bytes of TIFF data...', $d->getSize());
+
+ /*
+ * There must be at least 8 bytes available: 2 bytes for the byte
+ * order, 2 bytes for the TIFF header, and 4 bytes for the offset
+ * to the first IFD.
+ */
+ if ($d->getSize() < 8) {
+ throw new PelInvalidDataException('Expected at least 8 bytes of TIFF ' . 'data, found just %d bytes.', $d->getSize());
+ }
+ /* Byte order */
+ if ($d->strcmp(0, 'II')) {
+ Pel::debug('Found Intel byte order');
+ $d->setByteOrder(PelConvert::LITTLE_ENDIAN);
+ } elseif ($d->strcmp(0, 'MM')) {
+ Pel::debug('Found Motorola byte order');
+ $d->setByteOrder(PelConvert::BIG_ENDIAN);
+ } else {
+ throw new PelInvalidDataException('Unknown byte order found in TIFF ' . 'data: 0x%2X%2X', $d->getByte(0), $d->getByte(1));
+ }
+
+ /* Verify the TIFF header */
+ if ($d->getShort(2) != self::TIFF_HEADER) {
+ throw new PelInvalidDataException('Missing TIFF magic value.');
+ }
+ /* IFD 0 offset */
+ $offset = $d->getLong(4);
+ Pel::debug('First IFD at offset %d.', $offset);
+
+ if ($offset > 0) {
+ /*
+ * Parse the first IFD, this will automatically parse the
+ * following IFDs and any sub IFDs.
+ */
+ $this->ifd = new PelIfd(PelIfd::IFD0);
+ $this->ifd->load($d, $offset);
+ }
+ }
+
+ /**
+ * Load data from a file into a TIFF object.
+ *
+ * @param string $filename
+ * the filename. This must be a readable file.
+ */
+ public function loadFile($filename)
+ {
+ $this->load(new PelDataWindow(file_get_contents($filename)));
+ }
+
+ /**
+ * Set the first IFD.
+ *
+ * @param PelIfd $ifd
+ * the new first IFD, which must be of type {@link
+ * PelIfd::IFD0}.
+ */
+ public function setIfd(PelIfd $ifd)
+ {
+ if ($ifd->getType() != PelIfd::IFD0) {
+ throw new PelInvalidDataException('Invalid type of IFD: %d, expected %d.', $ifd->getType(), PelIfd::IFD0);
+ }
+ $this->ifd = $ifd;
+ }
+
+ /**
+ * Return the first IFD.
+ *
+ * @return PelIfd the first IFD contained in the TIFF data, if any.
+ * If there is no IFD null will be returned.
+ */
+ public function getIfd()
+ {
+ return $this->ifd;
+ }
+
+ /**
+ * Turn this object into bytes.
+ *
+ * TIFF images can have {@link PelConvert::LITTLE_ENDIAN
+ * little-endian} or {@link PelConvert::BIG_ENDIAN big-endian} byte
+ * order, and so this method takes an argument specifying that.
+ *
+ * @param PelByteOrder $order
+ * the desired byte order of the TIFF data.
+ * This should be one of {@link PelConvert::LITTLE_ENDIAN} or {@link
+ * PelConvert::BIG_ENDIAN}.
+ *
+ * @return string the bytes representing this object.
+ */
+ public function getBytes($order = PelConvert::LITTLE_ENDIAN)
+ {
+ if ($order == PelConvert::LITTLE_ENDIAN) {
+ $bytes = 'II';
+ } else {
+ $bytes = 'MM';
+ }
+
+ /* TIFF magic number --- fixed value. */
+ $bytes .= PelConvert::shortToBytes(self::TIFF_HEADER, $order);
+
+ if ($this->ifd !== null) {
+ /*
+ * IFD 0 offset. We will always start IDF 0 at an offset of 8
+ * bytes (2 bytes for byte order, another 2 bytes for the TIFF
+ * header, and 4 bytes for the IFD 0 offset make 8 bytes
+ * together).
+ */
+ $bytes .= PelConvert::longToBytes(8, $order);
+
+ /*
+ * The argument specifies the offset of this IFD. The IFD will
+ * use this to calculate offsets from the entries to their data,
+ * all those offsets are absolute offsets counted from the
+ * beginning of the data.
+ */
+ $bytes .= $this->ifd->getBytes(8, $order);
+ } else {
+ $bytes .= PelConvert::longToBytes(0, $order);
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Save the TIFF object as a TIFF image in a file.
+ *
+ * @param
+ * string the filename to save in. An existing file with the
+ * same name will be overwritten!
+ *
+ * @return integer|FALSE The number of bytes that were written to the
+ * file, or FALSE on failure.
+ */
+ public function saveFile($filename)
+ {
+ return file_put_contents($filename, $this->getBytes());
+ }
+
+ /**
+ * Return a string representation of this object.
+ *
+ * @return string a string describing this object. This is mostly useful
+ * for debugging.
+ */
+ public function __toString()
+ {
+ $str = Pel::fmt("Dumping TIFF data...\n");
+ if ($this->ifd !== null) {
+ $str .= $this->ifd->__toString();
+ }
+
+ return $str;
+ }
+
+ /**
+ * Check if data is valid TIFF data.
+ *
+ * This will read just enough data from the data window to determine
+ * if the data could be a valid TIFF data. This means that the
+ * check is more like a heuristic than a rigorous check.
+ *
+ * @param PelDataWindow $d
+ * the bytes that will be examined.
+ *
+ * @return boolean true if the data looks like valid TIFF data,
+ * false otherwise.
+ *
+ * @see PelJpeg::isValid()
+ */
+ public static function isValid(PelDataWindow $d)
+ {
+ /* First check that we have enough data. */
+ if ($d->getSize() < 8) {
+ return false;
+ }
+
+ /* Byte order */
+ if ($d->strcmp(0, 'II')) {
+ $d->setByteOrder(PelConvert::LITTLE_ENDIAN);
+ } elseif ($d->strcmp(0, 'MM')) {
+ Pel::debug('Found Motorola byte order');
+ $d->setByteOrder(PelConvert::BIG_ENDIAN);
+ } else {
+ return false;
+ }
+
+ /* Verify the TIFF header */
+ return $d->getShort(2) == self::TIFF_HEADER;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelUnexpectedFormatException.php b/modules/autorotate/lib/pel-master/src/PelUnexpectedFormatException.php
new file mode 100644
index 00000000..cfbadaa5
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelUnexpectedFormatException.php
@@ -0,0 +1,80 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+
+/**
+ * Exception indicating that an unexpected format was found.
+ *
+ * The documentation for each tag in {@link PelTag} will detail any
+ * constrains.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelUnexpectedFormatException extends PelEntryException
+{
+
+ /**
+ * Construct a new exception indicating an invalid format.
+ *
+ * @param int $type
+ * the type of IFD.
+ *
+ * @param int $tag
+ * the tag for which the violation was found as defined in {@link PelTag}
+ *
+ * @param int $found
+ * the format found as defined in {@link PelFormat}
+ *
+ * @param int $expected
+ * the expected as defined in {@link PelFormat}
+ */
+ public function __construct($type, $tag, $found, $expected)
+ {
+ parent::__construct(
+ 'Unexpected format found for %s tag: PelFormat::%s. Expected PelFormat::%s instead.',
+ PelTag::getName($type, $tag),
+ strtoupper(PelFormat::getName($found)),
+ strtoupper(PelFormat::getName($expected)));
+ $this->tag = $tag;
+ $this->type = $type;
+ }
+}
diff --git a/modules/autorotate/lib/pel-master/src/PelWrongComponentCountException.php b/modules/autorotate/lib/pel-master/src/PelWrongComponentCountException.php
new file mode 100644
index 00000000..8e8c4385
--- /dev/null
+++ b/modules/autorotate/lib/pel-master/src/PelWrongComponentCountException.php
@@ -0,0 +1,81 @@
+
+ * @license http://www.gnu.org/licenses/gpl.html GNU General Public
+ * License (GPL)
+ * @package PEL
+ */
+use lsolesen\pel\PelTag;
+
+/**
+ * Exception indicating that an unexpected number of components was
+ * found.
+ *
+ * Some tags have strict limits as to the allowed number of
+ * components, and this exception is thrown if the data violates such
+ * a constraint. The documentation for each tag in {@link PelTag}
+ * explains the expected number of components.
+ *
+ * @author Martin Geisler
+ * @package PEL
+ * @subpackage Exception
+ */
+class PelWrongComponentCountException extends \lsolesen\pel\PelEntryException
+{
+
+ /**
+ * Construct a new exception indicating a wrong number of
+ * components.
+ *
+ * @param int $type
+ * the type of IFD.
+ *
+ * @param int $tag
+ * the tag for which the violation was found.
+ *
+ * @param int $found
+ * the number of components found.
+ *
+ * @param int $expected
+ * the expected number of components.
+ */
+ public function __construct($type, $tag, $found, $expected)
+ {
+ parent::__construct('Wrong number of components found for %s tag: %d. ' . 'Expected %d.', PelTag::getName($type, $tag), $found, $expected);
+ $this->tag = $tag;
+ $this->type = $type;
+ }
+}
diff --git a/modules/autorotate/module.info b/modules/autorotate/module.info
new file mode 100644
index 00000000..9058a182
--- /dev/null
+++ b/modules/autorotate/module.info
@@ -0,0 +1,7 @@
+name = "Autorotate"
+description = "Rotate an image automatically on upload based on EXIF data. Requires PHP 5.3 or later."
+version = 3
+author_name = ""
+author_url = ""
+info_url = ""
+discuss_url = ""