1<?php 2/** 3 * JPEG metadata reader/writer 4 * 5 * @license BSD <http://www.opensource.org/licenses/bsd-license.php> 6 * @link http://github.com/sd/jpeg-php 7 * @author Sebastian Delmont <sdelmont@zonageek.com> 8 * @author Andreas Gohr <andi@splitbrain.org> 9 * @author Hakan Sandell <hakan.sandell@mydata.se> 10 * @todo Add support for Maker Notes, Extend for GIF and PNG metadata 11 */ 12 13// Original copyright notice: 14// 15// Copyright (c) 2003 Sebastian Delmont <sdelmont@zonageek.com> 16// All rights reserved. 17// 18// Redistribution and use in source and binary forms, with or without 19// modification, are permitted provided that the following conditions 20// are met: 21// 1. Redistributions of source code must retain the above copyright 22// notice, this list of conditions and the following disclaimer. 23// 2. Redistributions in binary form must reproduce the above copyright 24// notice, this list of conditions and the following disclaimer in the 25// documentation and/or other materials provided with the distribution. 26// 3. Neither the name of the author nor the names of its contributors 27// may be used to endorse or promote products derived from this software 28// without specific prior written permission. 29// 30// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 31// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 32// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 33// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 34// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 36// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 37// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 38// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 39// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 40// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE 41 42class JpegMeta { 43 var $_fileName; 44 var $_fp = null; 45 var $_fpout = null; 46 var $_type = 'unknown'; 47 48 var $_markers; 49 var $_info; 50 51 52 /** 53 * Constructor 54 * 55 * @author Sebastian Delmont <sdelmont@zonageek.com> 56 */ 57 function JpegMeta($fileName) { 58 59 $this->_fileName = $fileName; 60 61 $this->_fp = null; 62 $this->_type = 'unknown'; 63 64 unset($this->_info); 65 unset($this->_markers); 66 } 67 68 /** 69 * Returns all gathered info as multidim array 70 * 71 * @author Sebastian Delmont <sdelmont@zonageek.com> 72 */ 73 function & getRawInfo() { 74 $this->_parseAll(); 75 76 if ($this->_markers == null) { 77 return false; 78 } 79 80 return $this->_info; 81 } 82 83 /** 84 * Returns basic image info 85 * 86 * @author Sebastian Delmont <sdelmont@zonageek.com> 87 */ 88 function & getBasicInfo() { 89 $this->_parseAll(); 90 91 $info = array(); 92 93 if ($this->_markers == null) { 94 return false; 95 } 96 97 $info['Name'] = $this->_info['file']['Name']; 98 if (isset($this->_info['file']['Url'])) { 99 $info['Url'] = $this->_info['file']['Url']; 100 $info['NiceSize'] = "???KB"; 101 } else { 102 $info['Size'] = $this->_info['file']['Size']; 103 $info['NiceSize'] = $this->_info['file']['NiceSize']; 104 } 105 106 if (@isset($this->_info['sof']['Format'])) { 107 $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; 108 } else { 109 $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; 110 } 111 112 if (@isset($this->_info['sof']['ColorChannels'])) { 113 $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W"; 114 } 115 116 $info['Width'] = $this->getWidth(); 117 $info['Height'] = $this->getHeight(); 118 $info['DimStr'] = $this->getDimStr(); 119 120 $dates = $this->getDates(); 121 122 $info['DateTime'] = $dates['EarliestTime']; 123 $info['DateTimeStr'] = $dates['EarliestTimeStr']; 124 125 $info['HasThumbnail'] = $this->hasThumbnail(); 126 127 return $info; 128 } 129 130 131 /** 132 * Convinience function to access nearly all available Data 133 * through one function 134 * 135 * @author Andreas Gohr <andi@splitbrain.org> 136 * 137 * @param array|string $fields field name or array with field names 138 * @return bool|string 139 */ 140 function getField($fields) { 141 if(!is_array($fields)) $fields = array($fields); 142 $info = false; 143 foreach($fields as $field){ 144 if(strtolower(substr($field,0,5)) == 'iptc.'){ 145 $info = $this->getIPTCField(substr($field,5)); 146 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 147 $info = $this->getExifField(substr($field,5)); 148 }elseif(strtolower(substr($field,0,4)) == 'xmp.'){ 149 $info = $this->getXmpField(substr($field,4)); 150 }elseif(strtolower(substr($field,0,5)) == 'file.'){ 151 $info = $this->getFileField(substr($field,5)); 152 }elseif(strtolower(substr($field,0,5)) == 'date.'){ 153 $info = $this->getDateField(substr($field,5)); 154 }elseif(strtolower($field) == 'simple.camera'){ 155 $info = $this->getCamera(); 156 }elseif(strtolower($field) == 'simple.raw'){ 157 return $this->getRawInfo(); 158 }elseif(strtolower($field) == 'simple.title'){ 159 $info = $this->getTitle(); 160 }elseif(strtolower($field) == 'simple.shutterspeed'){ 161 $info = $this->getShutterSpeed(); 162 }else{ 163 $info = $this->getExifField($field); 164 } 165 if($info != false) break; 166 } 167 168 if($info === false) $info = ''; 169 if(is_array($info)){ 170 if(isset($info['val'])){ 171 $info = $info['val']; 172 }else{ 173 $info = join(', ',$info); 174 } 175 } 176 return trim($info); 177 } 178 179 /** 180 * Convinience function to set nearly all available Data 181 * through one function 182 * 183 * @author Andreas Gohr <andi@splitbrain.org> 184 * 185 * @param string $field field name 186 * @param string $value 187 * @return bool success or fail 188 */ 189 function setField($field, $value) { 190 if(strtolower(substr($field,0,5)) == 'iptc.'){ 191 return $this->setIPTCField(substr($field,5),$value); 192 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 193 return $this->setExifField(substr($field,5),$value); 194 }else{ 195 return $this->setExifField($field,$value); 196 } 197 } 198 199 /** 200 * Convinience function to delete nearly all available Data 201 * through one function 202 * 203 * @author Andreas Gohr <andi@splitbrain.org> 204 * 205 * @param string $field field name 206 * @return bool 207 */ 208 function deleteField($field) { 209 if(strtolower(substr($field,0,5)) == 'iptc.'){ 210 return $this->deleteIPTCField(substr($field,5)); 211 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 212 return $this->deleteExifField(substr($field,5)); 213 }else{ 214 return $this->deleteExifField($field); 215 } 216 } 217 218 /** 219 * Return a date field 220 * 221 * @author Andreas Gohr <andi@splitbrain.org> 222 * 223 * @param string $field 224 * @return false|string 225 */ 226 function getDateField($field) { 227 if (!isset($this->_info['dates'])) { 228 $this->_info['dates'] = $this->getDates(); 229 } 230 231 if (isset($this->_info['dates'][$field])) { 232 return $this->_info['dates'][$field]; 233 } 234 235 return false; 236 } 237 238 /** 239 * Return a file info field 240 * 241 * @author Andreas Gohr <andi@splitbrain.org> 242 * 243 * @param string $field field name 244 * @return false|string 245 */ 246 function getFileField($field) { 247 if (!isset($this->_info['file'])) { 248 $this->_parseFileInfo(); 249 } 250 251 if (isset($this->_info['file'][$field])) { 252 return $this->_info['file'][$field]; 253 } 254 255 return false; 256 } 257 258 /** 259 * Return the camera info (Maker and Model) 260 * 261 * @author Andreas Gohr <andi@splitbrain.org> 262 * @todo handle makernotes 263 * 264 * @return false|string 265 */ 266 function getCamera(){ 267 $make = $this->getField(array('Exif.Make','Exif.TIFFMake')); 268 $model = $this->getField(array('Exif.Model','Exif.TIFFModel')); 269 $cam = trim("$make $model"); 270 if(empty($cam)) return false; 271 return $cam; 272 } 273 274 /** 275 * Return shutter speed as a ratio 276 * 277 * @author Joe Lapp <joe.lapp@pobox.com> 278 * 279 * @return string 280 */ 281 function getShutterSpeed() { 282 if (!isset($this->_info['exif'])) { 283 $this->_parseMarkerExif(); 284 } 285 if(!isset($this->_info['exif']['ExposureTime'])){ 286 return ''; 287 } 288 289 $field = $this->_info['exif']['ExposureTime']; 290 if($field['den'] == 1) return $field['num']; 291 return $field['num'].'/'.$field['den']; 292 } 293 294 /** 295 * Return an EXIF field 296 * 297 * @author Sebastian Delmont <sdelmont@zonageek.com> 298 * 299 * @param string $field field name 300 * @return false|string 301 */ 302 function getExifField($field) { 303 if (!isset($this->_info['exif'])) { 304 $this->_parseMarkerExif(); 305 } 306 307 if ($this->_markers == null) { 308 return false; 309 } 310 311 if (isset($this->_info['exif'][$field])) { 312 return $this->_info['exif'][$field]; 313 } 314 315 return false; 316 } 317 318 /** 319 * Return an XMP field 320 * 321 * @author Hakan Sandell <hakan.sandell@mydata.se> 322 * 323 * @param string $field field name 324 * @return false|string 325 */ 326 function getXmpField($field) { 327 if (!isset($this->_info['xmp'])) { 328 $this->_parseMarkerXmp(); 329 } 330 331 if ($this->_markers == null) { 332 return false; 333 } 334 335 if (isset($this->_info['xmp'][$field])) { 336 return $this->_info['xmp'][$field]; 337 } 338 339 return false; 340 } 341 342 /** 343 * Return an Adobe Field 344 * 345 * @author Sebastian Delmont <sdelmont@zonageek.com> 346 * 347 * @param string $field field name 348 * @return false|string 349 */ 350 function getAdobeField($field) { 351 if (!isset($this->_info['adobe'])) { 352 $this->_parseMarkerAdobe(); 353 } 354 355 if ($this->_markers == null) { 356 return false; 357 } 358 359 if (isset($this->_info['adobe'][$field])) { 360 return $this->_info['adobe'][$field]; 361 } 362 363 return false; 364 } 365 366 /** 367 * Return an IPTC field 368 * 369 * @author Sebastian Delmont <sdelmont@zonageek.com> 370 * 371 * @param string $field field name 372 * @return false|string 373 */ 374 function getIPTCField($field) { 375 if (!isset($this->_info['iptc'])) { 376 $this->_parseMarkerAdobe(); 377 } 378 379 if ($this->_markers == null) { 380 return false; 381 } 382 383 if (isset($this->_info['iptc'][$field])) { 384 return $this->_info['iptc'][$field]; 385 } 386 387 return false; 388 } 389 390 /** 391 * Set an EXIF field 392 * 393 * @author Sebastian Delmont <sdelmont@zonageek.com> 394 * @author Joe Lapp <joe.lapp@pobox.com> 395 * 396 * @param string $field field name 397 * @param string $value 398 * @return bool 399 */ 400 function setExifField($field, $value) { 401 if (!isset($this->_info['exif'])) { 402 $this->_parseMarkerExif(); 403 } 404 405 if ($this->_markers == null) { 406 return false; 407 } 408 409 if ($this->_info['exif'] == false) { 410 $this->_info['exif'] = array(); 411 } 412 413 // make sure datetimes are in correct format 414 if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') { 415 if(strlen($value) < 8 || $value{4} != ':' || $value{7} != ':') { 416 $value = date('Y:m:d H:i:s', strtotime($value)); 417 } 418 } 419 420 $this->_info['exif'][$field] = $value; 421 422 return true; 423 } 424 425 /** 426 * Set an Adobe Field 427 * 428 * @author Sebastian Delmont <sdelmont@zonageek.com> 429 * 430 * @param string $field field name 431 * @param string $value 432 * @return bool 433 */ 434 function setAdobeField($field, $value) { 435 if (!isset($this->_info['adobe'])) { 436 $this->_parseMarkerAdobe(); 437 } 438 439 if ($this->_markers == null) { 440 return false; 441 } 442 443 if ($this->_info['adobe'] == false) { 444 $this->_info['adobe'] = array(); 445 } 446 447 $this->_info['adobe'][$field] = $value; 448 449 return true; 450 } 451 452 /** 453 * Calculates the multiplier needed to resize the image to the given 454 * dimensions 455 * 456 * @author Andreas Gohr <andi@splitbrain.org> 457 * 458 * @param int $maxwidth 459 * @param int $maxheight 460 * @return float|int 461 */ 462 function getResizeRatio($maxwidth,$maxheight=0){ 463 if(!$maxheight) $maxheight = $maxwidth; 464 465 $w = $this->getField('File.Width'); 466 $h = $this->getField('File.Height'); 467 468 $ratio = 1; 469 if($w >= $h){ 470 if($w >= $maxwidth){ 471 $ratio = $maxwidth/$w; 472 }elseif($h > $maxheight){ 473 $ratio = $maxheight/$h; 474 } 475 }else{ 476 if($h >= $maxheight){ 477 $ratio = $maxheight/$h; 478 }elseif($w > $maxwidth){ 479 $ratio = $maxwidth/$w; 480 } 481 } 482 return $ratio; 483 } 484 485 486 /** 487 * Set an IPTC field 488 * 489 * @author Sebastian Delmont <sdelmont@zonageek.com> 490 * 491 * @param string $field field name 492 * @param string $value 493 * @return bool 494 */ 495 function setIPTCField($field, $value) { 496 if (!isset($this->_info['iptc'])) { 497 $this->_parseMarkerAdobe(); 498 } 499 500 if ($this->_markers == null) { 501 return false; 502 } 503 504 if ($this->_info['iptc'] == false) { 505 $this->_info['iptc'] = array(); 506 } 507 508 $this->_info['iptc'][$field] = $value; 509 510 return true; 511 } 512 513 /** 514 * Delete an EXIF field 515 * 516 * @author Sebastian Delmont <sdelmont@zonageek.com> 517 * 518 * @param string $field field name 519 * @return bool 520 */ 521 function deleteExifField($field) { 522 if (!isset($this->_info['exif'])) { 523 $this->_parseMarkerAdobe(); 524 } 525 526 if ($this->_markers == null) { 527 return false; 528 } 529 530 if ($this->_info['exif'] != false) { 531 unset($this->_info['exif'][$field]); 532 } 533 534 return true; 535 } 536 537 /** 538 * Delete an Adobe field 539 * 540 * @author Sebastian Delmont <sdelmont@zonageek.com> 541 * 542 * @param string $field field name 543 * @return bool 544 */ 545 function deleteAdobeField($field) { 546 if (!isset($this->_info['adobe'])) { 547 $this->_parseMarkerAdobe(); 548 } 549 550 if ($this->_markers == null) { 551 return false; 552 } 553 554 if ($this->_info['adobe'] != false) { 555 unset($this->_info['adobe'][$field]); 556 } 557 558 return true; 559 } 560 561 /** 562 * Delete an IPTC field 563 * 564 * @author Sebastian Delmont <sdelmont@zonageek.com> 565 * 566 * @param string $field field name 567 * @return bool 568 */ 569 function deleteIPTCField($field) { 570 if (!isset($this->_info['iptc'])) { 571 $this->_parseMarkerAdobe(); 572 } 573 574 if ($this->_markers == null) { 575 return false; 576 } 577 578 if ($this->_info['iptc'] != false) { 579 unset($this->_info['iptc'][$field]); 580 } 581 582 return true; 583 } 584 585 /** 586 * Get the image's title, tries various fields 587 * 588 * @param int $max maximum number chars (keeps words) 589 * @return false|string 590 * 591 * @author Andreas Gohr <andi@splitbrain.org> 592 */ 593 function getTitle($max=80){ 594 // try various fields 595 $cap = $this->getField(array('Iptc.Headline', 596 'Iptc.Caption', 597 'Xmp.dc:title', 598 'Exif.UserComment', 599 'Exif.TIFFUserComment', 600 'Exif.TIFFImageDescription', 601 'File.Name')); 602 if (empty($cap)) return false; 603 604 if(!$max) return $cap; 605 // Shorten to 80 chars (keeping words) 606 $new = preg_replace('/\n.+$/','',wordwrap($cap, $max)); 607 if($new != $cap) $new .= '...'; 608 609 return $new; 610 } 611 612 /** 613 * Gather various date fields 614 * 615 * @author Sebastian Delmont <sdelmont@zonageek.com> 616 * 617 * @return array|bool 618 */ 619 function getDates() { 620 $this->_parseAll(); 621 if ($this->_markers == null) { 622 if (@isset($this->_info['file']['UnixTime'])) { 623 $dates = array(); 624 $dates['FileModified'] = $this->_info['file']['UnixTime']; 625 $dates['Time'] = $this->_info['file']['UnixTime']; 626 $dates['TimeSource'] = 'FileModified'; 627 $dates['TimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 628 $dates['EarliestTime'] = $this->_info['file']['UnixTime']; 629 $dates['EarliestTimeSource'] = 'FileModified'; 630 $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 631 $dates['LatestTime'] = $this->_info['file']['UnixTime']; 632 $dates['LatestTimeSource'] = 'FileModified'; 633 $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 634 return $dates; 635 } 636 return false; 637 } 638 639 $dates = array(); 640 641 $latestTime = 0; 642 $latestTimeSource = ""; 643 $earliestTime = time(); 644 $earliestTimeSource = ""; 645 646 if (@isset($this->_info['exif']['DateTime'])) { 647 $dates['ExifDateTime'] = $this->_info['exif']['DateTime']; 648 649 $aux = $this->_info['exif']['DateTime']; 650 $aux{4} = "-"; 651 $aux{7} = "-"; 652 $t = strtotime($aux); 653 654 if ($t && $t > $latestTime) { 655 $latestTime = $t; 656 $latestTimeSource = "ExifDateTime"; 657 } 658 659 if ($t && $t < $earliestTime) { 660 $earliestTime = $t; 661 $earliestTimeSource = "ExifDateTime"; 662 } 663 } 664 665 if (@isset($this->_info['exif']['DateTimeOriginal'])) { 666 $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime']; 667 668 $aux = $this->_info['exif']['DateTimeOriginal']; 669 $aux{4} = "-"; 670 $aux{7} = "-"; 671 $t = strtotime($aux); 672 673 if ($t && $t > $latestTime) { 674 $latestTime = $t; 675 $latestTimeSource = "ExifDateTimeOriginal"; 676 } 677 678 if ($t && $t < $earliestTime) { 679 $earliestTime = $t; 680 $earliestTimeSource = "ExifDateTimeOriginal"; 681 } 682 } 683 684 if (@isset($this->_info['exif']['DateTimeDigitized'])) { 685 $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime']; 686 687 $aux = $this->_info['exif']['DateTimeDigitized']; 688 $aux{4} = "-"; 689 $aux{7} = "-"; 690 $t = strtotime($aux); 691 692 if ($t && $t > $latestTime) { 693 $latestTime = $t; 694 $latestTimeSource = "ExifDateTimeDigitized"; 695 } 696 697 if ($t && $t < $earliestTime) { 698 $earliestTime = $t; 699 $earliestTimeSource = "ExifDateTimeDigitized"; 700 } 701 } 702 703 if (@isset($this->_info['iptc']['DateCreated'])) { 704 $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated']; 705 706 $aux = $this->_info['iptc']['DateCreated']; 707 $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2); 708 $t = strtotime($aux); 709 710 if ($t && $t > $latestTime) { 711 $latestTime = $t; 712 $latestTimeSource = "IPTCDateCreated"; 713 } 714 715 if ($t && $t < $earliestTime) { 716 $earliestTime = $t; 717 $earliestTimeSource = "IPTCDateCreated"; 718 } 719 } 720 721 if (@isset($this->_info['file']['UnixTime'])) { 722 $dates['FileModified'] = $this->_info['file']['UnixTime']; 723 724 $t = $this->_info['file']['UnixTime']; 725 726 if ($t && $t > $latestTime) { 727 $latestTime = $t; 728 $latestTimeSource = "FileModified"; 729 } 730 731 if ($t && $t < $earliestTime) { 732 $earliestTime = $t; 733 $earliestTimeSource = "FileModified"; 734 } 735 } 736 737 $dates['Time'] = $earliestTime; 738 $dates['TimeSource'] = $earliestTimeSource; 739 $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime); 740 $dates['EarliestTime'] = $earliestTime; 741 $dates['EarliestTimeSource'] = $earliestTimeSource; 742 $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime); 743 $dates['LatestTime'] = $latestTime; 744 $dates['LatestTimeSource'] = $latestTimeSource; 745 $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime); 746 747 return $dates; 748 } 749 750 /** 751 * Get the image width, tries various fields 752 * 753 * @author Sebastian Delmont <sdelmont@zonageek.com> 754 * 755 * @return false|string 756 */ 757 function getWidth() { 758 if (!isset($this->_info['sof'])) { 759 $this->_parseMarkerSOF(); 760 } 761 762 if ($this->_markers == null) { 763 return false; 764 } 765 766 if (isset($this->_info['sof']['ImageWidth'])) { 767 return $this->_info['sof']['ImageWidth']; 768 } 769 770 if (!isset($this->_info['exif'])) { 771 $this->_parseMarkerExif(); 772 } 773 774 if (isset($this->_info['exif']['PixelXDimension'])) { 775 return $this->_info['exif']['PixelXDimension']; 776 } 777 778 return false; 779 } 780 781 /** 782 * Get the image height, tries various fields 783 * 784 * @author Sebastian Delmont <sdelmont@zonageek.com> 785 * 786 * @return false|string 787 */ 788 function getHeight() { 789 if (!isset($this->_info['sof'])) { 790 $this->_parseMarkerSOF(); 791 } 792 793 if ($this->_markers == null) { 794 return false; 795 } 796 797 if (isset($this->_info['sof']['ImageHeight'])) { 798 return $this->_info['sof']['ImageHeight']; 799 } 800 801 if (!isset($this->_info['exif'])) { 802 $this->_parseMarkerExif(); 803 } 804 805 if (isset($this->_info['exif']['PixelYDimension'])) { 806 return $this->_info['exif']['PixelYDimension']; 807 } 808 809 return false; 810 } 811 812 /** 813 * Get an dimension string for use in img tag 814 * 815 * @author Sebastian Delmont <sdelmont@zonageek.com> 816 * 817 * @return false|string 818 */ 819 function getDimStr() { 820 if ($this->_markers == null) { 821 return false; 822 } 823 824 $w = $this->getWidth(); 825 $h = $this->getHeight(); 826 827 return "width='" . $w . "' height='" . $h . "'"; 828 } 829 830 /** 831 * Checks for an embedded thumbnail 832 * 833 * @author Sebastian Delmont <sdelmont@zonageek.com> 834 * 835 * @param string $which possible values: 'any', 'exif' or 'adobe' 836 * @return false|string 837 */ 838 function hasThumbnail($which = 'any') { 839 if (($which == 'any') || ($which == 'exif')) { 840 if (!isset($this->_info['exif'])) { 841 $this->_parseMarkerExif(); 842 } 843 844 if ($this->_markers == null) { 845 return false; 846 } 847 848 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 849 if (isset($this->_info['exif']['JFIFThumbnail'])) { 850 return 'exif'; 851 } 852 } 853 } 854 855 if ($which == 'adobe') { 856 if (!isset($this->_info['adobe'])) { 857 $this->_parseMarkerAdobe(); 858 } 859 860 if ($this->_markers == null) { 861 return false; 862 } 863 864 if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { 865 if (isset($this->_info['adobe']['ThumbnailData'])) { 866 return 'exif'; 867 } 868 } 869 } 870 871 return false; 872 } 873 874 /** 875 * Send embedded thumbnail to browser 876 * 877 * @author Sebastian Delmont <sdelmont@zonageek.com> 878 * 879 * @param string $which possible values: 'any', 'exif' or 'adobe' 880 * @return bool 881 */ 882 function sendThumbnail($which = 'any') { 883 $data = null; 884 885 if (($which == 'any') || ($which == 'exif')) { 886 if (!isset($this->_info['exif'])) { 887 $this->_parseMarkerExif(); 888 } 889 890 if ($this->_markers == null) { 891 return false; 892 } 893 894 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 895 if (isset($this->_info['exif']['JFIFThumbnail'])) { 896 $data =& $this->_info['exif']['JFIFThumbnail']; 897 } 898 } 899 } 900 901 if (($which == 'adobe') || ($data == null)){ 902 if (!isset($this->_info['adobe'])) { 903 $this->_parseMarkerAdobe(); 904 } 905 906 if ($this->_markers == null) { 907 return false; 908 } 909 910 if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { 911 if (isset($this->_info['adobe']['ThumbnailData'])) { 912 $data =& $this->_info['adobe']['ThumbnailData']; 913 } 914 } 915 } 916 917 if ($data != null) { 918 header("Content-type: image/jpeg"); 919 echo $data; 920 return true; 921 } 922 923 return false; 924 } 925 926 /** 927 * Save changed Metadata 928 * 929 * @author Sebastian Delmont <sdelmont@zonageek.com> 930 * @author Andreas Gohr <andi@splitbrain.org> 931 * 932 * @param string $fileName file name or empty string for a random name 933 * @return bool 934 */ 935 function save($fileName = "") { 936 if ($fileName == "") { 937 $tmpName = tempnam(dirname($this->_fileName),'_metatemp_'); 938 $this->_writeJPEG($tmpName); 939 if (file_exists($tmpName)) { 940 return io_rename($tmpName, $this->_fileName); 941 } 942 } else { 943 return $this->_writeJPEG($fileName); 944 } 945 return false; 946 } 947 948 /*************************************************************/ 949 /* PRIVATE FUNCTIONS (Internal Use Only!) */ 950 /*************************************************************/ 951 952 /*************************************************************/ 953 function _dispose($fileName = "") { 954 $this->_fileName = $fileName; 955 956 $this->_fp = null; 957 $this->_type = 'unknown'; 958 959 unset($this->_markers); 960 unset($this->_info); 961 } 962 963 /*************************************************************/ 964 function _readJPEG() { 965 unset($this->_markers); 966 //unset($this->_info); 967 $this->_markers = array(); 968 //$this->_info = array(); 969 970 $this->_fp = @fopen($this->_fileName, 'rb'); 971 if ($this->_fp) { 972 if (file_exists($this->_fileName)) { 973 $this->_type = 'file'; 974 } 975 else { 976 $this->_type = 'url'; 977 } 978 } else { 979 $this->_fp = null; 980 return false; // ERROR: Can't open file 981 } 982 983 // Check for the JPEG signature 984 $c1 = ord(fgetc($this->_fp)); 985 $c2 = ord(fgetc($this->_fp)); 986 987 if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) 988 $this->_markers = null; 989 return false; // ERROR: File is not a JPEG 990 } 991 992 $count = 0; 993 994 $done = false; 995 $ok = true; 996 997 while (!$done) { 998 $capture = false; 999 1000 // First, skip any non 0xFF bytes 1001 $discarded = 0; 1002 $c = ord(fgetc($this->_fp)); 1003 while (!feof($this->_fp) && ($c != 0xFF)) { 1004 $discarded++; 1005 $c = ord(fgetc($this->_fp)); 1006 } 1007 // Then skip all 0xFF until the marker byte 1008 do { 1009 $marker = ord(fgetc($this->_fp)); 1010 } while (!feof($this->_fp) && ($marker == 0xFF)); 1011 1012 if (feof($this->_fp)) { 1013 return false; // ERROR: Unexpected EOF 1014 } 1015 if ($discarded != 0) { 1016 return false; // ERROR: Extraneous data 1017 } 1018 1019 $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); 1020 if (feof($this->_fp)) { 1021 return false; // ERROR: Unexpected EOF 1022 } 1023 if ($length < 2) { 1024 return false; // ERROR: Extraneous data 1025 } 1026 $length = $length - 2; // The length we got counts itself 1027 1028 switch ($marker) { 1029 case 0xC0: // SOF0 1030 case 0xC1: // SOF1 1031 case 0xC2: // SOF2 1032 case 0xC9: // SOF9 1033 case 0xE0: // APP0: JFIF data 1034 case 0xE1: // APP1: EXIF or XMP data 1035 case 0xED: // APP13: IPTC / Photoshop data 1036 $capture = true; 1037 break; 1038 case 0xDA: // SOS: Start of scan... the image itself and the last block on the file 1039 $capture = false; 1040 $length = -1; // This field has no length... it includes all data until EOF 1041 $done = true; 1042 break; 1043 default: 1044 $capture = true;//false; 1045 break; 1046 } 1047 1048 $this->_markers[$count] = array(); 1049 $this->_markers[$count]['marker'] = $marker; 1050 $this->_markers[$count]['length'] = $length; 1051 1052 if ($capture) { 1053 if ($length) 1054 $this->_markers[$count]['data'] = fread($this->_fp, $length); 1055 else 1056 $this->_markers[$count]['data'] = ""; 1057 } 1058 elseif (!$done) { 1059 $result = @fseek($this->_fp, $length, SEEK_CUR); 1060 // fseek doesn't seem to like HTTP 'files', but fgetc has no problem 1061 if (!($result === 0)) { 1062 for ($i = 0; $i < $length; $i++) { 1063 fgetc($this->_fp); 1064 } 1065 } 1066 } 1067 $count++; 1068 } 1069 1070 if ($this->_fp) { 1071 fclose($this->_fp); 1072 $this->_fp = null; 1073 } 1074 1075 return $ok; 1076 } 1077 1078 /*************************************************************/ 1079 function _parseAll() { 1080 if (!isset($this->_info['file'])) { 1081 $this->_parseFileInfo(); 1082 } 1083 if (!isset($this->_markers)) { 1084 $this->_readJPEG(); 1085 } 1086 1087 if ($this->_markers == null) { 1088 return false; 1089 } 1090 1091 if (!isset($this->_info['jfif'])) { 1092 $this->_parseMarkerJFIF(); 1093 } 1094 if (!isset($this->_info['jpeg'])) { 1095 $this->_parseMarkerSOF(); 1096 } 1097 if (!isset($this->_info['exif'])) { 1098 $this->_parseMarkerExif(); 1099 } 1100 if (!isset($this->_info['xmp'])) { 1101 $this->_parseMarkerXmp(); 1102 } 1103 if (!isset($this->_info['adobe'])) { 1104 $this->_parseMarkerAdobe(); 1105 } 1106 } 1107 1108 /*************************************************************/ 1109 1110 /** 1111 * @param string $outputName 1112 */ 1113 function _writeJPEG($outputName) { 1114 $this->_parseAll(); 1115 1116 $wroteEXIF = false; 1117 $wroteAdobe = false; 1118 1119 $this->_fp = @fopen($this->_fileName, 'r'); 1120 if ($this->_fp) { 1121 if (file_exists($this->_fileName)) { 1122 $this->_type = 'file'; 1123 } 1124 else { 1125 $this->_type = 'url'; 1126 } 1127 } else { 1128 $this->_fp = null; 1129 return false; // ERROR: Can't open file 1130 } 1131 1132 $this->_fpout = fopen($outputName, 'wb'); 1133 if (!$this->_fpout) { 1134 $this->_fpout = null; 1135 fclose($this->_fp); 1136 $this->_fp = null; 1137 return false; // ERROR: Can't open output file 1138 } 1139 1140 // Check for the JPEG signature 1141 $c1 = ord(fgetc($this->_fp)); 1142 $c2 = ord(fgetc($this->_fp)); 1143 1144 if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) 1145 return false; // ERROR: File is not a JPEG 1146 } 1147 1148 fputs($this->_fpout, chr(0xFF), 1); 1149 fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI) 1150 1151 $count = 0; 1152 1153 $done = false; 1154 $ok = true; 1155 1156 while (!$done) { 1157 // First, skip any non 0xFF bytes 1158 $discarded = 0; 1159 $c = ord(fgetc($this->_fp)); 1160 while (!feof($this->_fp) && ($c != 0xFF)) { 1161 $discarded++; 1162 $c = ord(fgetc($this->_fp)); 1163 } 1164 // Then skip all 0xFF until the marker byte 1165 do { 1166 $marker = ord(fgetc($this->_fp)); 1167 } while (!feof($this->_fp) && ($marker == 0xFF)); 1168 1169 if (feof($this->_fp)) { 1170 $ok = false; 1171 break; // ERROR: Unexpected EOF 1172 } 1173 if ($discarded != 0) { 1174 $ok = false; 1175 break; // ERROR: Extraneous data 1176 } 1177 1178 $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); 1179 if (feof($this->_fp)) { 1180 $ok = false; 1181 break; // ERROR: Unexpected EOF 1182 } 1183 if ($length < 2) { 1184 $ok = false; 1185 break; // ERROR: Extraneous data 1186 } 1187 $length = $length - 2; // The length we got counts itself 1188 1189 unset($data); 1190 if ($marker == 0xE1) { // APP1: EXIF data 1191 $data =& $this->_createMarkerEXIF(); 1192 $wroteEXIF = true; 1193 } 1194 elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data 1195 $data =& $this->_createMarkerAdobe(); 1196 $wroteAdobe = true; 1197 } 1198 elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file 1199 $done = true; 1200 } 1201 1202 if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) { 1203 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 1204 $exif =& $this->_createMarkerEXIF(); 1205 $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0); 1206 unset($exif); 1207 } 1208 $wroteEXIF = true; 1209 } 1210 1211 if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) { 1212 if ((isset($this->_info['adobe']) && is_array($this->_info['adobe'])) 1213 || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) { 1214 $adobe =& $this->_createMarkerAdobe(); 1215 $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0); 1216 unset($adobe); 1217 } 1218 $wroteAdobe = true; 1219 } 1220 1221 $origLength = $length; 1222 if (isset($data)) { 1223 $length = strlen($data); 1224 } 1225 1226 if ($marker != -1) { 1227 $this->_writeJPEGMarker($marker, $length, $data, $origLength); 1228 } 1229 } 1230 1231 if ($this->_fp) { 1232 fclose($this->_fp); 1233 $this->_fp = null; 1234 } 1235 1236 if ($this->_fpout) { 1237 fclose($this->_fpout); 1238 $this->_fpout = null; 1239 } 1240 1241 return $ok; 1242 } 1243 1244 /*************************************************************/ 1245 1246 /** 1247 * @param integer $marker 1248 * @param integer $length 1249 * @param integer $origLength 1250 */ 1251 function _writeJPEGMarker($marker, $length, &$data, $origLength) { 1252 if ($length <= 0) { 1253 return false; 1254 } 1255 1256 fputs($this->_fpout, chr(0xFF), 1); 1257 fputs($this->_fpout, chr($marker), 1); 1258 fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1); 1259 fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1); 1260 1261 if (isset($data)) { 1262 // Copy the generated data 1263 fputs($this->_fpout, $data, $length); 1264 1265 if ($origLength > 0) { // Skip the original data 1266 $result = @fseek($this->_fp, $origLength, SEEK_CUR); 1267 // fseek doesn't seem to like HTTP 'files', but fgetc has no problem 1268 if ($result != 0) { 1269 for ($i = 0; $i < $origLength; $i++) { 1270 fgetc($this->_fp); 1271 } 1272 } 1273 } 1274 } else { 1275 if ($marker == 0xDA) { // Copy until EOF 1276 while (!feof($this->_fp)) { 1277 $data = fread($this->_fp, 1024 * 16); 1278 fputs($this->_fpout, $data, strlen($data)); 1279 } 1280 } else { // Copy only $length bytes 1281 $data = @fread($this->_fp, $length); 1282 fputs($this->_fpout, $data, $length); 1283 } 1284 } 1285 1286 return true; 1287 } 1288 1289 /** 1290 * Gets basic info from the file - should work with non-JPEGs 1291 * 1292 * @author Sebastian Delmont <sdelmont@zonageek.com> 1293 * @author Andreas Gohr <andi@splitbrain.org> 1294 */ 1295 function _parseFileInfo() { 1296 if (file_exists($this->_fileName) && is_file($this->_fileName)) { 1297 $this->_info['file'] = array(); 1298 $this->_info['file']['Name'] = utf8_decodeFN(utf8_basename($this->_fileName)); 1299 $this->_info['file']['Path'] = fullpath($this->_fileName); 1300 $this->_info['file']['Size'] = filesize($this->_fileName); 1301 if ($this->_info['file']['Size'] < 1024) { 1302 $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; 1303 } elseif ($this->_info['file']['Size'] < (1024 * 1024)) { 1304 $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB'; 1305 } elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) { 1306 $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / (1024*1024)) . 'MB'; 1307 } else { 1308 $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; 1309 } 1310 $this->_info['file']['UnixTime'] = filemtime($this->_fileName); 1311 1312 // get image size directly from file 1313 $size = getimagesize($this->_fileName); 1314 $this->_info['file']['Width'] = $size[0]; 1315 $this->_info['file']['Height'] = $size[1]; 1316 // set mime types and formats 1317 // http://www.php.net/manual/en/function.getimagesize.php 1318 // http://www.php.net/manual/en/function.image-type-to-mime-type.php 1319 switch ($size[2]){ 1320 case 1: 1321 $this->_info['file']['Mime'] = 'image/gif'; 1322 $this->_info['file']['Format'] = 'GIF'; 1323 break; 1324 case 2: 1325 $this->_info['file']['Mime'] = 'image/jpeg'; 1326 $this->_info['file']['Format'] = 'JPEG'; 1327 break; 1328 case 3: 1329 $this->_info['file']['Mime'] = 'image/png'; 1330 $this->_info['file']['Format'] = 'PNG'; 1331 break; 1332 case 4: 1333 $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; 1334 $this->_info['file']['Format'] = 'SWF'; 1335 break; 1336 case 5: 1337 $this->_info['file']['Mime'] = 'image/psd'; 1338 $this->_info['file']['Format'] = 'PSD'; 1339 break; 1340 case 6: 1341 $this->_info['file']['Mime'] = 'image/bmp'; 1342 $this->_info['file']['Format'] = 'BMP'; 1343 break; 1344 case 7: 1345 $this->_info['file']['Mime'] = 'image/tiff'; 1346 $this->_info['file']['Format'] = 'TIFF (Intel)'; 1347 break; 1348 case 8: 1349 $this->_info['file']['Mime'] = 'image/tiff'; 1350 $this->_info['file']['Format'] = 'TIFF (Motorola)'; 1351 break; 1352 case 9: 1353 $this->_info['file']['Mime'] = 'application/octet-stream'; 1354 $this->_info['file']['Format'] = 'JPC'; 1355 break; 1356 case 10: 1357 $this->_info['file']['Mime'] = 'image/jp2'; 1358 $this->_info['file']['Format'] = 'JP2'; 1359 break; 1360 case 11: 1361 $this->_info['file']['Mime'] = 'application/octet-stream'; 1362 $this->_info['file']['Format'] = 'JPX'; 1363 break; 1364 case 12: 1365 $this->_info['file']['Mime'] = 'application/octet-stream'; 1366 $this->_info['file']['Format'] = 'JB2'; 1367 break; 1368 case 13: 1369 $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; 1370 $this->_info['file']['Format'] = 'SWC'; 1371 break; 1372 case 14: 1373 $this->_info['file']['Mime'] = 'image/iff'; 1374 $this->_info['file']['Format'] = 'IFF'; 1375 break; 1376 case 15: 1377 $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp'; 1378 $this->_info['file']['Format'] = 'WBMP'; 1379 break; 1380 case 16: 1381 $this->_info['file']['Mime'] = 'image/xbm'; 1382 $this->_info['file']['Format'] = 'XBM'; 1383 break; 1384 default: 1385 $this->_info['file']['Mime'] = 'image/unknown'; 1386 } 1387 } else { 1388 $this->_info['file'] = array(); 1389 $this->_info['file']['Name'] = utf8_basename($this->_fileName); 1390 $this->_info['file']['Url'] = $this->_fileName; 1391 } 1392 1393 return true; 1394 } 1395 1396 /*************************************************************/ 1397 function _parseMarkerJFIF() { 1398 if (!isset($this->_markers)) { 1399 $this->_readJPEG(); 1400 } 1401 1402 if ($this->_markers == null) { 1403 return false; 1404 } 1405 1406 $data = null; 1407 $count = count($this->_markers); 1408 for ($i = 0; $i < $count; $i++) { 1409 if ($this->_markers[$i]['marker'] == 0xE0) { 1410 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4); 1411 if ($signature == 'JFIF') { 1412 $data =& $this->_markers[$i]['data']; 1413 break; 1414 } 1415 } 1416 } 1417 1418 if ($data == null) { 1419 $this->_info['jfif'] = false; 1420 return false; 1421 } 1422 1423 $this->_info['jfif'] = array(); 1424 1425 $vmaj = $this->_getByte($data, 5); 1426 $vmin = $this->_getByte($data, 6); 1427 1428 $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin); 1429 1430 $units = $this->_getByte($data, 7); 1431 switch ($units) { 1432 case 0: 1433 $this->_info['jfif']['Units'] = 'pixels'; 1434 break; 1435 case 1: 1436 $this->_info['jfif']['Units'] = 'dpi'; 1437 break; 1438 case 2: 1439 $this->_info['jfif']['Units'] = 'dpcm'; 1440 break; 1441 default: 1442 $this->_info['jfif']['Units'] = 'unknown'; 1443 break; 1444 } 1445 1446 $xdens = $this->_getShort($data, 8); 1447 $ydens = $this->_getShort($data, 10); 1448 1449 $this->_info['jfif']['XDensity'] = $xdens; 1450 $this->_info['jfif']['YDensity'] = $ydens; 1451 1452 $thumbx = $this->_getByte($data, 12); 1453 $thumby = $this->_getByte($data, 13); 1454 1455 $this->_info['jfif']['ThumbnailWidth'] = $thumbx; 1456 $this->_info['jfif']['ThumbnailHeight'] = $thumby; 1457 1458 return true; 1459 } 1460 1461 /*************************************************************/ 1462 function _parseMarkerSOF() { 1463 if (!isset($this->_markers)) { 1464 $this->_readJPEG(); 1465 } 1466 1467 if ($this->_markers == null) { 1468 return false; 1469 } 1470 1471 $data = null; 1472 $count = count($this->_markers); 1473 for ($i = 0; $i < $count; $i++) { 1474 switch ($this->_markers[$i]['marker']) { 1475 case 0xC0: // SOF0 1476 case 0xC1: // SOF1 1477 case 0xC2: // SOF2 1478 case 0xC9: // SOF9 1479 $data =& $this->_markers[$i]['data']; 1480 $marker = $this->_markers[$i]['marker']; 1481 break; 1482 } 1483 } 1484 1485 if ($data == null) { 1486 $this->_info['sof'] = false; 1487 return false; 1488 } 1489 1490 $pos = 0; 1491 $this->_info['sof'] = array(); 1492 1493 switch ($marker) { 1494 case 0xC0: // SOF0 1495 $format = 'Baseline'; 1496 break; 1497 case 0xC1: // SOF1 1498 $format = 'Progessive'; 1499 break; 1500 case 0xC2: // SOF2 1501 $format = 'Non-baseline'; 1502 break; 1503 case 0xC9: // SOF9 1504 $format = 'Arithmetic'; 1505 break; 1506 default: 1507 return false; 1508 } 1509 1510 $this->_info['sof']['Format'] = $format; 1511 $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0); 1512 $this->_info['sof']['ImageHeight'] = $this->_getShort($data, $pos + 1); 1513 $this->_info['sof']['ImageWidth'] = $this->_getShort($data, $pos + 3); 1514 $this->_info['sof']['ColorChannels'] = $this->_getByte($data, $pos + 5); 1515 1516 return true; 1517 } 1518 1519 /** 1520 * Parses the XMP data 1521 * 1522 * @author Hakan Sandell <hakan.sandell@mydata.se> 1523 */ 1524 function _parseMarkerXmp() { 1525 if (!isset($this->_markers)) { 1526 $this->_readJPEG(); 1527 } 1528 1529 if ($this->_markers == null) { 1530 return false; 1531 } 1532 1533 $data = null; 1534 $count = count($this->_markers); 1535 for ($i = 0; $i < $count; $i++) { 1536 if ($this->_markers[$i]['marker'] == 0xE1) { 1537 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 29); 1538 if ($signature == "http://ns.adobe.com/xap/1.0/\0") { 1539 $data = substr($this->_markers[$i]['data'], 29); 1540 break; 1541 } 1542 } 1543 } 1544 1545 if ($data == null) { 1546 $this->_info['xmp'] = false; 1547 return false; 1548 } 1549 1550 $parser = xml_parser_create(); 1551 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); 1552 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); 1553 $result = xml_parse_into_struct($parser, $data, $values, $tags); 1554 xml_parser_free($parser); 1555 1556 if ($result == 0) { 1557 $this->_info['xmp'] = false; 1558 return false; 1559 } 1560 1561 $this->_info['xmp'] = array(); 1562 $count = count($values); 1563 for ($i = 0; $i < $count; $i++) { 1564 if ($values[$i]['tag'] == 'rdf:Description' && $values[$i]['type'] == 'open') { 1565 1566 while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Description')) { 1567 $this->_parseXmpNode($values, $i, $this->_info['xmp'][$values[$i]['tag']], $count); 1568 } 1569 } 1570 } 1571 return true; 1572 } 1573 1574 /** 1575 * Parses XMP nodes by recursion 1576 * 1577 * @author Hakan Sandell <hakan.sandell@mydata.se> 1578 * @param integer $count 1579 */ 1580 function _parseXmpNode($values, &$i, &$meta, $count) { 1581 if ($values[$i]['type'] == 'close') return; 1582 1583 if ($values[$i]['type'] == 'complete') { 1584 // Simple Type property 1585 $meta = $values[$i]['value']; 1586 return; 1587 } 1588 1589 $i++; 1590 if ($i >= $count) return; 1591 1592 if ($values[$i]['tag'] == 'rdf:Bag' || $values[$i]['tag'] == 'rdf:Seq') { 1593 // Array property 1594 $meta = array(); 1595 while ($values[++$i]['tag'] == 'rdf:li') { 1596 $this->_parseXmpNode($values, $i, $meta[], $count); 1597 } 1598 $i++; // skip closing Bag/Seq tag 1599 1600 } elseif ($values[$i]['tag'] == 'rdf:Alt') { 1601 // Language Alternative property, only the first (default) value is used 1602 if ($values[$i]['type'] == 'open') { 1603 $i++; 1604 $this->_parseXmpNode($values, $i, $meta, $count); 1605 while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Alt')); 1606 $i++; // skip closing Alt tag 1607 } 1608 1609 } else { 1610 // Structure property 1611 $meta = array(); 1612 $startTag = $values[$i-1]['tag']; 1613 do { 1614 $this->_parseXmpNode($values, $i, $meta[$values[$i]['tag']], $count); 1615 } while ((++$i < $count) && ($values[$i]['tag'] != $startTag)); 1616 } 1617 } 1618 1619 /*************************************************************/ 1620 function _parseMarkerExif() { 1621 if (!isset($this->_markers)) { 1622 $this->_readJPEG(); 1623 } 1624 1625 if ($this->_markers == null) { 1626 return false; 1627 } 1628 1629 $data = null; 1630 $count = count($this->_markers); 1631 for ($i = 0; $i < $count; $i++) { 1632 if ($this->_markers[$i]['marker'] == 0xE1) { 1633 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); 1634 if ($signature == "Exif\0\0") { 1635 $data =& $this->_markers[$i]['data']; 1636 break; 1637 } 1638 } 1639 } 1640 1641 if ($data == null) { 1642 $this->_info['exif'] = false; 1643 return false; 1644 } 1645 $pos = 6; 1646 $this->_info['exif'] = array(); 1647 1648 // We don't increment $pos after this because Exif uses offsets relative to this point 1649 1650 $byteAlign = $this->_getShort($data, $pos + 0); 1651 1652 if ($byteAlign == 0x4949) { // "II" 1653 $isBigEndian = false; 1654 } elseif ($byteAlign == 0x4D4D) { // "MM" 1655 $isBigEndian = true; 1656 } else { 1657 return false; // Unexpected data 1658 } 1659 1660 $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian); 1661 if ($alignCheck != 0x002A) // That's the expected value 1662 return false; // Unexpected data 1663 1664 if ($isBigEndian) { 1665 $this->_info['exif']['ByteAlign'] = "Big Endian"; 1666 } else { 1667 $this->_info['exif']['ByteAlign'] = "Little Endian"; 1668 } 1669 1670 $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian); 1671 if ($offsetIFD0 < 8) 1672 return false; // Unexpected data 1673 1674 $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0'); 1675 if ($offsetIFD1 != 0) 1676 $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1'); 1677 1678 return true; 1679 } 1680 1681 /*************************************************************/ 1682 1683 /** 1684 * @param integer $base 1685 * @param boolean $isBigEndian 1686 * @param string $mode 1687 */ 1688 function _readIFD($data, $base, $offset, $isBigEndian, $mode) { 1689 $EXIFTags = $this->_exifTagNames($mode); 1690 1691 $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian); 1692 $offset += 2; 1693 1694 $exifTIFFOffset = 0; 1695 $exifTIFFLength = 0; 1696 $exifThumbnailOffset = 0; 1697 $exifThumbnailLength = 0; 1698 1699 for ($i = 0; $i < $numEntries; $i++) { 1700 $tag = $this->_getShort($data, $base + $offset, $isBigEndian); 1701 $offset += 2; 1702 $type = $this->_getShort($data, $base + $offset, $isBigEndian); 1703 $offset += 2; 1704 $count = $this->_getLong($data, $base + $offset, $isBigEndian); 1705 $offset += 4; 1706 1707 if (($type < 1) || ($type > 12)) 1708 return false; // Unexpected Type 1709 1710 $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 ); 1711 1712 $dataLength = $typeLengths[$type] * $count; 1713 if ($dataLength > 4) { 1714 $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian); 1715 $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength); 1716 } else { 1717 $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength); 1718 } 1719 $offset += 4; 1720 1721 switch ($type) { 1722 case 1: // UBYTE 1723 if ($count == 1) { 1724 $value = $this->_getByte($rawValue, 0); 1725 } else { 1726 $value = array(); 1727 for ($j = 0; $j < $count; $j++) 1728 $value[$j] = $this->_getByte($rawValue, $j); 1729 } 1730 break; 1731 case 2: // ASCII 1732 $value = $rawValue; 1733 break; 1734 case 3: // USHORT 1735 if ($count == 1) { 1736 $value = $this->_getShort($rawValue, 0, $isBigEndian); 1737 } else { 1738 $value = array(); 1739 for ($j = 0; $j < $count; $j++) 1740 $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); 1741 } 1742 break; 1743 case 4: // ULONG 1744 if ($count == 1) { 1745 $value = $this->_getLong($rawValue, 0, $isBigEndian); 1746 } else { 1747 $value = array(); 1748 for ($j = 0; $j < $count; $j++) 1749 $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); 1750 } 1751 break; 1752 case 5: // URATIONAL 1753 if ($count == 1) { 1754 $a = $this->_getLong($rawValue, 0, $isBigEndian); 1755 $b = $this->_getLong($rawValue, 4, $isBigEndian); 1756 $value = array(); 1757 $value['val'] = 0; 1758 $value['num'] = $a; 1759 $value['den'] = $b; 1760 if (($a != 0) && ($b != 0)) { 1761 $value['val'] = $a / $b; 1762 } 1763 } else { 1764 $value = array(); 1765 for ($j = 0; $j < $count; $j++) { 1766 $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); 1767 $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); 1768 $value = array(); 1769 $value[$j]['val'] = 0; 1770 $value[$j]['num'] = $a; 1771 $value[$j]['den'] = $b; 1772 if (($a != 0) && ($b != 0)) 1773 $value[$j]['val'] = $a / $b; 1774 } 1775 } 1776 break; 1777 case 6: // SBYTE 1778 if ($count == 1) { 1779 $value = $this->_getByte($rawValue, 0); 1780 } else { 1781 $value = array(); 1782 for ($j = 0; $j < $count; $j++) 1783 $value[$j] = $this->_getByte($rawValue, $j); 1784 } 1785 break; 1786 case 7: // UNDEFINED 1787 $value = $rawValue; 1788 break; 1789 case 8: // SSHORT 1790 if ($count == 1) { 1791 $value = $this->_getShort($rawValue, 0, $isBigEndian); 1792 } else { 1793 $value = array(); 1794 for ($j = 0; $j < $count; $j++) 1795 $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); 1796 } 1797 break; 1798 case 9: // SLONG 1799 if ($count == 1) { 1800 $value = $this->_getLong($rawValue, 0, $isBigEndian); 1801 } else { 1802 $value = array(); 1803 for ($j = 0; $j < $count; $j++) 1804 $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); 1805 } 1806 break; 1807 case 10: // SRATIONAL 1808 if ($count == 1) { 1809 $a = $this->_getLong($rawValue, 0, $isBigEndian); 1810 $b = $this->_getLong($rawValue, 4, $isBigEndian); 1811 $value = array(); 1812 $value['val'] = 0; 1813 $value['num'] = $a; 1814 $value['den'] = $b; 1815 if (($a != 0) && ($b != 0)) 1816 $value['val'] = $a / $b; 1817 } else { 1818 $value = array(); 1819 for ($j = 0; $j < $count; $j++) { 1820 $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); 1821 $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); 1822 $value = array(); 1823 $value[$j]['val'] = 0; 1824 $value[$j]['num'] = $a; 1825 $value[$j]['den'] = $b; 1826 if (($a != 0) && ($b != 0)) 1827 $value[$j]['val'] = $a / $b; 1828 } 1829 } 1830 break; 1831 case 11: // FLOAT 1832 $value = $rawValue; 1833 break; 1834 1835 case 12: // DFLOAT 1836 $value = $rawValue; 1837 break; 1838 default: 1839 return false; // Unexpected Type 1840 } 1841 1842 $tagName = ''; 1843 if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset 1844 $this->_readIFD($data, $base, $value, $isBigEndian, 'exif'); 1845 } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset 1846 $this->_readIFD($data, $base, $value, $isBigEndian, 'gps'); 1847 } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets 1848 $exifTIFFOffset = $value; 1849 } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts 1850 $exifTIFFLength = $value; 1851 } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset 1852 $exifThumbnailOffset = $value; 1853 } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength 1854 $exifThumbnailLength = $value; 1855 } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset 1856 $this->_readIFD($data, $base, $value, $isBigEndian, 'interop'); 1857 } 1858 // elseif (($mode == 'exif') && ($tag == 0x927C)) { // MakerNote 1859 // } 1860 else { 1861 if (isset($EXIFTags[$tag])) { 1862 $tagName = $EXIFTags[$tag]; 1863 if (isset($this->_info['exif'][$tagName])) { 1864 if (!is_array($this->_info['exif'][$tagName])) { 1865 $aux = array(); 1866 $aux[0] = $this->_info['exif'][$tagName]; 1867 $this->_info['exif'][$tagName] = $aux; 1868 } 1869 1870 $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value; 1871 } else { 1872 $this->_info['exif'][$tagName] = $value; 1873 } 1874 } 1875 /* 1876 else { 1877 echo sprintf("<h1>Unknown tag %02x (t: %d l: %d) %s in %s</h1>", $tag, $type, $count, $mode, $this->_fileName); 1878 // Unknown Tags will be ignored!!! 1879 // That's because the tag might be a pointer (like the Exif tag) 1880 // and saving it without saving the data it points to might 1881 // create an invalid file. 1882 } 1883 */ 1884 } 1885 } 1886 1887 if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) { 1888 $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength); 1889 } 1890 1891 if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) { 1892 $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength); 1893 } 1894 1895 $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian); 1896 return $nextOffset; 1897 } 1898 1899 /*************************************************************/ 1900 function & _createMarkerExif() { 1901 $data = null; 1902 $count = count($this->_markers); 1903 for ($i = 0; $i < $count; $i++) { 1904 if ($this->_markers[$i]['marker'] == 0xE1) { 1905 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); 1906 if ($signature == "Exif\0\0") { 1907 $data =& $this->_markers[$i]['data']; 1908 break; 1909 } 1910 } 1911 } 1912 1913 if (!isset($this->_info['exif'])) { 1914 return false; 1915 } 1916 1917 $data = "Exif\0\0"; 1918 $pos = 6; 1919 $offsetBase = 6; 1920 1921 if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) { 1922 $isBigEndian = true; 1923 $aux = "MM"; 1924 $pos = $this->_putString($data, $pos, $aux); 1925 } else { 1926 $isBigEndian = false; 1927 $aux = "II"; 1928 $pos = $this->_putString($data, $pos, $aux); 1929 } 1930 $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian); 1931 $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8 1932 1933 $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0'); 1934 $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1'); 1935 1936 $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true); 1937 $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false); 1938 1939 return $data; 1940 } 1941 1942 /*************************************************************/ 1943 1944 /** 1945 * @param integer $offsetBase 1946 * @param boolean $isBigEndian 1947 * @param boolean $hasNext 1948 */ 1949 function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext) { 1950 $tiffData = null; 1951 $tiffDataOffsetPos = -1; 1952 1953 $entryCount = count($entries); 1954 1955 $dataPos = $pos + 2 + ($entryCount * 12) + 4; 1956 $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian); 1957 1958 for ($i = 0; $i < $entryCount; $i++) { 1959 $tag = $entries[$i]['tag']; 1960 $type = $entries[$i]['type']; 1961 1962 if ($type == -99) { // SubIFD 1963 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 1964 $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG 1965 $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 1966 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 1967 1968 $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false); 1969 } elseif ($type == -98) { // TIFF Data 1970 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 1971 $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG 1972 $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 1973 $tiffDataOffsetPos = $pos; 1974 $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now 1975 $tiffData =& $entries[$i]['value'] ; 1976 } else { // Regular Entry 1977 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 1978 $pos = $this->_putShort($data, $pos, $type, $isBigEndian); 1979 $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian); 1980 if (strlen($entries[$i]['value']) > 4) { 1981 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 1982 $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']); 1983 } else { 1984 $val = str_pad($entries[$i]['value'], 4, "\0"); 1985 $pos = $this->_putString($data, $pos, $val); 1986 } 1987 } 1988 } 1989 1990 if ($tiffData != null) { 1991 $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian); 1992 $dataPos = $this->_putString($data, $dataPos, $tiffData); 1993 } 1994 1995 if ($hasNext) { 1996 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 1997 } else { 1998 $pos = $this->_putLong($data, $pos, 0, $isBigEndian); 1999 } 2000 2001 return $dataPos; 2002 } 2003 2004 /*************************************************************/ 2005 2006 /** 2007 * @param boolean $isBigEndian 2008 * @param string $mode 2009 */ 2010 function & _getIFDEntries($isBigEndian, $mode) { 2011 $EXIFNames = $this->_exifTagNames($mode); 2012 $EXIFTags = $this->_exifNameTags($mode); 2013 $EXIFTypeInfo = $this->_exifTagTypes($mode); 2014 2015 $ifdEntries = array(); 2016 $entryCount = 0; 2017 2018 reset($EXIFNames); 2019 while (list($tag, $name) = each($EXIFNames)) { 2020 $type = $EXIFTypeInfo[$tag][0]; 2021 $count = $EXIFTypeInfo[$tag][1]; 2022 $value = null; 2023 2024 if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset 2025 if (isset($this->_info['exif']['EXIFVersion'])) { 2026 $value =& $this->_getIFDEntries($isBigEndian, "exif"); 2027 $type = -99; 2028 } 2029 else { 2030 $value = null; 2031 } 2032 } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset 2033 if (isset($this->_info['exif']['GPSVersionID'])) { 2034 $value =& $this->_getIFDEntries($isBigEndian, "gps"); 2035 $type = -99; 2036 } else { 2037 $value = null; 2038 } 2039 } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets 2040 if (isset($this->_info['exif']['TIFFStrips'])) { 2041 $value =& $this->_info['exif']['TIFFStrips']; 2042 $type = -98; 2043 } else { 2044 $value = null; 2045 } 2046 } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts 2047 if (isset($this->_info['exif']['TIFFStrips'])) { 2048 $value = strlen($this->_info['exif']['TIFFStrips']); 2049 } else { 2050 $value = null; 2051 } 2052 } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset 2053 if (isset($this->_info['exif']['JFIFThumbnail'])) { 2054 $value =& $this->_info['exif']['JFIFThumbnail']; 2055 $type = -98; 2056 } else { 2057 $value = null; 2058 } 2059 } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength 2060 if (isset($this->_info['exif']['JFIFThumbnail'])) { 2061 $value = strlen($this->_info['exif']['JFIFThumbnail']); 2062 } else { 2063 $value = null; 2064 } 2065 } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset 2066 if (isset($this->_info['exif']['InteroperabilityIndex'])) { 2067 $value =& $this->_getIFDEntries($isBigEndian, "interop"); 2068 $type = -99; 2069 } else { 2070 $value = null; 2071 } 2072 } elseif (isset($this->_info['exif'][$name])) { 2073 $origValue =& $this->_info['exif'][$name]; 2074 2075 // This makes it easier to process variable size elements 2076 if (!is_array($origValue) || isset($origValue['val'])) { 2077 unset($origValue); // Break the reference 2078 $origValue = array($this->_info['exif'][$name]); 2079 } 2080 $origCount = count($origValue); 2081 2082 if ($origCount == 0 ) { 2083 $type = -1; // To ignore this field 2084 } 2085 2086 $value = " "; 2087 2088 switch ($type) { 2089 case 1: // UBYTE 2090 if ($count == 0) { 2091 $count = $origCount; 2092 } 2093 2094 $j = 0; 2095 while (($j < $count) && ($j < $origCount)) { 2096 2097 $this->_putByte($value, $j, $origValue[$j]); 2098 $j++; 2099 } 2100 2101 while ($j < $count) { 2102 $this->_putByte($value, $j, 0); 2103 $j++; 2104 } 2105 break; 2106 case 2: // ASCII 2107 $v = strval($origValue[0]); 2108 if (($count != 0) && (strlen($v) > $count)) { 2109 $v = substr($v, 0, $count); 2110 } 2111 elseif (($count > 0) && (strlen($v) < $count)) { 2112 $v = str_pad($v, $count, "\0"); 2113 } 2114 2115 $count = strlen($v); 2116 2117 $this->_putString($value, 0, $v); 2118 break; 2119 case 3: // USHORT 2120 if ($count == 0) { 2121 $count = $origCount; 2122 } 2123 2124 $j = 0; 2125 while (($j < $count) && ($j < $origCount)) { 2126 $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); 2127 $j++; 2128 } 2129 2130 while ($j < $count) { 2131 $this->_putShort($value, $j * 2, 0, $isBigEndian); 2132 $j++; 2133 } 2134 break; 2135 case 4: // ULONG 2136 if ($count == 0) { 2137 $count = $origCount; 2138 } 2139 2140 $j = 0; 2141 while (($j < $count) && ($j < $origCount)) { 2142 $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); 2143 $j++; 2144 } 2145 2146 while ($j < $count) { 2147 $this->_putLong($value, $j * 4, 0, $isBigEndian); 2148 $j++; 2149 } 2150 break; 2151 case 5: // URATIONAL 2152 if ($count == 0) { 2153 $count = $origCount; 2154 } 2155 2156 $j = 0; 2157 while (($j < $count) && ($j < $origCount)) { 2158 $v = $origValue[$j]; 2159 if (is_array($v)) { 2160 $a = $v['num']; 2161 $b = $v['den']; 2162 } 2163 else { 2164 $a = 0; 2165 $b = 0; 2166 // TODO: Allow other types and convert them 2167 } 2168 $this->_putLong($value, $j * 8, $a, $isBigEndian); 2169 $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); 2170 $j++; 2171 } 2172 2173 while ($j < $count) { 2174 $this->_putLong($value, $j * 8, 0, $isBigEndian); 2175 $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); 2176 $j++; 2177 } 2178 break; 2179 case 6: // SBYTE 2180 if ($count == 0) { 2181 $count = $origCount; 2182 } 2183 2184 $j = 0; 2185 while (($j < $count) && ($j < $origCount)) { 2186 $this->_putByte($value, $j, $origValue[$j]); 2187 $j++; 2188 } 2189 2190 while ($j < $count) { 2191 $this->_putByte($value, $j, 0); 2192 $j++; 2193 } 2194 break; 2195 case 7: // UNDEFINED 2196 $v = strval($origValue[0]); 2197 if (($count != 0) && (strlen($v) > $count)) { 2198 $v = substr($v, 0, $count); 2199 } 2200 elseif (($count > 0) && (strlen($v) < $count)) { 2201 $v = str_pad($v, $count, "\0"); 2202 } 2203 2204 $count = strlen($v); 2205 2206 $this->_putString($value, 0, $v); 2207 break; 2208 case 8: // SSHORT 2209 if ($count == 0) { 2210 $count = $origCount; 2211 } 2212 2213 $j = 0; 2214 while (($j < $count) && ($j < $origCount)) { 2215 $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); 2216 $j++; 2217 } 2218 2219 while ($j < $count) { 2220 $this->_putShort($value, $j * 2, 0, $isBigEndian); 2221 $j++; 2222 } 2223 break; 2224 case 9: // SLONG 2225 if ($count == 0) { 2226 $count = $origCount; 2227 } 2228 2229 $j = 0; 2230 while (($j < $count) && ($j < $origCount)) { 2231 $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); 2232 $j++; 2233 } 2234 2235 while ($j < $count) { 2236 $this->_putLong($value, $j * 4, 0, $isBigEndian); 2237 $j++; 2238 } 2239 break; 2240 case 10: // SRATIONAL 2241 if ($count == 0) { 2242 $count = $origCount; 2243 } 2244 2245 $j = 0; 2246 while (($j < $count) && ($j < $origCount)) { 2247 $v = $origValue[$j]; 2248 if (is_array($v)) { 2249 $a = $v['num']; 2250 $b = $v['den']; 2251 } 2252 else { 2253 $a = 0; 2254 $b = 0; 2255 // TODO: Allow other types and convert them 2256 } 2257 2258 $this->_putLong($value, $j * 8, $a, $isBigEndian); 2259 $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); 2260 $j++; 2261 } 2262 2263 while ($j < $count) { 2264 $this->_putLong($value, $j * 8, 0, $isBigEndian); 2265 $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); 2266 $j++; 2267 } 2268 break; 2269 case 11: // FLOAT 2270 if ($count == 0) { 2271 $count = $origCount; 2272 } 2273 2274 $j = 0; 2275 while (($j < $count) && ($j < $origCount)) { 2276 $v = strval($origValue[$j]); 2277 if (strlen($v) > 4) { 2278 $v = substr($v, 0, 4); 2279 } 2280 elseif (strlen($v) < 4) { 2281 $v = str_pad($v, 4, "\0"); 2282 } 2283 $this->_putString($value, $j * 4, $v); 2284 $j++; 2285 } 2286 2287 while ($j < $count) { 2288 $v = "\0\0\0\0"; 2289 $this->_putString($value, $j * 4, $v); 2290 $j++; 2291 } 2292 break; 2293 case 12: // DFLOAT 2294 if ($count == 0) { 2295 $count = $origCount; 2296 } 2297 2298 $j = 0; 2299 while (($j < $count) && ($j < $origCount)) { 2300 $v = strval($origValue[$j]); 2301 if (strlen($v) > 8) { 2302 $v = substr($v, 0, 8); 2303 } 2304 elseif (strlen($v) < 8) { 2305 $v = str_pad($v, 8, "\0"); 2306 } 2307 $this->_putString($value, $j * 8, $v); 2308 $j++; 2309 } 2310 2311 while ($j < $count) { 2312 $v = "\0\0\0\0\0\0\0\0"; 2313 $this->_putString($value, $j * 8, $v); 2314 $j++; 2315 } 2316 break; 2317 default: 2318 $value = null; 2319 break; 2320 } 2321 } 2322 2323 if ($value != null) { 2324 $ifdEntries[$entryCount] = array(); 2325 $ifdEntries[$entryCount]['tag'] = $tag; 2326 $ifdEntries[$entryCount]['type'] = $type; 2327 $ifdEntries[$entryCount]['count'] = $count; 2328 $ifdEntries[$entryCount]['value'] = $value; 2329 2330 $entryCount++; 2331 } 2332 } 2333 2334 return $ifdEntries; 2335 } 2336 2337 /*************************************************************/ 2338 function _parseMarkerAdobe() { 2339 if (!isset($this->_markers)) { 2340 $this->_readJPEG(); 2341 } 2342 2343 if ($this->_markers == null) { 2344 return false; 2345 } 2346 2347 $data = null; 2348 $count = count($this->_markers); 2349 for ($i = 0; $i < $count; $i++) { 2350 if ($this->_markers[$i]['marker'] == 0xED) { 2351 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14); 2352 if ($signature == "Photoshop 3.0\0") { 2353 $data =& $this->_markers[$i]['data']; 2354 break; 2355 } 2356 } 2357 } 2358 2359 if ($data == null) { 2360 $this->_info['adobe'] = false; 2361 $this->_info['iptc'] = false; 2362 return false; 2363 } 2364 $pos = 14; 2365 $this->_info['adobe'] = array(); 2366 $this->_info['adobe']['raw'] = array(); 2367 $this->_info['iptc'] = array(); 2368 2369 $datasize = strlen($data); 2370 2371 while ($pos < $datasize) { 2372 $signature = $this->_getFixedString($data, $pos, 4); 2373 if ($signature != '8BIM') 2374 return false; 2375 $pos += 4; 2376 2377 $type = $this->_getShort($data, $pos); 2378 $pos += 2; 2379 2380 $strlen = $this->_getByte($data, $pos); 2381 $pos += 1; 2382 $header = ''; 2383 for ($i = 0; $i < $strlen; $i++) { 2384 $header .= $data{$pos + $i}; 2385 } 2386 $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself 2387 2388 $length = $this->_getLong($data, $pos); 2389 $pos += 4; 2390 2391 $basePos = $pos; 2392 2393 switch ($type) { 2394 case 0x0404: // Caption (IPTC Data) 2395 $pos = $this->_readIPTC($data, $pos); 2396 if ($pos == false) 2397 return false; 2398 break; 2399 case 0x040A: // CopyrightFlag 2400 $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos); 2401 $pos += $length; 2402 break; 2403 case 0x040B: // ImageURL 2404 $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length); 2405 $pos += $length; 2406 break; 2407 case 0x040C: // Thumbnail 2408 $aux = $this->_getLong($data, $pos); 2409 $pos += 4; 2410 if ($aux == 1) { 2411 $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos); 2412 $pos += 4; 2413 $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos); 2414 $pos += 4; 2415 2416 $pos += 16; // Skip some data 2417 2418 $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28); 2419 $pos += $length - 28; 2420 } 2421 break; 2422 default: 2423 break; 2424 } 2425 2426 // We save all blocks, even those we recognized 2427 $label = sprintf('8BIM_0x%04x', $type); 2428 $this->_info['adobe']['raw'][$label] = array(); 2429 $this->_info['adobe']['raw'][$label]['type'] = $type; 2430 $this->_info['adobe']['raw'][$label]['header'] = $header; 2431 $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length); 2432 2433 $pos = $basePos + $length + ($length % 2); // Even padding 2434 } 2435 2436 } 2437 2438 /*************************************************************/ 2439 function _readIPTC(&$data, $pos = 0) { 2440 $totalLength = strlen($data); 2441 2442 $IPTCTags = $this->_iptcTagNames(); 2443 2444 while ($pos < ($totalLength - 5)) { 2445 $signature = $this->_getShort($data, $pos); 2446 if ($signature != 0x1C02) 2447 return $pos; 2448 $pos += 2; 2449 2450 $type = $this->_getByte($data, $pos); 2451 $pos += 1; 2452 $length = $this->_getShort($data, $pos); 2453 $pos += 2; 2454 2455 $basePos = $pos; 2456 $label = ''; 2457 2458 if (isset($IPTCTags[$type])) { 2459 $label = $IPTCTags[$type]; 2460 } else { 2461 $label = sprintf('IPTC_0x%02x', $type); 2462 } 2463 2464 if ($label != '') { 2465 if (isset($this->_info['iptc'][$label])) { 2466 if (!is_array($this->_info['iptc'][$label])) { 2467 $aux = array(); 2468 $aux[0] = $this->_info['iptc'][$label]; 2469 $this->_info['iptc'][$label] = $aux; 2470 } 2471 $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length); 2472 } else { 2473 $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length); 2474 } 2475 } 2476 2477 $pos = $basePos + $length; // No padding 2478 } 2479 return $pos; 2480 } 2481 2482 /*************************************************************/ 2483 function & _createMarkerAdobe() { 2484 if (isset($this->_info['iptc'])) { 2485 if (!isset($this->_info['adobe'])) { 2486 $this->_info['adobe'] = array(); 2487 } 2488 if (!isset($this->_info['adobe']['raw'])) { 2489 $this->_info['adobe']['raw'] = array(); 2490 } 2491 if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) { 2492 $this->_info['adobe']['raw']['8BIM_0x0404'] = array(); 2493 } 2494 $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404; 2495 $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption"; 2496 $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC(); 2497 } 2498 2499 if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) { 2500 $data = "Photoshop 3.0\0"; 2501 $pos = 14; 2502 2503 reset($this->_info['adobe']['raw']); 2504 while (list($key) = each($this->_info['adobe']['raw'])) { 2505 $pos = $this->_write8BIM( 2506 $data, 2507 $pos, 2508 $this->_info['adobe']['raw'][$key]['type'], 2509 $this->_info['adobe']['raw'][$key]['header'], 2510 $this->_info['adobe']['raw'][$key]['data'] ); 2511 } 2512 } 2513 2514 return $data; 2515 } 2516 2517 /*************************************************************/ 2518 2519 /** 2520 * @param integer $pos 2521 */ 2522 function _write8BIM(&$data, $pos, $type, $header, &$value) { 2523 $signature = "8BIM"; 2524 2525 $pos = $this->_putString($data, $pos, $signature); 2526 $pos = $this->_putShort($data, $pos, $type); 2527 2528 $len = strlen($header); 2529 2530 $pos = $this->_putByte($data, $pos, $len); 2531 $pos = $this->_putString($data, $pos, $header); 2532 if (($len % 2) == 0) { // Even padding, including the length byte 2533 $pos = $this->_putByte($data, $pos, 0); 2534 } 2535 2536 $len = strlen($value); 2537 $pos = $this->_putLong($data, $pos, $len); 2538 $pos = $this->_putString($data, $pos, $value); 2539 if (($len % 2) != 0) { // Even padding 2540 $pos = $this->_putByte($data, $pos, 0); 2541 } 2542 return $pos; 2543 } 2544 2545 /*************************************************************/ 2546 function & _writeIPTC() { 2547 $data = " "; 2548 $pos = 0; 2549 2550 $IPTCNames =& $this->_iptcNameTags(); 2551 2552 reset($this->_info['iptc']); 2553 2554 while (list($label) = each($this->_info['iptc'])) { 2555 $value =& $this->_info['iptc'][$label]; 2556 $type = -1; 2557 2558 if (isset($IPTCNames[$label])) { 2559 $type = $IPTCNames[$label]; 2560 } 2561 elseif (substr($label, 0, 7) == "IPTC_0x") { 2562 $type = hexdec(substr($label, 7, 2)); 2563 } 2564 2565 if ($type != -1) { 2566 if (is_array($value)) { 2567 $vcnt = count($value); 2568 for ($i = 0; $i < $vcnt; $i++) { 2569 $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]); 2570 } 2571 } 2572 else { 2573 $pos = $this->_writeIPTCEntry($data, $pos, $type, $value); 2574 } 2575 } 2576 } 2577 2578 return $data; 2579 } 2580 2581 /*************************************************************/ 2582 2583 /** 2584 * @param integer $pos 2585 */ 2586 function _writeIPTCEntry(&$data, $pos, $type, &$value) { 2587 $pos = $this->_putShort($data, $pos, 0x1C02); 2588 $pos = $this->_putByte($data, $pos, $type); 2589 $pos = $this->_putShort($data, $pos, strlen($value)); 2590 $pos = $this->_putString($data, $pos, $value); 2591 2592 return $pos; 2593 } 2594 2595 /*************************************************************/ 2596 function _exifTagNames($mode) { 2597 $tags = array(); 2598 2599 if ($mode == 'ifd0') { 2600 $tags[0x010E] = 'ImageDescription'; 2601 $tags[0x010F] = 'Make'; 2602 $tags[0x0110] = 'Model'; 2603 $tags[0x0112] = 'Orientation'; 2604 $tags[0x011A] = 'XResolution'; 2605 $tags[0x011B] = 'YResolution'; 2606 $tags[0x0128] = 'ResolutionUnit'; 2607 $tags[0x0131] = 'Software'; 2608 $tags[0x0132] = 'DateTime'; 2609 $tags[0x013B] = 'Artist'; 2610 $tags[0x013E] = 'WhitePoint'; 2611 $tags[0x013F] = 'PrimaryChromaticities'; 2612 $tags[0x0211] = 'YCbCrCoefficients'; 2613 $tags[0x0212] = 'YCbCrSubSampling'; 2614 $tags[0x0213] = 'YCbCrPositioning'; 2615 $tags[0x0214] = 'ReferenceBlackWhite'; 2616 $tags[0x8298] = 'Copyright'; 2617 $tags[0x8769] = 'ExifIFDOffset'; 2618 $tags[0x8825] = 'GPSIFDOffset'; 2619 } 2620 if ($mode == 'ifd1') { 2621 $tags[0x00FE] = 'TIFFNewSubfileType'; 2622 $tags[0x00FF] = 'TIFFSubfileType'; 2623 $tags[0x0100] = 'TIFFImageWidth'; 2624 $tags[0x0101] = 'TIFFImageHeight'; 2625 $tags[0x0102] = 'TIFFBitsPerSample'; 2626 $tags[0x0103] = 'TIFFCompression'; 2627 $tags[0x0106] = 'TIFFPhotometricInterpretation'; 2628 $tags[0x0107] = 'TIFFThreshholding'; 2629 $tags[0x0108] = 'TIFFCellWidth'; 2630 $tags[0x0109] = 'TIFFCellLength'; 2631 $tags[0x010A] = 'TIFFFillOrder'; 2632 $tags[0x010E] = 'TIFFImageDescription'; 2633 $tags[0x010F] = 'TIFFMake'; 2634 $tags[0x0110] = 'TIFFModel'; 2635 $tags[0x0111] = 'TIFFStripOffsets'; 2636 $tags[0x0112] = 'TIFFOrientation'; 2637 $tags[0x0115] = 'TIFFSamplesPerPixel'; 2638 $tags[0x0116] = 'TIFFRowsPerStrip'; 2639 $tags[0x0117] = 'TIFFStripByteCounts'; 2640 $tags[0x0118] = 'TIFFMinSampleValue'; 2641 $tags[0x0119] = 'TIFFMaxSampleValue'; 2642 $tags[0x011A] = 'TIFFXResolution'; 2643 $tags[0x011B] = 'TIFFYResolution'; 2644 $tags[0x011C] = 'TIFFPlanarConfiguration'; 2645 $tags[0x0122] = 'TIFFGrayResponseUnit'; 2646 $tags[0x0123] = 'TIFFGrayResponseCurve'; 2647 $tags[0x0128] = 'TIFFResolutionUnit'; 2648 $tags[0x0131] = 'TIFFSoftware'; 2649 $tags[0x0132] = 'TIFFDateTime'; 2650 $tags[0x013B] = 'TIFFArtist'; 2651 $tags[0x013C] = 'TIFFHostComputer'; 2652 $tags[0x0140] = 'TIFFColorMap'; 2653 $tags[0x0152] = 'TIFFExtraSamples'; 2654 $tags[0x0201] = 'TIFFJFIFOffset'; 2655 $tags[0x0202] = 'TIFFJFIFLength'; 2656 $tags[0x0211] = 'TIFFYCbCrCoefficients'; 2657 $tags[0x0212] = 'TIFFYCbCrSubSampling'; 2658 $tags[0x0213] = 'TIFFYCbCrPositioning'; 2659 $tags[0x0214] = 'TIFFReferenceBlackWhite'; 2660 $tags[0x8298] = 'TIFFCopyright'; 2661 $tags[0x9286] = 'TIFFUserComment'; 2662 } elseif ($mode == 'exif') { 2663 $tags[0x829A] = 'ExposureTime'; 2664 $tags[0x829D] = 'FNumber'; 2665 $tags[0x8822] = 'ExposureProgram'; 2666 $tags[0x8824] = 'SpectralSensitivity'; 2667 $tags[0x8827] = 'ISOSpeedRatings'; 2668 $tags[0x8828] = 'OECF'; 2669 $tags[0x9000] = 'EXIFVersion'; 2670 $tags[0x9003] = 'DatetimeOriginal'; 2671 $tags[0x9004] = 'DatetimeDigitized'; 2672 $tags[0x9101] = 'ComponentsConfiguration'; 2673 $tags[0x9102] = 'CompressedBitsPerPixel'; 2674 $tags[0x9201] = 'ShutterSpeedValue'; 2675 $tags[0x9202] = 'ApertureValue'; 2676 $tags[0x9203] = 'BrightnessValue'; 2677 $tags[0x9204] = 'ExposureBiasValue'; 2678 $tags[0x9205] = 'MaxApertureValue'; 2679 $tags[0x9206] = 'SubjectDistance'; 2680 $tags[0x9207] = 'MeteringMode'; 2681 $tags[0x9208] = 'LightSource'; 2682 $tags[0x9209] = 'Flash'; 2683 $tags[0x920A] = 'FocalLength'; 2684 $tags[0x927C] = 'MakerNote'; 2685 $tags[0x9286] = 'UserComment'; 2686 $tags[0x9290] = 'SubSecTime'; 2687 $tags[0x9291] = 'SubSecTimeOriginal'; 2688 $tags[0x9292] = 'SubSecTimeDigitized'; 2689 $tags[0xA000] = 'FlashPixVersion'; 2690 $tags[0xA001] = 'ColorSpace'; 2691 $tags[0xA002] = 'PixelXDimension'; 2692 $tags[0xA003] = 'PixelYDimension'; 2693 $tags[0xA004] = 'RelatedSoundFile'; 2694 $tags[0xA005] = 'InteropIFDOffset'; 2695 $tags[0xA20B] = 'FlashEnergy'; 2696 $tags[0xA20C] = 'SpatialFrequencyResponse'; 2697 $tags[0xA20E] = 'FocalPlaneXResolution'; 2698 $tags[0xA20F] = 'FocalPlaneYResolution'; 2699 $tags[0xA210] = 'FocalPlaneResolutionUnit'; 2700 $tags[0xA214] = 'SubjectLocation'; 2701 $tags[0xA215] = 'ExposureIndex'; 2702 $tags[0xA217] = 'SensingMethod'; 2703 $tags[0xA300] = 'FileSource'; 2704 $tags[0xA301] = 'SceneType'; 2705 $tags[0xA302] = 'CFAPattern'; 2706 } elseif ($mode == 'interop') { 2707 $tags[0x0001] = 'InteroperabilityIndex'; 2708 $tags[0x0002] = 'InteroperabilityVersion'; 2709 $tags[0x1000] = 'RelatedImageFileFormat'; 2710 $tags[0x1001] = 'RelatedImageWidth'; 2711 $tags[0x1002] = 'RelatedImageLength'; 2712 } elseif ($mode == 'gps') { 2713 $tags[0x0000] = 'GPSVersionID'; 2714 $tags[0x0001] = 'GPSLatitudeRef'; 2715 $tags[0x0002] = 'GPSLatitude'; 2716 $tags[0x0003] = 'GPSLongitudeRef'; 2717 $tags[0x0004] = 'GPSLongitude'; 2718 $tags[0x0005] = 'GPSAltitudeRef'; 2719 $tags[0x0006] = 'GPSAltitude'; 2720 $tags[0x0007] = 'GPSTimeStamp'; 2721 $tags[0x0008] = 'GPSSatellites'; 2722 $tags[0x0009] = 'GPSStatus'; 2723 $tags[0x000A] = 'GPSMeasureMode'; 2724 $tags[0x000B] = 'GPSDOP'; 2725 $tags[0x000C] = 'GPSSpeedRef'; 2726 $tags[0x000D] = 'GPSSpeed'; 2727 $tags[0x000E] = 'GPSTrackRef'; 2728 $tags[0x000F] = 'GPSTrack'; 2729 $tags[0x0010] = 'GPSImgDirectionRef'; 2730 $tags[0x0011] = 'GPSImgDirection'; 2731 $tags[0x0012] = 'GPSMapDatum'; 2732 $tags[0x0013] = 'GPSDestLatitudeRef'; 2733 $tags[0x0014] = 'GPSDestLatitude'; 2734 $tags[0x0015] = 'GPSDestLongitudeRef'; 2735 $tags[0x0016] = 'GPSDestLongitude'; 2736 $tags[0x0017] = 'GPSDestBearingRef'; 2737 $tags[0x0018] = 'GPSDestBearing'; 2738 $tags[0x0019] = 'GPSDestDistanceRef'; 2739 $tags[0x001A] = 'GPSDestDistance'; 2740 } 2741 2742 return $tags; 2743 } 2744 2745 /*************************************************************/ 2746 function _exifTagTypes($mode) { 2747 $tags = array(); 2748 2749 if ($mode == 'ifd0') { 2750 $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any 2751 $tags[0x010F] = array(2, 0); // Make -> ASCII, Any 2752 $tags[0x0110] = array(2, 0); // Model -> ASCII, Any 2753 $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1 2754 $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1 2755 $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1 2756 $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT 2757 $tags[0x0131] = array(2, 0); // Software -> ASCII, Any 2758 $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20 2759 $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any 2760 $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2 2761 $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6 2762 $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3 2763 $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2 2764 $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1 2765 $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6 2766 $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any 2767 $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1 2768 $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1 2769 } 2770 if ($mode == 'ifd1') { 2771 $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1 2772 $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1 2773 $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1 2774 $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1 2775 $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3 2776 $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1 2777 $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1 2778 $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1 2779 $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1 2780 $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1 2781 $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1 2782 $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any 2783 $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any 2784 $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any 2785 $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip) 2786 $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1 2787 $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1 2788 $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1 2789 $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip) 2790 $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel) 2791 $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel) 2792 $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1 2793 $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1 2794 $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1 2795 $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1 2796 $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample) 2797 $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1 2798 $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any 2799 $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20 2800 $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any 2801 $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any 2802 $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample) 2803 $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3) 2804 $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1 2805 $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1 2806 $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3 2807 $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2 2808 $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1 2809 $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6 2810 $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any 2811 $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any 2812 } elseif ($mode == 'exif') { 2813 $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1 2814 $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1 2815 $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1 2816 $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any 2817 $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any 2818 $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any 2819 $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4 2820 $tags[0x9003] = array(2, 20); // DatetimeOriginal -> ASCII, 20 2821 $tags[0x9004] = array(2, 20); // DatetimeDigitized -> ASCII, 20 2822 $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4 2823 $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1 2824 $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1 2825 $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1 2826 $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1 2827 $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1 2828 $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1 2829 $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1 2830 $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1 2831 $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1 2832 $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1 2833 $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1 2834 $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any 2835 $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any 2836 $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any 2837 $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any 2838 $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any 2839 $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4 2840 $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1 2841 $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1 2842 $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1 2843 $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13 2844 $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1 2845 $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1 2846 $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any 2847 $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1 2848 $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1 2849 $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1 2850 $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2 2851 $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1 2852 $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1 2853 $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1 2854 $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1 2855 $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any 2856 } elseif ($mode == 'interop') { 2857 $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any 2858 $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4 2859 $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any 2860 $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1 2861 $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1 2862 } elseif ($mode == 'gps') { 2863 $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4 2864 $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2 2865 $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3 2866 $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2 2867 $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3 2868 $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2 2869 $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1 2870 $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3 2871 $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any 2872 $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2 2873 $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2 2874 $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1 2875 $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2 2876 $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1 2877 $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2 2878 $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1 2879 $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2 2880 $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1 2881 $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any 2882 $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2 2883 $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3 2884 $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2 2885 $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3 2886 $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2 2887 $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1 2888 $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2 2889 $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1 2890 } 2891 2892 return $tags; 2893 } 2894 2895 /*************************************************************/ 2896 function _exifNameTags($mode) { 2897 $tags = $this->_exifTagNames($mode); 2898 return $this->_names2Tags($tags); 2899 } 2900 2901 /*************************************************************/ 2902 function _iptcTagNames() { 2903 $tags = array(); 2904 $tags[0x14] = 'SuplementalCategories'; 2905 $tags[0x19] = 'Keywords'; 2906 $tags[0x78] = 'Caption'; 2907 $tags[0x7A] = 'CaptionWriter'; 2908 $tags[0x69] = 'Headline'; 2909 $tags[0x28] = 'SpecialInstructions'; 2910 $tags[0x0F] = 'Category'; 2911 $tags[0x50] = 'Byline'; 2912 $tags[0x55] = 'BylineTitle'; 2913 $tags[0x6E] = 'Credit'; 2914 $tags[0x73] = 'Source'; 2915 $tags[0x74] = 'CopyrightNotice'; 2916 $tags[0x05] = 'ObjectName'; 2917 $tags[0x5A] = 'City'; 2918 $tags[0x5C] = 'Sublocation'; 2919 $tags[0x5F] = 'ProvinceState'; 2920 $tags[0x65] = 'CountryName'; 2921 $tags[0x67] = 'OriginalTransmissionReference'; 2922 $tags[0x37] = 'DateCreated'; 2923 $tags[0x0A] = 'CopyrightFlag'; 2924 2925 return $tags; 2926 } 2927 2928 /*************************************************************/ 2929 function & _iptcNameTags() { 2930 $tags = $this->_iptcTagNames(); 2931 return $this->_names2Tags($tags); 2932 } 2933 2934 /*************************************************************/ 2935 function _names2Tags($tags2Names) { 2936 $names2Tags = array(); 2937 reset($tags2Names); 2938 while (list($tag, $name) = each($tags2Names)) { 2939 $names2Tags[$name] = $tag; 2940 } 2941 2942 return $names2Tags; 2943 } 2944 2945 /*************************************************************/ 2946 2947 /** 2948 * @param integer $pos 2949 */ 2950 function _getByte(&$data, $pos) { 2951 return ord($data{$pos}); 2952 } 2953 2954 /*************************************************************/ 2955 2956 /** 2957 * @param integer $pos 2958 */ 2959 function _putByte(&$data, $pos, $val) { 2960 $val = intval($val); 2961 2962 $data{$pos} = chr($val); 2963 2964 return $pos + 1; 2965 } 2966 2967 /*************************************************************/ 2968 function _getShort(&$data, $pos, $bigEndian = true) { 2969 if ($bigEndian) { 2970 return (ord($data{$pos}) << 8) 2971 + ord($data{$pos + 1}); 2972 } else { 2973 return ord($data{$pos}) 2974 + (ord($data{$pos + 1}) << 8); 2975 } 2976 } 2977 2978 /*************************************************************/ 2979 function _putShort(&$data, $pos = 0, $val = 0, $bigEndian = true) { 2980 $val = intval($val); 2981 2982 if ($bigEndian) { 2983 $data{$pos + 0} = chr(($val & 0x0000FF00) >> 8); 2984 $data{$pos + 1} = chr(($val & 0x000000FF) >> 0); 2985 } else { 2986 $data{$pos + 0} = chr(($val & 0x00FF) >> 0); 2987 $data{$pos + 1} = chr(($val & 0xFF00) >> 8); 2988 } 2989 2990 return $pos + 2; 2991 } 2992 2993 /*************************************************************/ 2994 2995 /** 2996 * @param integer $pos 2997 */ 2998 function _getLong(&$data, $pos, $bigEndian = true) { 2999 if ($bigEndian) { 3000 return (ord($data{$pos}) << 24) 3001 + (ord($data{$pos + 1}) << 16) 3002 + (ord($data{$pos + 2}) << 8) 3003 + ord($data{$pos + 3}); 3004 } else { 3005 return ord($data{$pos}) 3006 + (ord($data{$pos + 1}) << 8) 3007 + (ord($data{$pos + 2}) << 16) 3008 + (ord($data{$pos + 3}) << 24); 3009 } 3010 } 3011 3012 /*************************************************************/ 3013 3014 /** 3015 * @param integer $pos 3016 */ 3017 function _putLong(&$data, $pos, $val, $bigEndian = true) { 3018 $val = intval($val); 3019 3020 if ($bigEndian) { 3021 $data{$pos + 0} = chr(($val & 0xFF000000) >> 24); 3022 $data{$pos + 1} = chr(($val & 0x00FF0000) >> 16); 3023 $data{$pos + 2} = chr(($val & 0x0000FF00) >> 8); 3024 $data{$pos + 3} = chr(($val & 0x000000FF) >> 0); 3025 } else { 3026 $data{$pos + 0} = chr(($val & 0x000000FF) >> 0); 3027 $data{$pos + 1} = chr(($val & 0x0000FF00) >> 8); 3028 $data{$pos + 2} = chr(($val & 0x00FF0000) >> 16); 3029 $data{$pos + 3} = chr(($val & 0xFF000000) >> 24); 3030 } 3031 3032 return $pos + 4; 3033 } 3034 3035 /*************************************************************/ 3036 function & _getNullString(&$data, $pos) { 3037 $str = ''; 3038 $max = strlen($data); 3039 3040 while ($pos < $max) { 3041 if (ord($data{$pos}) == 0) { 3042 return $str; 3043 } else { 3044 $str .= $data{$pos}; 3045 } 3046 $pos++; 3047 } 3048 3049 return $str; 3050 } 3051 3052 /*************************************************************/ 3053 function & _getFixedString(&$data, $pos, $length = -1) { 3054 if ($length == -1) { 3055 $length = strlen($data) - $pos; 3056 } 3057 3058 $rv = substr($data, $pos, $length); 3059 return $rv; 3060 } 3061 3062 /*************************************************************/ 3063 function _putString(&$data, $pos, &$str) { 3064 $len = strlen($str); 3065 for ($i = 0; $i < $len; $i++) { 3066 $data{$pos + $i} = $str{$i}; 3067 } 3068 3069 return $pos + $len; 3070 } 3071 3072 /*************************************************************/ 3073 function _hexDump(&$data, $start = 0, $length = -1) { 3074 if (($length == -1) || (($length + $start) > strlen($data))) { 3075 $end = strlen($data); 3076 } else { 3077 $end = $start + $length; 3078 } 3079 3080 $ascii = ''; 3081 $count = 0; 3082 3083 echo "<tt>\n"; 3084 3085 while ($start < $end) { 3086 if (($count % 16) == 0) { 3087 echo sprintf('%04d', $count) . ': '; 3088 } 3089 3090 $c = ord($data{$start}); 3091 $count++; 3092 $start++; 3093 3094 $aux = dechex($c); 3095 if (strlen($aux) == 1) 3096 echo '0'; 3097 echo $aux . ' '; 3098 3099 if ($c == 60) 3100 $ascii .= '<'; 3101 elseif ($c == 62) 3102 $ascii .= '>'; 3103 elseif ($c == 32) 3104 $ascii .= ' '; 3105 elseif ($c > 32) 3106 $ascii .= chr($c); 3107 else 3108 $ascii .= '.'; 3109 3110 if (($count % 4) == 0) { 3111 echo ' - '; 3112 } 3113 3114 if (($count % 16) == 0) { 3115 echo ': ' . $ascii . "<br>\n"; 3116 $ascii = ''; 3117 } 3118 } 3119 3120 if ($ascii != '') { 3121 while (($count % 16) != 0) { 3122 echo '-- '; 3123 $count++; 3124 if (($count % 4) == 0) { 3125 echo ' - '; 3126 } 3127 } 3128 echo ': ' . $ascii . "<br>\n"; 3129 } 3130 3131 echo "</tt>\n"; 3132 } 3133 3134 /*****************************************************************/ 3135} 3136 3137/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3138