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