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