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