Source for file PelIfd.php

Documentation is available at PelIfd.php

  1. <?php
  2.  
  3. /* PEL: PHP EXIF Library. A library with support for reading and
  4. * writing all EXIF headers in JPEG and TIFF images using PHP.
  5. *
  6. * Copyright (C) 2004, 2005 Martin Geisler.
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program in the file COPYING; if not, write to the
  20. * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  21. * Boston, MA 02111-1307 USA
  22. */
  23.  
  24. /* PelIfd.php,v 1.12 2005/02/10 21:22:42 gimpster Exp */
  25.  
  26.  
  27. /**
  28. * Classes for dealing with EXIF IFDs.
  29. *
  30. * @author Martin Geisler <gimpster@users.sourceforge.net>
  31. * @version 1.12
  32. * @date 2005/02/10 21:22:42
  33. * @license http://www.gnu.org/licenses/gpl.html GNU General Public
  34. * License (GPL)
  35. * @package PEL
  36. */
  37.  
  38. /**#@+ Required class definitions. */
  39. ('PelDataWindow.php');
  40. require_once('PelException.php');
  41. require_once('PelFormat.php');
  42. require_once('PelEntry.php');
  43. require_once('PelTag.php');
  44. require_once('Pel.php');
  45. /**#@-*/ * Exception indicating a general problem with the IFD.
  46. *
  47. * @author Martin Geisler <gimpster@users.sourceforge.net>
  48. * @package PEL
  49. * @subpackage Exception
  50. */
  51. class PelIfdException extends PelException {}
  52.  
  53. /**
  54. * Class representing an Image File Directory (IFD).
  55. *
  56. * {@link PelTiff TIFF data} is structured as a number of Image File
  57. * Directories, IFDs for short. Each IFD contains a number of {@link }
  58. * PelEntry entries}, some data and finally a link to the next IFD.
  59. *
  60. * @author Martin Geisler <gimpster@users.sourceforge.net>
  61. * @package PEL
  62. */
  63. class PelIfd {
  64.  
  65. const IFD0 = 0;
  66. const IFD1 = 1;
  67. const EXIF = 2;
  68. const GPS = 3;
  69. const INTEROPERABILITY = 4;
  70.  
  71. /**
  72. * The entries held by this directory.
  73. *
  74. * Each tag in the directory is represented by a {@link PelEntry}
  75. * object in this array.
  76. *
  77. * @var array
  78. */
  79. private $entries = array();
  80.  
  81. /**
  82. * The type of this directory (not currently used).
  83. *
  84. * @var int
  85. */
  86. private $type;
  87.  
  88. /**
  89. * The next directory.
  90. *
  91. * This will be initialized in the constructor, or be left as null
  92. * if this is the last directory.
  93. *
  94. * @var PelIfd
  95. */
  96. private $next = null;
  97.  
  98. /**
  99. * Sub-directories pointed to by this directory.
  100. *
  101. * This will be an array of ({@link PelTag}, {@link PelIfd}) pairs.
  102. *
  103. * @var array
  104. */
  105. private $sub = array();
  106.  
  107. /**
  108. * The thumbnail data.
  109. *
  110. * This will be initialized in the constructor, or be left as null
  111. * if there are no thumbnail as part of this directory.
  112. *
  113. * @var PelDataWindow
  114. */
  115. private $thumb_data = null;
  116. // TODO: use this format to choose between the
  117. // JPEG_INTERCHANGE_FORMAT and STRIP_OFFSETS tags.
  118. // private $thumb_format;
  119.  
  120.  
  121. /**
  122. * Construct a new Image File Directory (IFD).
  123. *
  124. * The IFD will be empty, use the {@link addEntry()} method to add
  125. * an {@link PelEntry}. Use the {@link setNext()} method to link
  126. * this IFD to another.
  127. */
  128. function __construct() {
  129.  
  130. }
  131.  
  132.  
  133. /**
  134. * Load data into a Image File Directory (IFD).
  135. *
  136. * @param PelDataWindow the data window that will provide the data.
  137. *
  138. * @param int the offset within the window where the directory will
  139. * be found.
  140. */
  141. function load(PelDataWindow $d, $offset) {
  142. $thumb_offset = 0;
  143. $thumb_length = 0;
  144.  
  145. Pel::debug('Constructing IFD at offset %d from %d bytes...',
  146. $offset, $d->getSize());
  147.  
  148. /* Read the number of entries */
  149. $n = $d->getShort($offset);
  150. Pel::debug('Loading %d entries...', $n);
  151. $offset += 2;
  152.  
  153. /* Check if we have enough data. */
  154. if ($offset + 12 * $n > $d->getSize()) {
  155. $n = floor(($offset - $d->getSize()) / 12);
  156. Pel::warning('Adjusted number of entries to %d.', $n);
  157. }
  158.  
  159. for ($i = 0; $i < $n; $i++) {
  160. // TODO: increment window start instead of using offsets.
  161. $tag = $d->getShort($offset + 12 * $i);
  162. Pel::debug('Loading entry %s (%d of %d)...',
  163. PelTag::getName($tag), $i + 1, $n);
  164. switch ($tag) {
  165. case PelTag::EXIF_IFD_POINTER:
  166. case PelTag::GPS_INFO_IFD_POINTER:
  167. case PelTag::INTEROPERABILITY_IFD_POINTER:
  168. $o = $d->getLong($offset + 12 * $i + 8);
  169. // println('Found sub IFD');
  170. $this->sub[$tag] = new PelIfd();
  171. $this->sub[$tag]->load($d, $o);
  172. break;
  173. case PelTag::JPEG_INTERCHANGE_FORMAT:
  174. $thumb_offset = $d->getLong($offset + 12 * $i + 8);
  175. $this->loadThumbnail($d, $thumb_offset, $thumb_length);
  176. break;
  177. case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH:
  178. $thumb_length = $d->getLong($offset + 12 * $i + 8);
  179. $this->loadThumbnail($d, $thumb_offset, $thumb_length);
  180. break;
  181. default:
  182. $format = $d->getShort($offset + 12 * $i + 2);
  183. $components = $d->getLong($offset + 12 * $i + 4);
  184. /* The data size. If bigger than 4 bytes, the actual data is
  185. * not in the entry but somewhere else, with the offset stored
  186. * in the entry.
  187. */
  188. $s = PelFormat::getSize($format) * $components;
  189. if ($s > 0) {
  190. if ($s > 4)
  191. $doff = $d->getLong($offset + 12 * $i + 8);
  192. else
  193. $doff = $offset + 12 * $i + 8;
  194.  
  195. $data = $d->getClone($doff, $s);
  196. } else {
  197. $data = new PelDataWindow();
  198. }
  199.  
  200. try {
  201. $entry = PelEntry::newFromData($tag, $format, $components, $data);
  202. $this->entries[$tag] = $entry;
  203. } catch (PelEntryException $e) {
  204. Pel::warning('Could not load entry %d: %s', $i, $e->getMessage());
  205. }
  206.  
  207. /* The format of the thumbnail is stored in this tag. */
  208. // TODO: handle TIFF thumbnail.
  209. // if ($tag == PelTag::COMPRESSION) {
  210. // $this->thumb_format = $data->getShort();
  211. // }
  212. // if (ExifTag::isKnownTag($tag)) {
  213. // $this->entries[] = new PelEntry($data, $offset + 12 * $i, $order);
  214. // } else {
  215. // // TODO: should we bail out completely like libexif does
  216. // // because we claim to know all EXIF tags?
  217. // printf("Unknown EXIF tag: 0x%02X\n", $tag);
  218. // }
  219. break;
  220. }
  221. }
  222.  
  223. /* Offset to next IFD */
  224. Pel::debug('Current offset is %d, reading link at %d',
  225. $offset, $offset + 12 * $n);
  226. $o = $d->getLong($offset + 12 * $n);
  227. if ($o > 0) {
  228. // println('Next IFD is at offset %d', $o);
  229. /* Sanity check. */
  230. if ($o > $d->getSize() - 6)
  231. throw new PelIfdException('Bogus offset!');
  232.  
  233. $this->next = new PelIfd();
  234. $this->next->load($d, $o);
  235. } else {
  236. // println('That was the last IFD');
  237. }
  238. }
  239.  
  240.  
  241. /**
  242. * Extract thumbnail data.
  243. *
  244. * It is safe to call this method repeatedly with either the offset
  245. * or the length set to zero, since it requires both of these
  246. * arguments to be positive before the thumbnail is extracted.
  247. *
  248. * @param PelDataWindow the data from which the thumbnail will be
  249. * extracted.
  250. *
  251. * @param int the offset into the data.
  252. *
  253. * @param int the length of the thumbnail.
  254. */
  255. private function loadThumbnail(PelDataWindow $d, $offset, $length) {
  256. /* Load the thumbnail if both the offset and the length is
  257. * available. */
  258. if ($offset > 0 && $length > 0) {
  259. /* Some images have a broken length, so we try to carefully
  260. * check the length before we store the thumbnail. */
  261. if ($offset + $length > $d->getSize()) {
  262. Pel::warning('Thumbnail length %d bytes adjusted to %d bytes.',
  263. $length, $d->getSize() - $offset);
  264. $length = $d->getSize() - $offset;
  265. }
  266.  
  267. /* Now move backwards until we find the EOI JPEG marker. */
  268. while ($d->getByte($offset+$length-2) != 0xFF ||
  269. $d->getByte($offset+$length-1) != PelJpegMarker::EOI) {
  270. $length--;
  271. Pel::warning('Decrementing thumbnail length to %d bytes', $length);
  272. }
  273.  
  274. Pel::debug('Loading %d bytes of thumbnail data from offset %d',
  275. $length, $offset);
  276. $this->thumb_data = $d->getClone($offset, $length);
  277. }
  278. }
  279.  
  280.  
  281. /**
  282. * Get the name of this directory (not currently used).
  283. *
  284. * @return string the name of this directory.
  285. */
  286. function getName() {
  287. switch ($this->type) {
  288. case self::IFD0: return '0';
  289. case self::IFD1: return '1';
  290. case self::EXIF: return 'EXIF';
  291. case self::GPS: return 'GPS';
  292. case self::INTEROPERABILITY: return 'Interoperability';
  293. }
  294. }
  295.  
  296.  
  297. /**
  298. * Adds an entry to the directory.
  299. *
  300. * @param PelEntry the entry that will be added.
  301. *
  302. * @todo The entry will be identified with it's tag, so each
  303. * directory can only contain one entry with each tag. Is this a
  304. * bug?
  305. */
  306. function addEntry(PelEntry $e) {
  307. $this->entries[$e->getTag()] = $e;
  308. }
  309.  
  310.  
  311. /**
  312. * Retrieve an entry.
  313. *
  314. * @param PelTag the tag identifying the entry.
  315. *
  316. * @return PelEntry the entry associated with the tag, or null if no
  317. * such entry exists.
  318. */
  319. function getEntry($tag) {
  320. if (isset($this->entries[$tag]))
  321. return $this->entries[$tag];
  322. else
  323. return null;
  324. }
  325.  
  326.  
  327. /**
  328. * Returns all entries contained in this IFD.
  329. *
  330. * @return array an array of {@link PelEntry} objects, or rather
  331. * descendant classes. The array has {@link PelTag}s as keys
  332. * and the entries as values.
  333. *
  334. * @see getEntry
  335. */
  336. function getEntries() {
  337. return $this->entries;
  338. }
  339.  
  340.  
  341. /**
  342. * Returns available thumbnail data.
  343. *
  344. * @return string the bytes in the thumbnail, if any. If the IFD
  345. * doesn't contain any thumbnail data, the empty string is returned.
  346. *
  347. * @todo Throw an exception instead when no data is available?
  348. *
  349. * @todo Return the $this->thumb_data object instead of the bytes?
  350. */
  351. function getThumbnailData() {
  352. if ($this->thumb_data != null)
  353. return $this->thumb_data->getBytes();
  354. else
  355. return '';
  356. }
  357.  
  358. /**
  359. * Make this directory point to a new directory.
  360. *
  361. * @param PelIfd the IFD that this directory will point to.
  362. */
  363. function setNextIfd(PelIfd $i) {
  364. $this->next = $i;
  365. }
  366.  
  367.  
  368. /**
  369. * Return the IFD pointed to by this directory.
  370. *
  371. * @return PelIfd the next IFD, following this IFD. If this is the
  372. * last IFD, null is returned.
  373. */
  374. function getNextIfd() {
  375. return $this->next;
  376. }
  377.  
  378.  
  379. /**
  380. * Check if this is the last IFD.
  381. *
  382. * @return boolean true if there are no following IFD, false
  383. * otherwise.
  384. */
  385. function isLastIfd() {
  386. return $this->next == null;
  387. }
  388.  
  389.  
  390. /**
  391. * Return a sub IFD.
  392. *
  393. * @param PelTag the tag of the sub IFD. This should be one of
  394. * {@link PelTag::EXIF_IFD_POINTER}, {@link }
  395. * PelTag::GPS_INFO_IFD_POINTER}, or {@link }
  396. * PelTag::INTEROPERABILITY_IFD_POINTER}.
  397. *
  398. * @return PelIfd the IFD associated with the tag, or null if
  399. * that sub IFD doesn't exist.
  400. */
  401. function getSubIfd($tag) {
  402. if (isset($this->sub[$tag]))
  403. return $this->sub[$tag];
  404. else
  405. return null;
  406. }
  407.  
  408.  
  409. /**
  410. * Get all sub IFDs.
  411. *
  412. * @return array an array with ({@link PelTag}, {@link PelIfd})
  413. * pairs.
  414. */
  415. function getSubIfds() {
  416. return $this->sub;
  417. }
  418.  
  419.  
  420. /**
  421. * Turn this directory into bytes.
  422. *
  423. * This directory will be turned into a byte string, with the
  424. * specified byte order. The offsets will be calculated from the
  425. * offset given.
  426. *
  427. * @param int the offset of the first byte of this directory.
  428. *
  429. * @param PelByteOrder the byte order that should be used when
  430. * turning integers into bytes. This should be one of {@link }
  431. * PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}.
  432. */
  433. function getBytes($offset, $order) {
  434. $bytes = '';
  435. $extra_bytes = '';
  436.  
  437. Pel::debug('Bytes from IDF will start at offset %d within EXIF data',
  438. $offset);
  439. $n = count($this->entries) + count($this->sub);
  440. if ($this->thumb_data != null) {
  441. /* We need two extra entries for the thumbnail offset and
  442. * length. */
  443. $n += 2;
  444. }
  445.  
  446. $bytes .= PelConvert::shortToBytes($n, $order);
  447.  
  448. /* Initialize offset of extra data. This included the bytes
  449. * preceding this IFD, the bytes needed for the count of entries,
  450. * the entries themselves (and sub entries), the extra data in the
  451. * entries, and the IFD link.
  452. */
  453. $end = $offset + 2 + 12 * $n + 4;
  454.  
  455. foreach ($this->entries as $tag => $entry) {
  456. /* Each entry is 12 bytes long. */
  457. $bytes .= PelConvert::shortToBytes($entry->getTag(), $order);
  458. $bytes .= PelConvert::shortToBytes($entry->getFormat(), $order);
  459. $bytes .= PelConvert::longToBytes($entry->getComponents(), $order);
  460. /*
  461. * Size? If bigger than 4 bytes, the actual data is not in
  462. * the entry but somewhere else.
  463. */
  464. $data = $entry->getBytes($order);
  465. $s = strlen($data);
  466. if ($s > 4) {
  467. Pel::debug('Data size %d too big, storing at offset %d instead.',
  468. $s, $end);
  469. $bytes .= PelConvert::longToBytes($end, $order);
  470. $extra_bytes .= $data;
  471. $end += $s;
  472. } else {
  473. Pel::debug('Data size %d fits.', $s);
  474. /* Copy data directly, pad with NULL bytes as necessary to
  475. * fill out the four bytes available.*/
  476. $bytes .= $data . str_repeat(chr(0), 4 - $s);
  477. }
  478. }
  479.  
  480. if ($this->thumb_data != null) {
  481. Pel::debug('Appending %d bytes of thumbnail data at %d',
  482. $this->thumb_data->getSize(), $end);
  483. // TODO: make PelEntry a class that can be constructed with
  484. // arguments corresponding to the newt four lines.
  485. $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH,
  486. $order);
  487. $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
  488. $bytes .= PelConvert::longToBytes(1, $order);
  489. $bytes .= PelConvert::longToBytes($this->thumb_data->getSize(),
  490. $order);
  491. $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT,
  492. $order);
  493. $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
  494. $bytes .= PelConvert::longToBytes(1, $order);
  495. $bytes .= PelConvert::longToBytes($end, $order);
  496. $extra_bytes .= $this->thumb_data->getBytes();
  497. $end += $this->thumb_data->getSize();
  498. }
  499.  
  500. /* Find bytes from sub IFDs. */
  501. $sub_bytes = '';
  502. foreach ($this->sub as $tag => $sub) {
  503. /* Make an aditional entry with the pointer. */
  504. $bytes .= PelConvert::shortToBytes($tag, $order);
  505. /* Next the format, which is always unsigned long. */
  506. $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order);
  507. /* There's only one component. */
  508. $bytes .= PelConvert::longToBytes(1, $order);
  509.  
  510. $data = $sub->getBytes($end, $order);
  511. $s = strlen($data);
  512. $sub_bytes .= $data;
  513.  
  514. $bytes .= PelConvert::longToBytes($end, $order);
  515. $end += $s;
  516. }
  517.  
  518. /* Make link to next IFD, if any*/
  519. if (self::isLastIFD()) {
  520. $link = 0;
  521. } else {
  522. $link = $end;
  523. }
  524.  
  525. Pel::debug('Link to next IFD: %d', $link);
  526. $bytes .= PelConvert::longtoBytes($link, $order);
  527.  
  528. $bytes .= $extra_bytes . $sub_bytes;
  529.  
  530. if (!self::isLastIfd())
  531. $bytes .= $this->next->getBytes($end, $order);
  532.  
  533. return $bytes;
  534. }
  535.  
  536. /**
  537. * Turn this directory into text.
  538. *
  539. * @return string information about the directory, mainly for
  540. * debugging.
  541. */
  542. function __toString() {
  543. $str = Pel::fmt("Dumping EXIF IFD %s with %d entries...\n",
  544. self::getName(), count($this->entries));
  545. foreach ($this->entries as $entry)
  546. $str .= $entry->__toString();
  547.  
  548. $str .= Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub));
  549.  
  550. foreach ($this->sub as $tag => $ifd)
  551. $str .= $ifd->__toString();
  552.  
  553. if ($this->next != null)
  554. $str .= $this->next->__toString();
  555.  
  556. return $str;
  557. }
  558.  
  559.  
  560. }
  561.  
  562. ?>

SourceForge.net Logo Documentation generated on Fri, 18 Feb 2005 01:43:18 +0100 by phpDocumentor 1.3.0RC3