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