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