Source for file exif.inc
Documentation is available at exif.inc
1 <?php 2 /** 3 * PHP Class to read, write and transfer EXIF information 4 * that most of the digital camera produces. 5 * 6 * This class is based on jhead (in C) by Matthias Wandel 7 * 8 * Vinay Yadav (vinayRas) < [email protected] > 9 * http://www.sanisoft.com/phpexifrw/ 10 * 11 * For more information on EXIF 12 * http://www.pima.net/standards/it10/PIMA15740/exif.htm 13 * 14 * Features: 15 * - Read Exif Information 16 * - Extract and display emdedded thumbnails 17 * - Transfer Exif Information 18 * - Add Comments to files. 19 * - Tranfering EXIF information from one file to another. 20 * 21 * TODO 22 * 1. Writing exif information to the file. 23 * 2. Add EXIF audio reading methods (I think it exists!) 24 * 3. Support of additional tags. 25 * 4. Handling Unicode character in UserComment tag of EXif Information. 26 */ 27 28 /** * Start Of Frame N */ 28 29 define("M_SOF0",0xC0); 30 /** * N indicates which compression process */ 30 31 define("M_SOF1",0xC1); 32 /** * Only SOF0-SOF2 are now in common use */ 32 33 define("M_SOF2",0xC2); 34 /** * */ 34 35 define("M_SOF3",0xC3); 36 /** * NB: codes C4 and CC are NOT SOF markers */ 36 37 define("M_SOF5",0xC5); 38 /** * */ 38 39 define("M_SOF6",0xC6); 40 /** * */ 40 41 define("M_SOF7",0xC7); 42 /** * */ 42 43 define("M_SOF9",0xC9); 44 /** * */ 44 45 define("M_SOF10",0xCA); 46 /** * */ 46 47 define("M_SOF11",0xCB); 48 /** * */ 48 49 define("M_SOF13",0xCD); 50 /** * */ 50 51 define("M_SOF14",0xCE); 52 /** * */ 52 53 define("M_SOF15",0xCF); 54 /** * Start Of Image (beginning of datastream) */ 54 55 define("M_SOI",0xD8); 56 /** * End Of Image (end of datastream) */ 56 57 define("M_EOI",0xD9); 58 /** * Start Of Scan (begins compressed data) */ 58 59 define("M_SOS",0xDA); 60 /** * Jfif marker */ 60 61 define("M_JFIF",0xE0); 62 /** * Exif marker */ 62 63 define("M_EXIF",0xE1); 64 /** * Image Title */ 64 65 define("M_COM",0xFE); 66 67 define("NUM_FORMATS","12"); 68 69 /** * Tag Data Format */ 69 70 define("FMT_BYTE","1"); 71 /** * ASCII */ 71 72 define("FMT_STRING","2"); 73 /** * Short */ 73 74 define("FMT_USHORT","3"); 75 /** * Long */ 75 76 define("FMT_ULONG","4"); 77 /** * Rational */ 77 78 define("FMT_URATIONAL","5"); 79 /** * Byte */ 79 80 define("FMT_SBYTE","6"); 81 /** * Undefined */ 81 82 define("FMT_UNDEFINED","7"); 83 /** * Short */ 83 84 define("FMT_SSHORT","8"); 85 /** * Long */ 85 86 define("FMT_SLONG","9"); 87 /** * Rational */ 87 88 define("FMT_SRATIONAL","10"); 89 /** * Single */ 89 90 define("FMT_SINGLE","11"); 91 /** * Double */ 91 92 define("FMT_DOUBLE","12"); 93 94 /** * Exif IFD */ 94 95 define("TAG_EXIF_OFFSET","0x8769"); 96 /** * Interoperability tag */ 96 97 define("TAG_INTEROP_OFFSET","0xa005"); 98 /** * Image input equipment manufacturer */ 98 99 define("TAG_MAKE","0x010F"); 100 /** * Image input equipment model */ 100 101 define("TAG_MODEL","0x0110"); 102 /** * Orientation of image */ 102 103 define("TAG_ORIENTATION","0x0112"); 104 /** * Exposure Time */ 104 105 define("TAG_EXPOSURETIME","0x829A"); 106 /** * F Number */ 106 107 define("TAG_FNUMBER","0x829D"); 108 /** * Shutter Speed */ 108 109 define("TAG_SHUTTERSPEED","0x9201"); 110 /** * Aperture */ 110 111 define("TAG_APERTURE","0x9202"); 112 /** * Aperture */ 112 113 define("TAG_MAXAPERTURE","0x9205"); 114 /** * Lens Focal Length */ 114 115 define("TAG_FOCALLENGTH","0x920A"); 116 /** * The date and time when the original image data was generated. */ 116 117 define("TAG_DATETIME_ORIGINAL","0x9003"); 118 /** * User Comments */ 118 119 define("TAG_USERCOMMENT","0x9286"); 120 /** * subject Location */ 120 121 define("TAG_SUBJECT_DISTANCE","0x9206"); 122 /** * Flash */ 122 123 define("TAG_FLASH","0x9209"); 124 /** * Focal Plane X Resolution */ 124 125 define("TAG_FOCALPLANEXRES","0xa20E"); 126 /** * Focal Plane Resolution Units */ 126 127 define("TAG_FOCALPLANEUNITS","0xa210"); 128 /** * Image Width */ 128 129 define("TAG_EXIF_IMAGEWIDTH","0xA002"); 130 /** * Image Height */ 130 131 define("TAG_EXIF_IMAGELENGTH","0xA003"); 132 /** * Exposure Bias */ 132 133 define("TAG_EXPOSURE_BIAS","0x9204"); 134 /** * Light Source */ 134 135 define("TAG_WHITEBALANCE","0x9208"); 136 /** * Metering Mode */ 136 137 define("TAG_METERING_MODE","0x9207"); 138 /** * Exposure Program */ 138 139 define("TAG_EXPOSURE_PROGRAM","0x8822"); 140 /** * ISO Equivalent Speed Rating */ 140 141 define("TAG_ISO_EQUIVALENT","0x8827"); 142 /** * Compressed Bits Per Pixel */ 142 143 define("TAG_COMPRESSION_LEVEL","0x9102"); 144 /** * Thumbnail Start Offset */ 144 145 define("TAG_THUMBNAIL_OFFSET","0x0201"); 146 /** * Thumbnail Length */ 146 147 define("TAG_THUMBNAIL_LENGTH","0x0202"); 148 /** * Image Marker */ 148 149 define("PSEUDO_IMAGE_MARKER",0x123); 150 /** * Max Image Title Length */ 150 151 define("MAX_COMMENT",2000); 152 153 /** 154 * As more and more tags will be added will, the contents of array will increase. 155 * DONT remove any blank array, since they do contain several tags. 156 */ 157 158 $FMT_BYTE_ARRAY = array(); 159 $FMT_STRING_ARRAY = array( 160 0x010E, //Image title 161 0x010F, // Make - Image input equipment manufacturer 162 0x0110, // Model - Image input equipment model 163 0x0131, // Software - Software used 164 0x013B, // Artist - Person who created the image 165 0x8298,// Copyright - Copyright holder 166 0x9003, // DateTimeOriginal - Date and time of original data generation 167 ); 168 $FMT_USHORT_ARRAY = array( 169 0x0112, // Orientation 170 0x8822, // Exposure Program 171 0x9207, // Metering mode 172 0x9209, // Flash 173 0xA002, // Valid image width PixelXDimension 174 0xA003, // Valid image height PixelYDimension 175 ); 176 $FMT_ULONG_ARRAY = array( 177 0x0202, // JPEGInterchangeFormatLength 178 ); 179 $FMT_URATIONAL_ARRAY = array( 180 0x829A, // Exposure Time 181 0x829D, // F Number 182 0x9102, // CompressedBitsPerPixel 183 0x9202, // Aperture 184 0x9205, // MaxApertureValue 185 0x920A, // focal length 186 ); 187 $FMT_SBYTE_ARRAY = array(); 188 $FMT_UNDEFINED_ARRAY = array(); 189 $FMT_SSHORT_ARRAY = array(); 190 $FMT_SLONG_ARRAY = array(); 191 $FMT_SRATIONAL_ARRAY = array( 192 0x9201, // shutter speed 193 0x9204, // Exposure Bias 194 ); 195 $FMT_SINGLE_ARRAY = array(); 196 $FMT_DOUBLE_ARRAY = array(); 197 198 /** error Description */ 198 199 /** 200 1 - File does not exists! 201 2 - 202 3 - Filename not provided 203 204 10 - too many padding bytes 205 11 - "invalid marker" 206 12 - Premature end of file? 207 208 209 51 - "Illegal subdirectory link" 210 52 - "NOT EXIF FORMAT" 211 53 - "Invalid Exif alignment marker.\n" 212 54 - "Invalid Exif start (1)" 213 214 */ 215 216 $TagTable = array( 217 array( 0x100, "ImageWidth"), 218 array( 0x101, "ImageLength"), 219 array( 0x102, "BitsPerSample"), 220 array( 0x103, "Compression"), 221 array( 0x106, "PhotometricInterpretation"), 222 array( 0x10A, "FillOrder"), 223 array( 0x10D, "DocumentName"), 224 array( 0x10E, "ImageDescription"), 225 array( 0x10F, "Make"), 226 array( 0x110, "Model"), 227 array( 0x111, "StripOffsets"), 228 array( 0x112, "Orientation"), 229 array( 0x115, "SamplesPerPixel"), 230 array( 0x116, "RowsPerStrip"), 231 array( 0x117, "StripByteCounts"), 232 array( 0x11A, "XResolution"), 233 array( 0x11B, "YResolution"), 234 array( 0x11C, "PlanarConfiguration"), 235 array( 0x128, "ResolutionUnit"), 236 array( 0x12D, "TransferFunction"), 237 array( 0x131, "Software"), 238 array( 0x132, "DateTime"), 239 array( 0x13B, "Artist"), 240 array( 0x13E, "WhitePoint"), 241 array( 0x13F, "PrimaryChromaticities"), 242 array( 0x156, "TransferRange"), 243 array( 0x200, "JPEGProc"), 244 array( 0x201, "ThumbnailOffset"), 245 array( 0x202, "ThumbnailLength"), 246 array( 0x211, "YCbCrCoefficients"), 247 array( 0x212, "YCbCrSubSampling"), 248 array( 0x213, "YCbCrPositioning"), 249 array( 0x214, "ReferenceBlackWhite"), 250 array( 0x828D, "CFARepeatPatternDim"), 251 array( 0x828E, "CFAPattern"), 252 array( 0x828F, "BatteryLevel"), 253 array( 0x8298, "Copyright"), 254 array( 0x829A, "ExposureTime"), 255 array( 0x829D, "FNumber"), 256 array( 0x83BB, "IPTC/NAA"), 257 array( 0x8769, "ExifOffset"), 258 array( 0x8773, "InterColorProfile"), 259 array( 0x8822, "ExposureProgram"), 260 array( 0x8824, "SpectralSensitivity"), 261 array( 0x8825, "GPSInfo"), 262 array( 0x8827, "ISOSpeedRatings"), 263 array( 0x8828, "OECF"), 264 array( 0x9000, "ExifVersion"), 265 array( 0x9003, "DateTimeOriginal"), 266 array( 0x9004, "DateTimeDigitized"), 267 array( 0x9101, "ComponentsConfiguration"), 268 array( 0x9102, "CompressedBitsPerPixel"), 269 array( 0x9201, "ShutterSpeedValue"), 270 array( 0x9202, "ApertureValue"), 271 array( 0x9203, "BrightnessValue"), 272 array( 0x9204, "ExposureBiasValue"), 273 array( 0x9205, "MaxApertureValue"), 274 array( 0x9206, "SubjectDistance"), 275 array( 0x9207, "MeteringMode"), 276 array( 0x9208, "LightSource"), 277 array( 0x9209, "Flash"), 278 array( 0x920A, "FocalLength"), 279 array( 0x927C, "MakerNote"), 280 array( 0x9286, "UserComment"), 281 array( 0x9290, "SubSecTime"), 282 array( 0x9291, "SubSecTimeOriginal"), 283 array( 0x9292, "SubSecTimeDigitized"), 284 array( 0xA000, "FlashPixVersion"), 285 array( 0xA001, "ColorSpace"), 286 array( 0xA002, "ExifImageWidth"), 287 array( 0xA003, "ExifImageLength"), 288 array( 0xA005, "InteroperabilityOffset"), 289 array( 0xA20B, "FlashEnergy"), // 0x920B in TIFF/EP 290 array( 0xA20C, "SpatialFrequencyResponse"), // 0x920C - - 291 array( 0xA20E, "FocalPlaneXResolution"), // 0x920E - - 292 array( 0xA20F, "FocalPlaneYResolution"), // 0x920F - - 293 array( 0xA210, "FocalPlaneResolutionUnit"), // 0x9210 - - 294 array( 0xA214, "SubjectLocation"), // 0x9214 - - 295 array( 0xA215, "ExposureIndex"), // 0x9215 - - 296 array( 0xA217, "SensingMethod"), // 0x9217 - - 297 array( 0xA300, "FileSource"), 298 array( 0xA301, "SceneType"), 299 array( 0, NULL) 300 ) ; 301 302 $ProcessTable = array( 303 array(M_SOF0, "Baseline"), 304 array(M_SOF1, "Extended sequential"), 305 array(M_SOF2, "Progressive"), 306 array(M_SOF3, "Lossless"), 307 array(M_SOF5, "Differential sequential"), 308 array(M_SOF6, "Differential progressive"), 309 array(M_SOF7, "Differential lossless"), 310 array(M_SOF9, "Extended sequential, arithmetic coding"), 311 array(M_SOF10, "Progressive, arithmetic coding"), 312 array(M_SOF11, "Lossless, arithmetic coding"), 313 array(M_SOF13, "Differential sequential, arithmetic coding"), 314 array(M_SOF14, "Differential progressive, arithmetic coding"), 315 array(M_SOF15, "Differential lossless, arithmetic coding"), 316 array(0, "Unknown") 317 ); 318 319 /** 320 * PHP Class to read, write and transfer EXIF information 321 * that most of the digital camera produces 322 * Currenty it can only read JPEG file. 323 */ 324 /** 325 * @author Vinay Yadav (vinayRas) < [email protected] > 326 * 327 * @todo Writing exif information to the file. 328 * @todo Add EXIF audio reading methods (I think it exists!) 329 * @todo Support of additional tags. 330 * @todo Handling Unicode character in UserComment tag of EXif Information. 331 * 332 * @version 0.5 333 * @licence http://opensource.org/licenses/lgpl-license.php GNU LGPL 334 */ 335 class phpExifRW { 336 337 /* 338 * Array containg all Exif and JPEG image attributes 339 * into regular expressions for themselves. 340 * $ImageInfo[TAG] = TAG_VALUE; 341 * 342 * @var array 343 * @access private 344 * 345 */ 346 var $ImageInfo = array(); 347 348 var $MotorolaOrder = 0; 349 var $ExifImageWidth = 0; // 350 var $FocalplaneXRes = 0; // 351 var $FocalplaneUnits = 0; // 352 var $sections = array(); 353 var $currSection = 0; /** Stores total number fo Sections */ 353 354 355 var $BytesPerFormat = array(0,1,1,2,4,8,1,1,2,4,8,4,8); 356 357 var $DirWithThumbnailPtrs = 0; 358 var $ThumbnailSize = 0; 359 360 var $ReadMode = array( 361 "READ_EXIF" => 1, 362 "READ_IMAGE" => 2, 363 "READ_ALL" => 3 364 ); 365 366 var $ImageReadMode = 3; /** related to $RealMode arrays values */ 366 367 var $file = ""; /** JPEG file to parse for EXIF data */ 367 368 var $newFile = 1; /** flag to check if the current file has been parsed or not. */ 368 369 370 var $thumbnail = ""; /* Name of thumbnail */ 371 var $thumbnailURL = ""; /* */ 372 373 var $exifSection = -1; // market the exif section index oout of all sections 374 375 var $errno = 0; 376 var $errstr = ""; 377 378 var $debug = false; 379 var $showTags = false; 380 381 // Caching ralated variables 382 var $caching = true; /* Should cacheing of image thumnails be allowed? */ 383 var $cacheDir = ""; /* Checkout constructor for default path. */ 384 385 /** 386 * Constructor 387 * @param string File name to be parsed. 388 * 389 */ 390 function phpExifRW($file = "") { 391 392 if(!empty($file)) { 393 $this->file = $file; 394 } 395 396 if($this->caching) { 397 $this->cacheDir = dirname(__FILE__)."/.cache_thumbs"; 398 399 /** 400 * If Cache directory does not exists then attempt to create it. 401 */ 402 if(!is_dir($this->cacheDir)) { 403 @mkdir($this->cacheDir); 404 } 405 406 // Prepare the ame of thumbnail 407 if(is_dir($this->cacheDir)) { 408 $this->thumbnail = $this->cacheDir."/".$this->file; 409 $this->thumbnailURL = ".cache_thumbs/$this->file"; 410 } 411 } 412 413 /** check if file exists! */ 414 if(!file_exists($this->file)) { 415 $this->errno = 1; 416 $this->errstr = "File '".$this->file."' does not exists!"; 417 } 418 $this->currSection = 0; 419 } 420 421 /** 422 * Show Debugging information 423 * 424 * @param string Debugging message to display 425 * @param int Type of error (0 - Warning, 1 - Error) 426 * @return void 427 * 428 */ 429 function debug($str,$TYPE = 0) { 430 if($this->debug) { 431 echo "<br>$str"; 432 if($TYPE == 1) { 433 exit; 434 } 435 } 436 } 437 438 /** 439 * Processes the whole file. 440 * 441 */ 442 function processFile() { 443 /** dont reparse the whole file. */ 444 if(!$this->newFile) return true; 445 446 /** Open the JPEG in binary safe reading mode */ 447 $fp = fopen($this->file,"rb"); 448 449 $this->ImageInfo["FileName"] = $this->file; 450 $this->ImageInfo["FileSize"] = filesize($this->file); /** Size of the File */ 451 $this->ImageInfo["FileDateTime"] = filectime($this->file); /** File node change time */ 452 453 /** check whether jped image or not */ 454 $a = fgetc($fp); 455 if (ord($a) != 0xff || ord(fgetc($fp)) != M_SOI){ 456 $this->debug("Not a JPEG FILE",1); 457 $this->errorno = 1; 458 $this->errorstr = "File '".$this->file."' does not exists!"; 459 } 460 $tmpTestLevel = 0; 461 /** Examines each byte one-by-one */ 462 while(!feof($fp)) { 463 $data = array(); 464 for ($a=0;$a<7;$a++){ 465 $marker = fgetc($fp); 466 if (ord($marker) != 0xff) break; 467 if ($a >= 6){ 468 $this->errno = 10; 469 $this->errstr = "too many padding bytes!"; 470 $this->debug($this->errstr,1); 471 return false; 472 } 473 } 474 475 if (ord($marker) == 0xff){ 476 // 0xff is legal padding, but if we get that many, something's wrong. 477 $this->errno = 10; 478 $this->errstr = "too many padding bytes!"; 479 $this->debug($this->errstr,1); 480 } 481 482 $marker = ord($marker); 483 $this->sections[$this->currSection]["type"] = $marker; 484 485 // Read the length of the section. 486 $lh = ord(fgetc($fp)); 487 $ll = ord(fgetc($fp)); 488 489 $itemlen = ($lh << 8) | $ll; 490 491 if ($itemlen < 2){ 492 $this->errno = 11; 493 $this->errstr = "invalid marker"; 494 $this->debug($this->errstr,1); 495 } 496 $this->sections[$this->currSection]["size"] = $itemlen; 497 498 $tmpDataArr = array(); /** Temporary Array */ 499 500 $tmpStr = fread($fp,$itemlen-2); 501 502 $tmpDataArr[] = chr($lh); 503 $tmpDataArr[] = chr($ll); 504 505 $chars = preg_split('//', $tmpStr, -1, PREG_SPLIT_NO_EMPTY); 506 $tmpDataArr = array_merge($tmpDataArr,$chars); 507 508 $data = $tmpDataArr; 509 $this->sections[$this->currSection]["data"] = $data; 510 511 if(count($data) != $itemlen) { 512 $this->errno = 12; 513 $this->errstr = "Premature end of file?"; 514 $this->debug($this->errstr,1); 515 } 516 517 $this->currSection++; /** */ 518 519 switch($marker) { 520 case M_SOS: 521 $this->debug("<br>Found '".M_SOS."' Section, Prcessing it... <br>");; 522 // If reading entire image is requested, read the rest of the data. 523 if ($this->ImageReadMode & $this->ReadMode["READ_IMAGE"]){ 524 // Determine how much file is left. 525 $cp = ftell($fp); 526 fseek($fp,0, SEEK_END); 527 $ep = ftell($fp); 528 fseek($fp, $cp, SEEK_SET); 529 530 $size = $ep-$cp; 531 $got = fread($fp, $size); 532 533 $this->sections[$this->currSection]["data"] = $got; 534 $this->sections[$this->currSection]["size"] = $size; 535 $this->sections[$this->currSection]["type"] = $PSEUDO_IMAGE_MARKER; 536 $this->currSection++; 537 $HaveAll = 1; 538 $exitAll =1; 539 } 540 $this->debug("<br>'".M_SOS."' Section, PROCESSED<br>"); 541 break; 542 case M_COM: // Comment section 543 $this->debug("<br>Found '".M_COM."'(Comment) Section, Processing<br>"); 544 $this->process_COM($data, $itemlen); 545 $this->debug("<br>'".M_COM."'(Comment) Section, PROCESSED<br>"); 546 547 $tmpTestLevel++; 548 break; 549 case M_SOI: 550 $this->debug(" <br> === START OF IMAGE =====<br>"); 551 break; 552 case M_EOI: 553 $this->debug(" <br>=== END OF IMAGE =====<br> "); 554 break; 555 case M_JFIF: 556 // Regular jpegs always have this tag, exif images have the exif 557 // marker instead, althogh ACDsee will write images with both markers. 558 // this program will re-create this marker on absence of exif marker. 559 // hence no need to keep the copy from the file. 560 //echo " <br> === M_JFIF =====<br>"; 561 $this->sections[--$this->currSection]["data"] = ""; 562 break; 563 case M_EXIF: 564 // Seen files from some 'U-lead' software with Vivitar scanner 565 // that uses marker 31 for non exif stuff. Thus make sure 566 // it says 'Exif' in the section before treating it as exif. 567 $this->debug("<br>Found '".M_EXIF."'(Exif) Section, Proccessing<br>"); 568 $this->exifSection = $this->currSection-1; 569 if (($this->ImageReadMode & $this->ReadMode["READ_EXIF"]) && ($data[2].$data[3].$data[4].$data[5]) == "Exif"){ 570 $this->process_EXIF($data, $itemlen); 571 }else{ 572 // Discard this section. 573 $this->sections[--$this->currSection]["data"] = ""; 574 } 575 $this->debug("<br>'".M_EXIF."'(Exif) Section, PROCESSED<br>"); 576 $tmpTestLevel++; 577 break; 578 case M_SOF0: 579 case M_SOF1: 580 case M_SOF2: 581 case M_SOF3: 582 case M_SOF5: 583 case M_SOF6: 584 case M_SOF7: 585 case M_SOF9: 586 case M_SOF10: 587 case M_SOF11: 588 case M_SOF13: 589 case M_SOF14: 590 case M_SOF15: 591 $this->debug("<br>Found M_SOFn Section, Processing<br>"); 592 $this->process_SOFn($data,$marker); 593 $this->debug("<br>M_SOFn Section, PROCESSED<br>"); 594 break; 595 default: 596 $this->debug("DEFAULT: Jpeg section marker 0x$marker x size $itemlen\n"); 597 } 598 $i++; 599 if($exitAll == 1) break; 600 if($tmpTestLevel == 2) break; 601 } 602 fclose($fp); 603 $this->newFile = 0; 604 } 605 606 /** 607 * Changing / Assiging new file 608 * @param string JPEG file to process 609 * 610 */ 611 function assign($file) { 612 613 if(!empty($file)) { 614 $this->file = $file; 615 } 616 617 /** check for existance of file! */ 618 if(!file_exists($this->file)) { 619 $this->errorno = 1; 620 $this->errorstr = "File '".$this->file."' does not exists!"; 621 } 622 $this->newFile = 1; 623 } 624 625 /** 626 * Process SOFn section of Image 627 * @param array An array containing whole section. 628 * @param hex Marker to specify the type of section. 629 * 630 */ 631 function process_SOFn($data,$marker) { 632 $data_precision = 0; 633 $num_components = 0; 634 635 $data_precision = ord($data[2]); 636 637 if($this->debug) { 638 print("Image Dimension Calculation:"); 639 print("((ord($data[3]) << 8) | ord($data[4]));"); 640 } 641 $this->ImageInfo["Height"] = ((ord($data[3]) << 8) | ord($data[4])); 642 $this->ImageInfo["Width"] = ((ord($data[5]) << 8) | ord($data[6])); 643 644 $num_components = ord($data[7]); 645 646 if ($num_components == 3){ 647 $this->ImageInfo["IsColor"] = 1; 648 }else{ 649 $this->ImageInfo["IsColor"] = 0; 650 } 651 652 $this->ImageInfo["Process"] = $marker; 653 $this->debug("JPEG image is $ImageInfo_Width * $ImageInfo_Height, $num_components color components, $data_precision bits per sample\n"); 654 } 655 656 /** 657 * Process Comments 658 * @param array Section data 659 * @param int Length of the section 660 * 661 */ 662 function process_COM($data,$length) { 663 if ($length > MAX_COMMENT) $length = MAX_COMMENT; 664 /** Truncate if it won't fit in our structure. */ 665 666 $nch = 0; 667 for ($a=2;$a<$length;$a++){ 668 $ch = $data[$a]; 669 if ($ch == '\r' && $data[$a+1] == '\n') continue; // Remove cr followed by lf. 670 671 $Comment .= $ch; 672 } 673 $this->ImageInfo[M_COM] = $Comment; 674 $this->debug("COM marker comment: $Comment\n"); 675 } 676 /** 677 * Process one of the nested EXIF directories. 678 * @param string All directory information 679 * @param string whole Section 680 * @param int Length of exif section 681 * 682 */ 683 function ProcessExifDir($DirStart, $OffsetBase, $ExifLength) { 684 global $TagTable 685 686 $NumDirEntries = 0; 687 $ValuePtr = array(); 688 689 $NumDirEntries = $this->Get16u($DirStart[0],$DirStart[1]); 690 691 692 $this->debug("<br>Directory with $NumDirEntries entries\n"); 693 694 for ($de=0;$de<$NumDirEntries;$de++){ 695 $DirEntry = array_slice($DirStart,2+12*$de); 696 697 $Tag = $this->Get16u($DirEntry[0],$DirEntry[1]); 698 $Format = $this->Get16u($DirEntry[2],$DirEntry[3]); 699 $Components = $this->Get32u($DirEntry[4],$DirEntry[5],$DirEntry[6],$DirEntry[7]); 700 701 /** 702 if ((Format-1) >= NUM_FORMATS) { 703 // (-1) catches illegal zero case as unsigned underflows to positive large. 704 ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); 705 continue; 706 } 707 */ 708 709 $ByteCount = $Components * $this->BytesPerFormat[$Format]; 710 711 if ($ByteCount > 4){ 712 $OffsetVal = $this->Get32u($DirEntry[8],$DirEntry[9],$DirEntry[10],$DirEntry[11]); 713 if ($OffsetVal+$ByteCount > $ExifLength){ 714 $this->debug("Illegal value pointer($OffsetVal) for tag $Tag",1); 715 } 716 $ValuePtr = array_slice($OffsetBase,$OffsetVal); 717 } else { 718 $ValuePtr = array_slice($DirEntry,8); 719 } 720 721 /** 722 if (LastExifRefd < ValuePtr+ByteCount){ 723 // Keep track of last byte in the exif header that was actually referenced. 724 // That way, we know where the discardable thumbnail data begins. 725 LastExifRefd = ValuePtr+ByteCount; 726 } 727 */ 728 729 if($this->showTags) { 730 for ($a=0;;$a++){ 731 if ($TagTable[$a][0] == 0){ 732 $this->debug(" Unknown Tag $Tag Value = "); 733 break; 734 } 735 if ($TagTable[$a][0] == $Tag){ 736 $this->debug(" ".$TagTable[$a][1]." ="); 737 break; 738 } 739 } 740 741 switch($Format) { 742 case FMT_UNDEFINED: 743 // Undefined is typically an ascii string. 744 745 case FMT_STRING: 746 // String arrays printed without function call (different from int arrays) 747 { 748 $this->debug("\""); //" 749 $str =""; 750 for ($a=0;$a<$ByteCount;$a++){ 751 $str .= $ValuePtr[$a]; 752 } 753 $this->debug("$str\"\n"); // " 754 755 } 756 break; 757 default: 758 $this->PrintFormatNumber($ValuePtr, $Format, $ByteCount); 759 } // end of switch 760 } // end of if 761 762 // Extract useful components of tag 763 switch($Tag){ 764 765 case TAG_MAKE: 766 $this->ImageInfo[TAG_MAKE]= implode("",array_slice($ValuePtr,0,$ByteCount)); 767 break; 768 769 case TAG_MODEL: 770 $this->ImageInfo[TAG_MODEL] = implode("",array_slice($ValuePtr,0,$ByteCount)); 771 772 break; 773 774 case TAG_DATETIME_ORIGINAL: 775 $this->ImageInfo[TAG_DATETIME_ORIGINAL] = implode("",array_slice($ValuePtr,0,$ByteCount)); 776 $this->ImageInfo["DatePointer"] = implode("",array_slice($ValuePtr,0)); 777 break; 778 779 case TAG_USERCOMMENT: 780 // Olympus has this padded with trailing spaces. Remove these first. 781 for ($a=$ByteCount;;){ 782 $a--; 783 if ($ValuePtr[$a] == ' '){ 784 //$ValuePtr[$a] = '\0'; 785 } else { 786 break; 787 } 788 if ($a == 0) break; 789 } 790 791 // Copy the comment 792 if (($ValuePtr[0].$ValuePtr[1].$ValuePtr[2].$ValuePtr[3].$ValuePtr[4]) == "ASCII"){ 793 for ($a=5;$a<10;$a++){ 794 $c = $ValuePtr[a]; 795 if ($c != '\0' && $c != ' '){ 796 $this->ImageInfo[TAG_USERCOMMENT] = implode("",array_slice($ValuePtr,0,$ByteCount)); 797 break; 798 } 799 } 800 } else if (($ValuePtr[0].$ValuePtr[1].$ValuePtr[2].$ValuePtr[3].$ValuePtr[4].$ValuePtr[5].$ValuePtr[6]) == "Unicode"){ 801 $this->ImageInfo[TAG_USERCOMMENT] = implode("",array_slice($ValuePtr,0,$ByteCount)); 802 /** 803 * Handle Unicode characters here... 804 */ 805 } else { 806 $this->ImageInfo[TAG_USERCOMMENT] = implode("",array_slice($ValuePtr,0,$ByteCount)); 807 } 808 break; 809 810 case TAG_FNUMBER: 811 // Simplest way of expressing aperture, so I trust it the most. 812 // (overwrite previously computd value if there is one) 813 $this->ImageInfo[TAG_FNUMBER] = $this->ConvertAnyFormat(implode("",array_slice($ValuePtr,0)), $Format); 814 break; 815 816 case TAG_APERTURE: 817 case TAG_MAXAPERTURE: 818 // More relevant info always comes earlier, so only use this field if we don't 819 // have appropriate aperture information yet. 820 if ($this->ImageInfo[TAG_MAXAPERTURE] == 0){ 821 $tmpArr = $this->ConvertAnyFormat($ValuePtr, $Format); 822 $this->ImageInfo[TAG_MAXAPERTURE] = exp($tmpArr[0]*log(2)*0.5); 823 } 824 break; 825 826 case TAG_FOCALLENGTH: 827 // Nice digital cameras actually save the focal length as a function 828 // of how farthey are zoomed in. 829 $this->ImageInfo[TAG_FOCALLENGTH] = $this->ConvertAnyFormat($ValuePtr, $Format); 830 break; 831 832 case TAG_SUBJECT_DISTANCE: 833 // Inidcates the distacne the autofocus camera is focused to. 834 // Tends to be less accurate as distance increases. 835 $this->ImageInfo[TAG_SUBJECT_DISTANCE] = $this->ConvertAnyFormat($ValuePtr, $Format); 836 break; 837 838 case TAG_EXPOSURETIME: 839 // Simplest way of expressing exposure time, so I trust it most. 840 // (overwrite previously computd value if there is one) 841 $this->ImageInfo[TAG_EXPOSURETIME] = $this->ConvertAnyFormat($ValuePtr, $Format); 842 break; 843 844 case TAG_SHUTTERSPEED: 845 // More complicated way of expressing exposure time, so only use 846 // this value if we don't already have it from somewhere else. 847 if ($this->ImageInfo[TAG_EXPOSURETIME] == 0){ 848 $sp = $this->ConvertAnyFormat($ValuePtr, $Format); 849 $this->ImageInfo[TAG_SHUTTERSPEED] = (1/exp($sp[0]*log(2))); 850 } 851 break; 852 853 case TAG_FLASH: 854 if ($this->ConvertAnyFormat($ValuePtr, $Format) & 7){ 855 $this->ImageInfo[TAG_FLASH] = 1; 856 } 857 break; 858 859 case TAG_ORIENTATION: 860 $this->ImageInfo[TAG_ORIENTATION] = $this->ConvertAnyFormat($ValuePtr, $Format); 861 if ($this->ImageInfo[TAG_ORIENTATION] < 1 || $this->ImageInfo[TAG_ORIENTATION] > 8){ 862 $this->debug(sprintf("Undefined rotation value %d", $this->ImageInfo[TAG_ORIENTATION], 0),1); 863 $this->ImageInfo[TAG_ORIENTATION] = 0; 864 } 865 break; 866 867 case TAG_EXIF_IMAGELENGTH: 868 /** 869 * Image height 870 */ 871 $a = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 872 if ($this->ExifImageLength < $a) $this->ExifImageLength = $a; 873 $this->ImageInfo[TAG_EXIF_IMAGELENGTH] = $this->ExifImageLength; 874 $this->ImageInfo["Height"] = $this->ExifImageLength; 875 break; 876 case TAG_EXIF_IMAGEWIDTH: 877 // Use largest of height and width to deal with images that have been 878 // rotated to portrait format. 879 $a = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 880 if ($this->ExifImageWidth < $a) $this->ExifImageWidth = $a; 881 $this->ImageInfo[TAG_EXIF_IMAGEWIDTH] = $this->ExifImageWidth; 882 $this->ImageInfo["Width"] = $this->ExifImageWidth; 883 884 break; 885 886 case TAG_FOCALPLANEXRES: 887 $this->FocalplaneXRes = $this->ConvertAnyFormat($ValuePtr, $Format); 888 $this->FocalplaneXRes = $this->FocalplaneXRes[0]; 889 $this->ImageInfo[TAG_FOCALPLANEXRES] = $this->FocalplaneXRes[0]; 890 break; 891 892 case TAG_FOCALPLANEUNITS: 893 switch($this->ConvertAnyFormat($ValuePtr, $Format)){ 894 case 1: $this->FocalplaneUnits = 25.4; break; // inch 895 case 2: 896 // According to the information I was using, 2 means meters. 897 // But looking at the Cannon powershot's files, inches is the only 898 // sensible value. 899 $this->FocalplaneUnits = 25.4; 900 break; 901 902 case 3: $this->FocalplaneUnits = 10; break; // centimeter 903 case 4: $this->FocalplaneUnits = 1; break; // milimeter 904 case 5: $this->FocalplaneUnits = .001; break; // micrometer 905 } 906 $this->ImageInfo[TAG_FOCALPLANEUNITS] = $this->FocalplaneUnits; 907 break; 908 909 // Remaining cases contributed by: Volker C. Schoech ([email protected]) 910 911 case TAG_EXPOSURE_BIAS: 912 $this->ImageInfo[TAG_EXPOSURE_BIAS] = $this->ConvertAnyFormat($ValuePtr, $Format); 913 break; 914 915 case TAG_WHITEBALANCE: 916 $this->ImageInfo[TAG_WHITEBALANCE] = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 917 break; 918 919 case TAG_METERING_MODE: 920 $this->ImageInfo[TAG_METERING_MODE] = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 921 break; 922 923 case TAG_EXPOSURE_PROGRAM: 924 $this->ImageInfo[TAG_EXPOSURE_PROGRAM] = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 925 break; 926 927 case TAG_ISO_EQUIVALENT: 928 $this->ImageInfo[TAG_ISO_EQUIVALENT] = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 929 if ( $this->ImageInfo[TAG_ISO_EQUIVALENT] < 50 ) $this->ImageInfo[TAG_ISO_EQUIVALENT] *= 200; 930 break; 931 932 case TAG_COMPRESSION_LEVEL: 933 $this->ImageInfo[TAG_COMPRESSION_LEVEL] = (int) $this->ConvertAnyFormat($ValuePtr, $Format); 934 break; 935 936 case TAG_THUMBNAIL_OFFSET: 937 $this->ThumbnailOffset = $this->ConvertAnyFormat($ValuePtr, $Format); 938 $this->DirWithThumbnailPtrs = $DirStart; 939 break; 940 941 case TAG_THUMBNAIL_LENGTH: 942 $this->ThumbnailSize = $this->ConvertAnyFormat($ValuePtr, $Format); 943 $this->ImageInfo[TAG_THUMBNAIL_LENGTH] = $this->ThumbnailSize; 944 break; 945 946 case TAG_EXIF_OFFSET: 947 case TAG_INTEROP_OFFSET: 948 { 949 950 $SubdirStart = array_slice($OffsetBase,$this->Get32u($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3])); 951 //if ($SubdirStart < $OffsetBase || $SubdirStart > $OffsetBase+$ExifLength){ 952 // debug("Illegal exif or interop ofset directory link",1); 953 //}else{ 954 $this->ProcessExifDir($SubdirStart, $OffsetBase, $ExifLength); 955 //} 956 continue; 957 } 958 } 959 } 960 961 { 962 // In addition to linking to subdirectories via exif tags, 963 // there's also a potential link to another directory at the end of each 964 // directory. this has got to be the result of a comitee! 965 $tmpDirStart = array_slice($DirStart,2+12*$NumDirEntries); 966 if (count($tmpDirStart) + 4 <= count($OffsetBase)+$ExifLength){ 967 $Offset = $this->Get32u($tmpDirStart[0],$tmpDirStart[1],$tmpDirStart[2],$tmpDirStart[3]); 968 if ($Offset){ 969 $SubdirStart = array_slice($OffsetBase,$Offset); 970 if (count($SubdirStart) > count($OffsetBase)+$ExifLength){ 971 if (count($SubdirStart) < count($OffsetBase)+$ExifLength+20){ 972 // Jhead 1.3 or earlier would crop the whole directory! 973 // As Jhead produces this form of format incorrectness, 974 // I'll just let it pass silently 975 //if (ShowTags) printf("Thumbnail removed with Jhead 1.3 or earlier\n"); 976 } else { 977 $this->errno = 51; 978 $this->errstr = "Illegal subdirectory link"; 979 $this->debug($this->errstr,1); 980 } 981 }else{ 982 if (count($SubdirStart) <= count($OffsetBase)+$ExifLength){ 983 $this->ProcessExifDir($SubdirStart, $OffsetBase, $ExifLength); 984 } 985 } 986 } 987 } else { 988 // The exif header ends before the last next directory pointer. 989 } 990 } 991 992 /** 993 * Check if thumbnail has been cached or not. 994 * If yes! then read the file. 995 */ 996 997 if(file_exists($this->thumbnail) && $this->caching && (filemtime($this->thumbnail) == filemtime($this->file) )) { 998 $fp = fopen($this->thumbnail,"rb"); 999 $tmpStr = fread($fp,filesize($this->thumbnail)); 1000 1001 $this->ImageInfo["ThumbnailPointer"] = preg_split('//', $tmpStr, -1, PREG_SPLIT_NO_EMPTY); 1002 $this->ImageInfo["ThumbnailSize"] = filesize($this->thumbnail); 1003 } else{ 1004 if ($this->ThumbnailSize && $this->ThumbnailOffset){ 1005 if ($this->ThumbnailSize + $this->ThumbnailOffset <= $ExifLength){ 1006 // The thumbnail pointer appears to be valid. Store it. 1007 $this->ImageInfo["ThumbnailPointer"] = array_slice($OffsetBase,$this->ThumbnailOffset); 1008 $this->ImageInfo["ThumbnailSize"] = $this->ThumbnailSize; 1009 1010 /* Save the thumbnail */ 1011 if($this->caching && is_dir($this->cacheDir)) { 1012 $this->saveThumbnail($this->thumbnail); 1013 } 1014 } 1015 } 1016 1017 } 1018 $this->debug(sprintf("Thumbnail size: %d bytes\n",$this->ThumbnailSize),"TAGS"); 1019 } 1020 1021 /** 1022 * Process Exif data 1023 * @param array Section data as an array 1024 * @param int Length of the section (length of data array) 1025 * 1026 */ 1027 function process_EXIF($data,$length) { 1028 1029 $this->debug("Exif header $length bytes long\n"); 1030 if(($data[2].$data[3].$data[4].$data[5]) != "Exif") { 1031 $this->errno = 52; 1032 $this->errstr = "NOT EXIF FORMAT"; 1033 $this->debug($this->errstr,1); 1034 } 1035 1036 $this->ImageInfo["FlashUsed"] = 0; 1037 /** If it s from a digicam, and it used flash, it says so. */ 1038 1039 $this->FocalplaneXRes = 0; 1040 $this->FocalplaneUnits = 0; 1041 $this->ExifImageWidth = 0; 1042 1043 if(($data[8].$data[9]) == "II") { 1044 $this->debug("Exif section in Intel order\n"); 1045 $this->MotorolaOrder = 0; 1046 } else if(($data[8].$data[9]) == "MM") { 1047 $this->debug("Exif section in Motorola order\n"); 1048 $this->MotorolaOrder = 1; 1049 } else { 1050 $this->errno = 53; 1051 $this->errstr = "Invalid Exif alignment marker.\n"; 1052 $this->debug($this->errstr,1); 1053 return; 1054 } 1055 1056 if($this->Get16u($data[10],$data[11]) != 0x2A || $this->Get32s($data[12],$data[13],$data[14],$data[15]) != 0x08) { 1057 $this->errno = 54; 1058 $this->errstr = "Invalid Exif start (1)"; 1059 $this->debug($this->errstr,1); 1060 } 1061 1062 $LastExifRefd = $ExifSection; 1063 $DirWithThumbnailPtrs = NULL; 1064 1065 $this->ProcessExifDir(array_slice($data,16),array_slice($data,8),$length); 1066 1067 // Compute the CCD width, in milimeters. 2 1068 if ($this->FocalplaneXRes != 0){ 1069 $this->ImageInfo["CCDWidth"] = (float)($this->ExifImageWidth * $this->FocalplaneUnits / $this->FocalplaneXRes); 1070 } 1071 1072 $this->debug("Non settings part of Exif header: ".($ExifSection+$length-$LastExifRefd)." bytes\n"); 1073 } // end of function process_EXIF 1074 1075 /** 1076 * Converts two byte number into its equivalent int integer 1077 * @param int 1078 * @param int 1079 * 1080 */ 1081 function Get16u($val,$by) { 1082 if($this->MotorolaOrder){ 1083 return ((ord($val) << 8) | ord($by)); 1084 } else { 1085 return ((ord($by) << 8) | ord($val)); 1086 } 1087 } 1088 1089 /** 1090 * Converts 4-byte number into its equivalent integer 1091 * 1092 * @param int 1093 * @param int 1094 * @param int 1095 * @param int 1096 * 1097 * @return int 1098 */ 1099 function Get32s($val1,$val2,$val3,$val4) 1100 { 1101 $val1 = ord($val1); 1102 $val2 = ord($val2); 1103 $val3 = ord($val3); 1104 $val4 = ord($val4); 1105 1106 if ($this->MotorolaOrder){ 1107 return (($val1 << 24) | ($val2 << 16) | ($val3 << 8 ) | ($val4 << 0 )); 1108 }else{ 1109 return (($val4 << 24) | ($val3 << 16) | ($val2 << 8 ) | ($val1 << 0 )); 1110 } 1111 } 1112 /** 1113 * Converts 4-byte number into its equivalent integer with the help of Get32s 1114 * 1115 * @param int 1116 * @param int 1117 * @param int 1118 * @param int 1119 * 1120 * @return int 1121 * 1122 */ 1123 function get32u($val1,$val2,$val3,$val4) { 1124 return ($this->Get32s($val1,$val2,$val3,$val4) & 0xffffffff); 1125 } 1126 1127 /** 1128 * needed for examining exif header and printng the value of a tag 1129 * 1130 * @param array 1131 * @param int 1132 * @param int 1133 */ 1134 function PrintFormatNumber($ValuePtr, $Format, $ByteCount) 1135 { 1136 switch($Format){ 1137 case FMT_SBYTE: 1138 case FMT_BYTE: printf("%02x\n",$ValuePtr[0]); break; 1139 case FMT_USHORT: printf("%d\n",$this->Get16u($ValuePtr[0],$ValuePtr[1])); break; 1140 case FMT_ULONG: 1141 case FMT_SLONG: printf("%d\n",$this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3])); break; 1142 case FMT_SSHORT: printf("%hd\n",$this->Get16u($ValuePtr[0],$ValuePtr[1])); break; 1143 case FMT_URATIONAL: 1144 case FMT_SRATIONAL: 1145 printf("%d/%d\n",$this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]), $this->Get32s($ValuePtr[4],$ValuePtr[5],$ValuePtr[6],$ValuePtr[7])); 1146 break; 1147 1148 case FMT_SINGLE: printf("%f\n",$ValuePtr[0]); 1149 break; 1150 case FMT_DOUBLE: printf("%f\n",$ValuePtr[0]); 1151 break; 1152 default: 1153 printf("Unknown format %d:", $Format); 1154 } 1155 } 1156 1157 //-------------------------------------------------------------------------- 1158 // Evaluate number, be it int, rational, or float from directory. 1159 //-------------------------------------------------------------------------- 1160 function ConvertAnyFormat($ValuePtr, $Format) 1161 { 1162 $Value = 0; 1163 1164 switch($Format){ 1165 case FMT_SBYTE: $Value = $ValuePtr[0]; break; 1166 case FMT_BYTE: $Value = $ValuePtr[0]; break; 1167 1168 case FMT_USHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break; 1169 case FMT_ULONG: $Value = $this->Get32u($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break; 1170 1171 case FMT_URATIONAL: 1172 case FMT_SRATIONAL: 1173 { 1174 1175 $Num = $this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); 1176 $Den = $this->Get32s($ValuePtr[4],$ValuePtr[5],$ValuePtr[6],$ValuePtr[7]); 1177 if ($Den == 0){ 1178 $Value = 0; 1179 }else{ 1180 $Value = (double) ($Num/$Den); 1181 } 1182 return array($Value,array($Num,$Den)); 1183 break; 1184 } 1185 1186 case FMT_SSHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break; 1187 case FMT_SLONG: $Value = $this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break; 1188 1189 // Not sure if this is correct (never seen float used in Exif format) 1190 case FMT_SINGLE: $Value = $ValuePtr[0]; break; 1191 case FMT_DOUBLE: $Value = $ValuePtr[0]; break; 1192 } 1193 return $Value; 1194 } 1195 1196 /** 1197 * 1198 * Reverse of ConvertAnyFormat, - Incomplete 1199 * TODO: 1200 only FMT_URATIONAL, FMT_SRATIONAL works 1201 * 1202 */ 1203 function ConvertAnyFormatBack($Value, $Format) 1204 { 1205 //$Value = 0; 1206 switch($Format){ 1207 case FMT_SBYTE: $Value = $ValuePtr[0]; break; 1208 case FMT_BYTE: $Value = $ValuePtr[0]; break; 1209 1210 case FMT_USHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break; 1211 case FMT_ULONG: $Value = $this->Get32u($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break; 1212 1213 case FMT_URATIONAL: 1214 case FMT_SRATIONAL: 1215 { 1216 1217 $num = $Value[1][0]; 1218 $Den = $Value[1][1]; 1219 1220 $ValuePtr[0] = chr($num >> 24); 1221 $ValuePtr[1] = chr($num >> 16); 1222 $ValuePtr[2] = chr($num >> 8); 1223 $ValuePtr[3] = chr($num); 1224 1225 $ValuePtr[4] = chr($Den >> 24); 1226 $ValuePtr[5] = chr($Den >> 16); 1227 $ValuePtr[6] = chr($Den >> 8); 1228 $ValuePtr[7] = chr($Den); 1229 1230 break; 1231 } 1232 1233 case FMT_SSHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break; 1234 case FMT_SLONG: $Value = $this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break; 1235 1236 // Not sure if this is correct (never seen float used in Exif format) 1237 case FMT_SINGLE: $Value = $ValuePtr[0]; break; 1238 case FMT_DOUBLE: $Value = $ValuePtr[0]; break; 1239 default: 1240 return -1; 1241 } 1242 return $ValuePtr; 1243 } 1244 /** 1245 * Function to extract thumbnail from Exif data of the image. 1246 * and store it in a filename given by $ThumbFile 1247 * 1248 * @param String Files name to store the thumbnail 1249 * 1250 */ 1251 function saveThumbnail($ThumbFile) { 1252 $ThumbFile = trim($ThumbFile); 1253 if(empty($ThumbFile)) $ThumbFile = "th_".$this->file; 1254 1255 if (strlen($this->ImageInfo["ThumbnailPointer"]) > 0){ 1256 $tp = fopen($ThumbFile,"wb"); 1257 if(!$tp) { 1258 $this->errno = 2; 1259 $this->errstr = "Cannot Open file '$ThumbFile'"; 1260 } 1261 fwrite($tp,implode("",$this->ImageInfo["ThumbnailPointer"]), $this->ImageInfo["ThumbnailSize"]); 1262 fclose($tp); 1263 touch($ThumbFile,filemtime($this->file)); 1264 } 1265 $this->thumbnail = $ThumbFile; 1266 } 1267 1268 /** 1269 * Returns thumbnail url along with parameter supplied. 1270 * Should be called in src attribute of image 1271 * 1272 * @return string File URL 1273 * 1274 */ 1275 function showThumbnail() { 1276 return "showThumbnail.php?file=".$this->file; 1277 } 1278 1279 /** 1280 * Function to give back the whole image 1281 * @return string full image 1282 * 1283 */ 1284 function getThumbnail() { 1285 /* 1286 if($this->caching && !empty($this->cacheDir)) { 1287 $fp = fopen($thumbFilename,"rb"); 1288 $tmpStr = fread($fp,filesize($thumbFilename)); 1289 $this->ImageInfo["ThumbnailPointer"] = preg_split('//', $tmpStr, -1, PREG_SPLIT_NO_EMPTY); 1290 } 1291 */ 1292 return implode("",$this->ImageInfo["ThumbnailPointer"]); 1293 } 1294 1295 /** 1296 * Display the extracted Exif information. It can overloaded for the look to change. 1297 * 1298 * @return void 1299 */ 1300 function showImageInfo() { 1301 global $ProcessTable 1302 $tmpDebug = $this->debug; 1303 $this->debug = true; 1304 $this->debug(sprintf("File name : %s\n",$this->ImageInfo["FileName"])); 1305 $this->debug(sprintf("File size : %d bytes\n",$this->ImageInfo["FileSize"])); 1306 1307 { 1308 $this->debug(sprintf("File date : %s\n",date("d-M-Y H:i:s",$this->ImageInfo["FileDateTime"]))); 1309 } 1310 1311 if ($this->ImageInfo[TAG_MAKE]){ 1312 $this->debug(sprintf("Camera make : %s\n",$this->ImageInfo[TAG_MAKE])); 1313 $this->debug(sprintf("Camera model : %s\n",$this->ImageInfo[TAG_MODEL])); 1314 } 1315 if ($this->ImageInfo["DateTime"]){ 1316 $this->debug(sprintf("Date/Time : %s\n",$this->ImageInfo[TAG_DATETIME_ORIGINAL])); 1317 } 1318 1319 $this->debug(sprintf("Resolution : %d x %d\n",$this->ImageInfo["Width"], $this->ImageInfo["Height"])); 1320 1321 if ($this->ImageInfo[TAG_ORIENTATION] > 1){ 1322 // Only print orientation if one was supplied, and if its not 1 (normal orientation) 1323 1324 // 1 - "The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side." 1325 // 2 - "The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side." 1326 // 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side." 1327 // 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side." 1328 1329 // 5 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual top." 1330 // 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top." 1331 // 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom." 1332 // 8 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual bottom." 1333 1334 // Note: The descriptions here are the same as the name of the command line 1335 // ption to pass to jpegtran to right the image 1336 $OrientTab = array( 1337 "Undefined", 1338 "Normal", // 1 1339 "flip horizontal", // left right reversed mirror 1340 "rotate 180", // 3 1341 "flip vertical", // upside down mirror 1342 "transpose", // Flipped about top-left <--> bottom-right axis. 1343 "rotate 90", // rotate 90 cw to right it. 1344 "transverse", // flipped about top-right <--> bottom-left axis 1345 "rotate 270", // rotate 270 to right it. 1346 ); 1347 1348 $this->debug(sprintf("Orientation : %s\n", $OrientTab[$this->ImageInfo[TAG_ORIENTATION]])); 1349 } 1350 1351 if ($this->ImageInfo["IsColor"] == 0){ 1352 $this->debug(sprintf("Color/bw : Black and white\n")); 1353 } 1354 if ($this->ImageInfo[TAG_FLASH] >= 0){ 1355 $this->debug(sprintf("Flash used : %s\n",$this->ImageInfo[TAG_FLASH] ? "Yes" :"No")); 1356 } 1357 if ($this->ImageInfo[TAG_FOCALLENGTH]){ 1358 $this->debug(sprintf("Focal length : %4.1fmm (%s/%s)",(double)$this->ImageInfo[TAG_FOCALLENGTH][0],$this->ImageInfo[TAG_FOCALLENGTH][1][0],$this->ImageInfo[TAG_FOCALLENGTH][1][1])); 1359 if ($this->ImageInfo["CCDWidth"]){ 1360 $this->debug(sprintf(" (35mm equivalent: %dmm)", 1361 (int)($this->ImageInfo["FocalLength"]/$this->ImageInfo["CCDWidth"]*36 + 0.5))); 1362 } 1363 } 1364 1365 if ($this->ImageInfo["CCDWidth"]){ 1366 $this->debug(sprintf("CCD width : %4.2fmm\n",(double)$this->ImageInfo["CCDWidth"])); 1367 } 1368 1369 if ($this->ImageInfo[TAG_EXPOSURETIME]){ 1370 $this->debug(sprintf("Exposure time:%6.3f s (%d/%d)",(double)$this->ImageInfo[TAG_EXPOSURETIME][0], 1371 $this->ImageInfo[TAG_EXPOSURETIME][1][0],$this->ImageInfo[TAG_EXPOSURETIME][1][1])); 1372 if ($this->ImageInfo[TAG_EXPOSURETIME] <= 0.5){ 1373 $this->debug(sprintf(" (1/%d)",(int)(0.5 + 1/$this->ImageInfo[TAG_EXPOSURETIME][0]))); 1374 } 1375 } 1376 if ($this->ImageInfo[TAG_FNUMBER]){ 1377 $this->debug(sprintf("Aperture : f/%3.1f\n",(double)$this->ImageInfo[TAG_FNUMBER][0])); 1378 } 1379 if ($this->ImageInfo["Distance"]){ 1380 if ($this->ImageInfo["Distance"] < 0){ 1381 $this->debug("Focus dist. : Infinite\n"); 1382 }else{ 1383 $this->debug(sprintf("Focus dist. : %4.2fm\n",(double)$this->ImageInfo["Distance"])); 1384 } 1385 } 1386 1387 1388 if ($this->ImageInfo[TAG_ISO_EQUIVALENT]){ // 05-jan-2001 vcs 1389 $this->debug(sprintf("ISO equiv. : %2d\n",(int)$this->ImageInfo[TAG_ISO_EQUIVALENT])); 1390 } 1391 if ($this->ImageInfo[TAG_EXPOSURE_BIAS]){ // 05-jan-2001 vcs 1392 $this->debug(sprintf("Exposure bias:%4.2f (%d/%d)\n",(double)$this->ImageInfo[TAG_EXPOSURE_BIAS][0], 1393 $this->ImageInfo[TAG_EXPOSURE_BIAS][1][0],$this->ImageInfo[TAG_EXPOSURE_BIAS][1][1])); 1394 } 1395 1396 if ($this->ImageInfo[TAG_WHITEBALANCE]){ // 05-jan-2001 vcs 1397 switch($this->ImageInfo[TAG_WHITEBALANCE]) { 1398 case 1: 1399 $this->debug("Whitebalance : sunny\n"); 1400 break; 1401 case 2: 1402 $this->debug("Whitebalance : fluorescent\n"); 1403 break; 1404 case 3: 1405 $this->debug("Whitebalance : incandescent\n"); 1406 break; 1407 default: 1408 $this->debug("Whitebalance : cloudy\n"); 1409 } 1410 } 1411 if ($this->ImageInfo[TAG_METERING_MODE]){ // 05-jan-2001 vcs 1412 switch($this->ImageInfo[TAG_METERING_MODE]) { 1413 case 2: 1414 $this->debug("Metering Mode: center weight\n"); 1415 break; 1416 case 3: 1417 $this->debug("Metering Mode: spot\n"); 1418 break; 1419 case 5: 1420 $this->debug("Metering Mode: matrix\n"); 1421 break; 1422 } 1423 } 1424 if ($this->ImageInfo[TAG_EXPOSURE_PROGRAM]){ // 05-jan-2001 vcs 1425 switch($this->ImageInfo[TAG_EXPOSURE_PROGRAM]) { 1426 case 2: 1427 $this->debug("Exposure : program (auto)\n"); 1428 break; 1429 case 3: 1430 $this->debug("Exposure : aperture priority (semi-auto)\n"); 1431 break; 1432 case 4: 1433 $this->debug("Exposure : shutter priority (semi-auto)\n"); 1434 break; 1435 } 1436 } 1437 if ($this->ImageInfo[TAG_COMPRESSION_LEVEL]){ // 05-jan-2001 vcs 1438 switch($this->ImageInfo[TAG_COMPRESSION_LEVEL]) { 1439 case 1: 1440 $this->debug("Jpeg Quality : basic\n"); 1441 break; 1442 case 2: 1443 $this->debug("Jpeg Quality : normal\n"); 1444 break; 1445 case 4: 1446 $this->debug("Jpeg Quality : fine\n"); 1447 break; 1448 } 1449 } 1450 1451 for ($a=0;;$a++){ 1452 if ($ProcessTable[$a][0] == $this->ImageInfo["Process"] || $ProcessTable[$a][0] == 0){ 1453 $this->debug("Jpeg process : ".$ProcessTable[$a][1]); 1454 break; 1455 } 1456 } 1457 1458 // Print the comment. Print 'Comment:' for each new line of comment. 1459 if ($this->ImageInfo[TAG_USERCOMMENT]){ 1460 $this->debug("Exif Comment : ".$this->ImageInfo[TAG_USERCOMMENT]); 1461 } 1462 1463 // Print the comment. Print 'Comment:' for each new line of comment. 1464 if ($this->ImageInfo[M_COM]){ 1465 $this->debug("Image Comment : ".htmlentities($this->ImageInfo[M_COM])); 1466 } 1467 1468 1469 if($this->thumbnailURL) { 1470 $this->debug("<a href='$this->thumbnailURL'>$this->thumbnailURL</a>"); 1471 /** create a link to view extracted thumbnail */ 1472 } 1473 $this->debug = $tmpDebug; 1474 } // end of show function 1475 1476 1477 /** 1478 * Modifies or sets value of specified Tag - 1479 * @param hex Tag, whose value has to be set 1480 * @param string Tags value 1481 * 1482 */ 1483 function setExifData($param,$value) { 1484 $this->ImageInfo["$param"] = $value; 1485 } 1486 1487 /** 1488 * This functiion writes back the modifed exif data into the imageinfo array - INCOMPLETE 1489 * 1490 */ 1491 function modifyExifDetails() { 1492 1493 $newData[0] = $this->sections[$this->exifSection]["data"][0]; 1494 $newData[1] = $this->sections[$this->exifSection]["data"][1]; 1495 1496 $newData[2] = 'E'; $newData[3] = 'x'; 1497 $newData[4] = 'i'; $newData[5] = 'f'; 1498 $newData[6] = $this->sections[$this->exifSection]["data"][6]; 1499 $newData[7] = $this->sections[$this->exifSection]["data"][7]; 1500 1501 if($this->MotorolaOrder == 1) { 1502 $newData[8] = 'M';$newData[9] = 'M'; 1503 } else { 1504 $newData[8] = 'I';$newData[9] = 'I'; 1505 } 1506 1507 $newData[10] = chr(42 >> 8); 1508 $newData[11] = chr(42); 1509 1510 $newData[12] = chr(0); 1511 $newData[13] = chr(0); 1512 $newData[14] = chr(0); 1513 $newData[15] = chr(8); 1514 1515 $newData[16] = 1; 1516 $newData[17] = 1; 1517 1518 $totalLength = 16; $totalElements = 0; 1519 $offset = 10+(15*12); 1520 foreach($this->ImageInfo as $tag => $val) { 1521 if(eregi("0x",$tag)) { 1522 $tmpTag = hexdec($tag); 1523 // tag 1524 $newData[] = chr($tmpTag >> 8); 1525 $newData[] = chr($tmpTag); 1526 1527 // format 1528 $fmt = $this->getFormat($tag); 1529 $newData[] = chr($fmt >> 8); 1530 $newData[] = chr($fmt); 1531 1532 //components 1533 $chars = preg_split('//', $val, -1, PREG_SPLIT_NO_EMPTY); 1534 $ByteCount = count($chars); 1535 1536 $Components = ceil($ByteCount / $this->BytesPerFormat[$fmt]); 1537 1538 $newData[] = chr($Components >> 24); 1539 $newData[] = chr($Components >> 16); 1540 $newData[] = chr($Components >> 8); 1541 $newData[] = chr($Components); 1542 1543 if($ByteCount <= 4) { 1544 $newData[] = chr($chars[0] >> 8); 1545 $newData[] = chr($chars[0]); 1546 $newData[] = chr($chars[2]); 1547 $newData[] = chr($chars[3]); 1548 } else { 1549 $newData[] = chr($offset >> 24); 1550 $newData[] = chr($offset >> 16); 1551 $newData[] = chr($offset >> 8); 1552 $newData[] = chr($offset); 1553 1554 1555 if($fmt != FMT_STRING) { 1556 $arr = $this->ConvertAnyFormatBack($val,$fmt); 1557 $chars = $arr; 1558 $ByteCount = 8; 1559 } 1560 $offset+=$ByteCount; 1561 1562 $otherDataArr = array_merge($otherDataArr,$chars); 1563 } 1564 $totalLength += 12+$ByteCount; 1565 $totalElements++; 1566 } 1567 } 1568 $newData = array_merge($newData,$otherDataArr); 1569 1570 /** 1571 * Write the thumbnail back to the exif section 1572 * Dont know if this works - 1573 */ 1574 /** 1575 if($this->thumbnail) { 1576 echo "Thumnail Size:".count($this->ImageInfo["ThumbnailPointer"]); 1577 1578 $tmpTag = hexdec(); 1579 // tag 1580 $newData[] = chr($tmpTag >> 8); 1581 $newData[] = chr($tmpTag); 1582 1583 // format 1584 $fmt = $this->getFormat($tag); 1585 $newData[] = chr($fmt >> 8); 1586 $newData[] = chr($fmt); 1587 1588 //components 1589 $chars = preg_split('//', $val, -1, PREG_SPLIT_NO_EMPTY); 1590 $ByteCount = count($chars); 1591 1592 $Components = $ByteCount / $this->BytesPerFormat[$fmt]; 1593 1594 $newData[] = chr($Components >> 32); 1595 $newData[] = chr($Components >> 16); 1596 $newData[] = chr($Components >> 8); 1597 $newData[] = chr($Components); 1598 1599 1600 //$newData = array_merge($newData,$chars); 1601 1602 $newData = array_merge($newData,$this->ImageInfo["ThumbnailPointer"]); 1603 $totalLength += count($this->ImageInfo["ThumbnailPointer"]); 1604 } 1605 */ 1606 1607 $totalLength += 2; 1608 $newData[0] = chr($totalLength >> 8); 1609 $newData[1] = chr($totalLength); 1610 1611 $newData[16] = chr($totalElements >> 8); 1612 $newData[17] = chr($totalElements); 1613 1614 $this->sections[$this->exifSection]["data"] = $newData; 1615 $this->sections[$this->exifSection]["size"] = $totalLength; 1616 1617 } 1618 1619 /** 1620 * Searched for the tag specified in the sections list 1621 * 1622 * @param hex Tag to search for 1623 * 1624 * @return int 1625 -1 - Tag not found 1626 */ 1627 function findMarker($marker) { 1628 for($i=0;$i<$this->currSection;$i++) { 1629 if($this->sections[$i]["type"] == $marker) { 1630 return $i; 1631 } 1632 } 1633 return -1; 1634 } 1635 1636 /** 1637 * Adds comment to the image. 1638 * NOTE: Will have to call writeExif for the comments and 1639 * other data to be written back to image. 1640 * @param string Commnent as string 1641 * 1642 */ 1643 function addComment($comment) { 1644 1645 /** check if comments already exists! */ 1646 $commentSection = $this->findMarker(M_COM); 1647 if($commentSection == -1) { 1648 // make 3rd element as comment section - Push-up all elements 1649 for($i=$this->currSection;$i>2;$i--) { 1650 $this->sections[$i]["type"] = $this->sections[$i-1]["type"]; 1651 $this->sections[$i]["data"] = $this->sections[$i-1]["data"]; 1652 $this->sections[$i]["size"] = $this->sections[$i-1]["size"]; 1653 } 1654 $this->currSection++; 1655 $commentSection = 2; 1656 } 1657 1658 $data[0] = 0; // dummy data 1659 $data[1] = 0; // dummy data 1660 1661 $chars = preg_split('//', $comment, -1, PREG_SPLIT_NO_EMPTY); 1662 $data = array_merge($data,$chars); 1663 1664 $this->sections[$commentSection]["size"] = count($data); 1665 1666 $data[0] = chr($this->sections[$commentSection]["size"] >> 8); 1667 $data[1] = chr($this->sections[$commentSection]["size"]); 1668 1669 $this->sections[$commentSection]["type"] = M_COM; 1670 $this->sections[$commentSection]["data"] = $data; 1671 } 1672 1673 /** 1674 * Return the format of data of any tag. 1675 * 1676 * @param hex Tag whose format has to looked for 1677 * 1678 * @return int Return the format as int 1679 * 1680 */ 1681 function getFormat($tag) { 1682 global $FMT_BYTE_ARRAY $FMT_STRING_ARRAY $FMT_USHORT_ARRAY $FMT_ULONG_ARRAY 1683 $FMT_URATIONAL_ARRAY $FMT_SBYTE_ARRAY $FMT_UNDEFINED_ARRAY $FMT_SSHORT_ARRAY 1684 $FMT_SLONG_ARRAY $FMT_SRATIONAL_ARRAY $FMT_SINGLE_ARRAY $FMT_DOUBLE_ARRAY 1685 1686 if(in_array($tag,$FMT_BYTE_ARRAY)) { 1687 return FMT_BYTE; 1688 } else if(in_array($tag,$FMT_STRING_ARRAY)) { 1689 return FMT_STRING; 1690 } else if(in_array($tag,$FMT_USHORT_ARRAY)) { 1691 return FMT_USHORT; 1692 } else if(in_array($tag,$FMT_ULONG_ARRAY)) { 1693 return FMT_ULONG; 1694 } else if(in_array($tag,$FMT_URATIONAL_ARRAY)) { 1695 return FMT_URATIONAL; 1696 } else if(in_array($tag,$FMT_SBYTE_ARRAY)) { 1697 return FMT_SBYTE; 1698 } else if(in_array($tag,$FMT_UNDEFINED_ARRAY)) { 1699 return FMT_UNDEFINED; 1700 } else if(in_array($tag,$FMT_SSHORT_ARRAY)) { 1701 return FMT_SSHORT; 1702 } else if(in_array($tag,$FMT_SRATIONAL_ARRAY)) { 1703 return FMT_SRATIONAL; 1704 } else if(in_array($tag,$FMT_SINGLE_ARRAY)) { 1705 return FMT_SINGLE; 1706 } else if(in_array($tag,$FMT_DOUBLE_ARRAY)) { 1707 return FMT_DOUBLE; 1708 } 1709 } 1710 1711 /** 1712 * Returns the exif information stored 1713 * 1714 */ 1715 function getExif() { 1716 if($this->exifSection > -1) { 1717 return $this->sections[$this->exifSection]["data"]; 1718 } 1719 /** Exif data does not exists */ 1720 return -1; 1721 } 1722 1723 /** 1724 * Returns the exif information stored 1725 * 1726 * @param string Exif Data to be added. 1727 * 1728 * NOTE: This function will blindly replace any existing EXIF data 1729 */ 1730 function addExif($exifData) { 1731 $exifSection = $this->findMarker(M_EXIF); 1732 if($exifSection == -1) { 1733 // make 3rd element as comment section - Push-up all elements 1734 for($i=$this->currSection;$i>2;$i--) { 1735 $this->sections[$i]["type"] = $this->sections[$i-1]["type"]; 1736 $this->sections[$i]["data"] = $this->sections[$i-1]["data"]; 1737 $this->sections[$i]["size"] = $this->sections[$i-1]["size"]; 1738 } 1739 $exifSection = 2; 1740 $this->currSection++; 1741 } 1742 1743 $this->sections[$exifSection]["type"] = M_EXIF; 1744 $this->sections[$exifSection]["data"] = $exifData; 1745 $this->sections[$exifSection]["size"] = strlen($exifData); 1746 } 1747 1748 /** 1749 * Write the whole image back into a file. 1750 * This function does not write back to the same file. 1751 * You need to specify a filename 1752 * 1753 * @param string filename to save the JPEG content to 1754 */ 1755 function writeImage($file) { 1756 1757 $file = trim($file); 1758 if(empty($file)) { 1759 $this->errno = 3; 1760 $this->errstr = "File name not provided!"; 1761 debug($this->errstr,1); 1762 } 1763 1764 $fp = fopen($file,"wb"); 1765 1766 /** Initial static jpeg marker. */ 1767 fwrite($fp,chr(0xff)); 1768 fwrite($fp,chr(0xd8)); 1769 1770 if ($this->sections[0]["type"] != M_EXIF && $this->sections[0]["type"] != M_JFIF){ 1771 $JfifHead = array( 1772 chr(0xff), chr(M_JFIF), 1773 chr(0x00), chr(0x10), 'J' , 'F' , 'I' , 'F' , chr(0x00), chr(0x01), 1774 chr(0x01), chr(0x01), chr(0x01), chr(0x2C), chr(0x01), chr(0x2C), chr(0x00), chr(0x00) 1775 ); 1776 1777 fwrite($fp,implode("",$JfifHead)); 1778 } 1779 1780 /** write each section back into the file */ 1781 for($key=0;$key<$this->currSection-1;$key++) { 1782 if(!empty($this->sections[$key]["data"])) { 1783 fwrite($fp,chr(0xff)); 1784 fwrite($fp,chr($this->sections[$key]["type"])); 1785 /** 1786 dat acan be array as well as string. Check the data-type of data. 1787 If it is an array then convert it to string. 1788 */ 1789 if(is_array($this->sections[$key]["data"])) { 1790 $this->sections[$key]["data"] = implode("",$this->sections[$key]["data"]); 1791 } 1792 fwrite($fp,$this->sections[$key]["data"]); 1793 } 1794 } 1795 // Write the remaining image data. 1796 if(is_array($this->sections[$key]["data"])) { 1797 $this->sections[$key]["data"] = implode("",$this->sections[$key]["data"]); 1798 } 1799 fwrite($fp,$this->sections[$key]["data"]); 1800 fclose($fp); 1801 } 1802 } // end of class 1803 ?>
|