X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=vendor%2Flsolesen%2Fpel%2Fsrc%2FPelJpeg.php;fp=vendor%2Flsolesen%2Fpel%2Fsrc%2FPelJpeg.php;h=c12e71cfc042a3206f55d42773333836edcc5301;hp=0000000000000000000000000000000000000000;hb=0bf8d09d2542548982e81a441b1f16e75873a04f;hpb=74df008bdbb3a11eeea356744f39b802369bda3c diff --git a/vendor/lsolesen/pel/src/PelJpeg.php b/vendor/lsolesen/pel/src/PelJpeg.php new file mode 100644 index 000000000..c12e71cfc --- /dev/null +++ b/vendor/lsolesen/pel/src/PelJpeg.php @@ -0,0 +1,663 @@ + + * $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 + */ + private $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])) { + if ($this->sections[$i][0] == PelJpegMarker::APP0) { + $app0_offset = $i; + } elseif ($this->sections[$i][0] == PelJpegMarker::APP1) { + $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 Exif 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() + { + $exif = $this->getSection(PelJpegMarker::APP1); + if ($exif instanceof PelExif) { + return $exif; + } + 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 the first @{link PelJpegMarker::APP1} + * section found (there should normally be just one). + */ + public function clearExif() + { + $sections_count = count($this->sections); + for ($i = 0; $i < $sections_count; $i ++) { + if ($this->sections[$i][0] == PelJpegMarker::APP1) { + unset($this->sections[$i]); + return; + } + } + } + + /** + * 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; + } +}