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