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

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