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

SourceForge.net Logo Documentation generated on Wed, 21 Jul 2004 19:13:11 +0200 by phpDocumentor 1.3.0RC3