Source for file PelJpeg.php

Documentation is available at PelJpeg.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.
  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. /* PelJpeg.php,v 1.19 2005/02/10 21:22:44 gimpster Exp */
  25.  
  26.  
  27. /**
  28. * Classes representing JPEG data.
  29. *
  30. * @author Martin Geisler <gimpster@users.sourceforge.net>
  31. * @version 1.19
  32. * @date 2005/02/10 21:22:44
  33. * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL)
  34. * @package PEL
  35. */
  36.  
  37. /**#@+ Required class definitions. */
  38. ('PelJpegContent.php');
  39. require_once('PelDataWindow.php');
  40. require_once('PelJpegMarker.php');
  41. require_once('PelException.php');
  42. require_once('PelExif.php');
  43. require_once('Pel.php');
  44. /**#@-*/ * Exception thrown when an invalid marker is found.
  45. *
  46. * This exception is thrown when PEL expects to find a {@link
  47. * PelJpegMarker} and instead finds a byte that isn't a known marker.
  48. *
  49. * @author Martin Geisler <gimpster@users.sourceforge.net>
  50. * @package PEL
  51. * @subpackage Exception
  52. */
  53. class PelJpegInvalidMarkerException extends PelException {
  54.  
  55. /**
  56. * Construct a new invalid marker exception.
  57. *
  58. * The exception will contain a message describing the error,
  59. * including the byte found and the offset of the offending byte.
  60. *
  61. * @param int the byte found.
  62. *
  63. * @param int the offset in the data.
  64. */
  65. function __construct($marker, $offset) {
  66. parent::__construct('Invalid marker found at offset %d: 0x%2X',
  67. $offset, $marker);
  68. }
  69. }
  70.  
  71. /**
  72. * Class for handling JPEG data.
  73. *
  74. * The {@link PelJpeg} class defined here provides an abstraction for
  75. * dealing with a JPEG file. The file will be contain a number of
  76. * sections containing some {@link PelJpegContent content} identified
  77. * by a {@link PelJpegMarker marker}.
  78. *
  79. * The {@link getSection()} method is used to pick out a particular
  80. * section --- the EXIF information is typically stored in the {@link }
  81. * PelJpegMarker::APP1 APP1} section, and so if the name of the JPEG
  82. * file is stored in $filename, then one would get hold of the EXIF
  83. * data by saying:
  84. *
  85. * <code>
  86. * $jpeg = new PelJpeg();
  87. * $jpeg->loadFile($filename);
  88. * $app1 = $jpeg->getSection(PelJpegMarker::APP1);
  89. * $tiff = $app1->getTiff();
  90. * $ifd0 = $tiff->getIfd();
  91. * $exif = $ifd0->getSubIfd(PelTag::EXIF_IFD_POINTER);
  92. * $ifd1 = $ifd0->getNextIfd();
  93. * </code>
  94. *
  95. * The $idf0 and $ifd1 variables will then be two {@link PelTiff TIFF}
  96. * {@link PelIfd Image File Directories}, in which the data is stored
  97. * under the keys found in {@link PelTag}.
  98. *
  99. * Should one have some image data (in the form of a {@link }
  100. * PelDataWindow}) of an unknown type, then the {@link }
  101. * PelJpeg::isValid()} function is handy: it will quickly test if the
  102. * data could be valid JPEG data. The {@link PelTiff::isValid()}
  103. * function does the same for TIFF images.
  104. *
  105. * @author Martin Geisler <gimpster@users.sourceforge.net>
  106. * @package PEL
  107. */
  108. class PelJpeg {
  109.  
  110. /**
  111. * The sections in the JPEG data.
  112. *
  113. * A JPEG file is built up as a sequence of sections, each section
  114. * is identified with a {@link PelJpegMarker}. Some sections can
  115. * occur more than once in the JPEG stream (the {@link }
  116. * PelJpegMarker::DQT DQT} and {@link PelJpegMarker::DHT DTH}
  117. * markers for example) and so this is an array of ({@link }
  118. * PelJpegMarker}, {@link PelJpegContent}) pairs.
  119. *
  120. * The content can be either generic {@link PelJpegContent JPEG}
  121. * content} or {@link PelExif EXIF data}.
  122. *
  123. * @var array
  124. */
  125. private $sections = array();
  126.  
  127. /**
  128. * The JPEG image data.
  129. *
  130. * @var PelDataWindow
  131. */
  132. private $jpeg_data = null;
  133.  
  134. /**
  135. * Construct a new JPEG object.
  136. *
  137. * The new object will be empty, use the {@link load()} or {@link }
  138. * loadFile()} methods to load JPEG data from a {@link }
  139. * PelDataWindow} or a file, respectively.
  140. *
  141. * Individual sections of JPEG content can be added with the {@link }
  142. * appendSection()} method --- use this method to add a {@link }
  143. * PelExif} object as the {@link PelJpegMarker::APP1} section of an
  144. * existing file without EXIF information:
  145. *
  146. * <code>
  147. * $jpeg = new PelJpeg();
  148. * $jpeg->load($data);
  149. * $jpeg->appendSection($exif, PelJpegMarker::APP1);
  150. * </code>
  151. */
  152. function __construct() {
  153.  
  154. }
  155.  
  156. /**
  157. * Load data into a JPEG object.
  158. *
  159. * The data supplied will be parsed and turned into an object
  160. * structure representing the image. This structure can then be
  161. * manipulated and later turned back into an string of bytes.
  162. *
  163. * This methods can be called at any time after a JPEG object has
  164. * been constructed, also after the {@link appendSection()} has been
  165. * called to append custom sections. Loading several JPEG images
  166. * into one object will accumulate the sections, but there will only
  167. * be one {@link PelJpegMarker::SOS} section at any given time.
  168. *
  169. * @param PelDataWindow the data that will be turned into JPEG
  170. * sections.
  171. */
  172. function load(PelDataWindow $d) {
  173.  
  174. Pel::debug('Parsing %d bytes...', $d->getSize());
  175.  
  176. /* JPEG data is stored in big-endian format. */
  177. $d->setByteOrder(PelConvert::BIG_ENDIAN);
  178. /* Run through the data to read the sections in the image. After
  179. * each section is read, the start of the data window will be
  180. * moved forward, and after the last section we'll terminate with
  181. * no data left in the window. */
  182. while ($d->getSize() > 0) {
  183. /* JPEG sections start with 0xFF. The first byte that is not
  184. * 0xFF is a marker (hopefully).
  185. */
  186. for ($i = 0; $i < 7; $i++)
  187. if ($d->getByte($i) != 0xFF)
  188. break;
  189.  
  190. $marker = $d->getByte($i);
  191.  
  192. if (!PelJpegMarker::isValid($marker))
  193. throw new PelJpegInvalidMarkerException($marker, $i);
  194.  
  195. /* Move window so first byte becomes first byte in this
  196. * section. */
  197. $d->setWindowStart($i+1);
  198.  
  199. if ($marker == PelJpegMarker::SOI || $marker == PelJpegMarker::EOI) {
  200. $content = new PelJpegContent(new PelDataWindow());
  201. $this->appendSection($marker, $content);
  202. } else {
  203. /* Read the length of the section. The length includes the
  204. * two bytes used to store the length. */
  205. $len = $d->getShort(0) - 2;
  206. Pel::debug('Found %s section of length %d',
  207. PelJpegMarker::getName($marker), $len);
  208.  
  209. /* Skip past the length. */
  210. $d->setWindowStart(2);
  211.  
  212. if ($marker == PelJpegMarker::APP1) {
  213. try {
  214. $content = new PelExif();
  215. $content->load($d->getClone(0, $len));
  216. } catch (PelInvalidDataException $e) {
  217. Pel::warning('Found non-EXIF APP1 section.');
  218. /* We store the data as normal JPEG content if it could
  219. * not be parsed as EXIF data. */
  220. $content = new PelJpegContent($d->getClone(0, $len));
  221. }
  222. $this->appendSection($marker, $content);
  223. /* Skip past the data. */
  224. $d->setWindowStart($len);
  225. } else {
  226. $content = new PelJpegContent($d->getClone(0, $len));
  227. $this->appendSection($marker, $content);
  228. /* Skip past the data. */
  229. $d->setWindowStart($len);
  230. /* In case of SOS, image data will follow. */
  231. if ($marker == PelJpegMarker::SOS) {
  232. /* Some images have some trailing (garbage?) following the
  233. * EOI marker. To handle this we seek backwards until we
  234. * find the EOI marker. Any trailing content is stored as
  235. * a PelJpegContent object. */
  236.  
  237. $length = $d->getSize();
  238. while ($d->getByte($length-2) != 0xFF ||
  239. $d->getByte($length-1) != PelJpegMarker::EOI) {
  240. $length--;
  241. }
  242.  
  243. $this->jpeg_data = $d->getClone(0, $length-2);
  244. Pel::debug('JPEG data: ' . $this->jpeg_data->__toString());
  245.  
  246. /* Append the EOI. */
  247. $this->appendSection(PelJpegMarker::EOI,
  248. new PelJpegContent(new PelDataWindow()));
  249.  
  250. /* Now check to see if there are any trailing data. */
  251. if ($length != $d->getSize()) {
  252. Pel::warning('Found trailing content after EOI: %d bytes',
  253. $d->getSize() - $length);
  254. $content = new PelJpegContent($d->getClone($length));
  255. /* We don't have a proper JPEG marker for trailing
  256. * garbage, so we just use 0x00... */
  257. $this->appendSection(0x00, $content);
  258. }
  259.  
  260. /* Done with the loop. */
  261. break;
  262. }
  263. }
  264. }
  265. } /* while ($d->getSize() > 0) */
  266. }
  267.  
  268.  
  269. /**
  270. * Load data from a file into a JPEG object.
  271. *
  272. * @param string the filename. This must be a readable file.
  273. */
  274. function loadFile($filename) {
  275. $this->load(new PelDataWindow(file_get_contents($filename)));
  276. }
  277.  
  278. /**
  279. * Add a new section.
  280. *
  281. * @param PelJpegMarker the marker identifying the new section.
  282. *
  283. * @param PelJpegContent the content of the new section.
  284. */
  285. function appendSection($marker, PelJpegContent $content) {
  286. $this->sections[] = array($marker, $content);
  287. }
  288.  
  289.  
  290. /**
  291. * Insert a new section.
  292. *
  293. * @param PelJpegMarker the marker for the new section.
  294. *
  295. * @param PelJpegContent the content of the new section.
  296. *
  297. * @param int the offset where the new section will be inserted ---
  298. * use 0 to insert it at the very beginning, use 1 to insert it
  299. * between sections 1 and 2, etc.
  300. */
  301. function insertSection($marker, PelJpegContent $content, $offset) {
  302. array_splice($this->sections, $offset, 0, array(array($marker, $content)));
  303. }
  304.  
  305.  
  306. /**
  307. * Get a sections corresponding to a particular marker.
  308. *
  309. * This will search through the sections of this JPEG object,
  310. * looking for a section identified with the specified {@link }
  311. * PelJpegMarker marker}. The {@link PelJpegContent content} will
  312. * then be returned. The optional argument can be used to skip over
  313. * some of the sections. So if one is looking for the, say, third
  314. * {@link PelJpegMarker::DHT DHT} section one would do:
  315. *
  316. * <code>
  317. * $dht3 = $jpeg->getSection(PelJpegMarker::DHT, 2);
  318. * </code>
  319. *
  320. * whereas one can just do:
  321. *
  322. * <code>
  323. * $app1 = $jpeg->getSection(PelJpegMarker::APP1);
  324. * </code>
  325. *
  326. * to get hold of the first (and normally only) {@link }
  327. * PelJpegMarker::APP1 APP1} section, which would hold the EXIF
  328. * data.
  329. *
  330. * @param PelJpegMarker the marker identifying the section.
  331. *
  332. * @param int the number of sections to be skipped. This must be a
  333. * non-negative integer.
  334. *
  335. * @return PelJpegContent the content found, or null if there is no
  336. * content available.
  337. */
  338. function getSection($marker, $skip = 0) {
  339. foreach ($this->sections as $s) {
  340. if ($s[0] == $marker)
  341. if ($skip > 0)
  342. $skip--;
  343. else
  344. return $s[1];
  345. }
  346.  
  347. return null;
  348. }
  349.  
  350.  
  351. /**
  352. * Get all sections.
  353. *
  354. * @return array an array of ({@link PelJpegMarker}, {@link }
  355. * PelJpegContent}) pairs.
  356. */
  357. function getSections() {
  358. return $this->sections;
  359. }
  360.  
  361.  
  362. /**
  363. * Turn this JPEG object into bytes.
  364. *
  365. * The bytes returned by this method is ready to be stored in a file
  366. * as a valid JPEG image.
  367. *
  368. * @return string bytes representing this JPEG object, including all
  369. * its sections and their associated data.
  370. */
  371. function getBytes() {
  372. $bytes = '';
  373.  
  374. foreach ($this->sections as $section) {
  375. $m = $section[0];
  376. $c = $section[1];
  377.  
  378. /* Write the marker */
  379. $bytes .= "\xFF" . PelJpegMarker::getBytes($m);
  380. if ($m == PelJpegMarker::SOI ||
  381. $m == PelJpegMarker::EOI)
  382. continue;
  383.  
  384. $data = $c->getBytes();
  385. $size = strlen($data);
  386. $bytes .= chr(($size + 2) >> 8);
  387. $bytes .= chr($size + 2);
  388. $bytes .= $data;
  389. /* In case of SOS, we need to write the JPEG data. */
  390. if ($m == PelJpegMarker::SOS)
  391. $bytes .= $this->jpeg_data->getBytes();
  392. }
  393.  
  394. return $bytes;
  395.  
  396. }
  397.  
  398.  
  399. /**
  400. * Make a string representation of this JPEG object.
  401. *
  402. * This is mainly usefull for debugging. It will show the structure
  403. * of the image, and its sections.
  404. *
  405. * @return string debugging information about this JPEG object.
  406. */
  407. function __toString() {
  408. $str = Pel::tra("Dumping JPEG data...\n");
  409. for ($i = 0; $i < count($this->sections); $i++) {
  410. $m = $this->sections[$i][0];
  411. $c = $this->sections[$i][1];
  412. $str .= Pel::fmt("Section %d (marker 0x%02X - %s):\n",
  413. $i, $m, PelJpegMarker::getName($m));
  414. $str .= Pel::fmt(" Description: %s\n",
  415. PelJpegMarker::getDescription($m));
  416. if ($m == PelJpegMarker::SOI ||
  417. $m == PelJpegMarker::EOI)
  418. continue;
  419. if ($c instanceof PelExif) {
  420. $str .= Pel::tra(" Content : EXIF data\n");
  421. $str .= $c->__toString() . "\n";
  422. } else {
  423. $str .= Pel::fmt(" Size : %d bytes\n", $c->getSize());
  424. $str .= Pel::tra(" Content : Unknown\n");
  425. }
  426. }
  427.  
  428. return $str;
  429. }
  430.  
  431.  
  432. /**
  433. * Test data to see if it could be a valid JPEG image.
  434. *
  435. * The function will only look at the first few bytes of the data,
  436. * and try to determine if it could be a valid JPEG image based on
  437. * those bytes. This means that the check is more like a heuristic
  438. * than a rigorous check.
  439. *
  440. * @param PelDataWindow the bytes that will be checked.
  441. *
  442. * @return boolean true if the bytes look like the beginning of a
  443. * JPEG image, false otherwise.
  444. *
  445. * @see PelTiff::isValid()
  446. */
  447. static function isValid(PelDataWindow $d) {
  448. /* JPEG data is stored in big-endian format. */
  449. $d->setByteOrder(PelConvert::BIG_ENDIAN);
  450. for ($i = 0; $i < 7; $i++)
  451. if ($d->getByte($i) != 0xFF)
  452. break;
  453. return $d->getByte($i) == PelJpegMarker::SOI;
  454. }
  455.  
  456. }
  457.  
  458. ?>

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