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