* Show exif data after the image not before it.
[lhc/web/wiklou.git] / includes / exifReader.inc
1 <?php
2 /**
3 * PHP Class to read EXIF information
4 * that most of the digital camera produce
5 *
6 * This class is based on jhead (in C) by Matthias Wandel
7 *
8 * Vinay Yadav < vinay@vinayras.com >
9 Concept by Tarique Sani < tarique@sanisoft.com >
10
11 * http://www.vinayras.com/project/phpexifrw.php
12 * http://www.sanisoft.com/phpexifrw/
13 *
14 * For more information on EXIF
15 * http://www.exif.org/
16 *
17 * Features:
18 * - Read Exif Information
19 * - Extract and display emdedded thumbnails
20 *
21 * Tested With
22
23 - Sony
24 - Cybershot (Sony)
25 - DSC-D700
26 - PowerShotA5
27 - SANYO Electric Co.,Ltd
28 - SR6
29 - SX113
30 - OLYMPUS OPTICAL CO.,LTD
31 - C960Z,D460Z
32 - Canon
33 PowerShot A40 (Canon)
34 Canon DIGITAL IXUS
35 - RICOH
36 - Caplio RR30
37 - RDC-5300
38 - NIKON
39 - D100 (NIKON CORPORATION)
40 - E5700 (NIKON)
41 - E950
42 - CASIO QV-8000SX
43 - KODAK
44 - DC290 Zoom Digital Camera (V01.00) [Eastman Kodak Company]
45 - DC210 Zoom (V05.00) [Eastman Kodak Company]
46 - KODAK DC240 ZOOM DIGITAL CAMERA
47 - FujiFilm
48 DX10
49 FinePix40i
50 MX-1700ZOOM
51 *
52 *
53 */
54
55 /** * Start Of Frame N */
56 define("M_SOF0",0xC0);
57 /** * N indicates which compression process */
58 define("M_SOF1",0xC1);
59 /** * Only SOF0-SOF2 are now in common use */
60 define("M_SOF2",0xC2);
61 /** * */
62 define("M_SOF3",0xC3);
63 /** * NB: codes C4 and CC are NOT SOF markers */
64 define("M_SOF5",0xC5);
65 /** * */
66 define("M_SOF6",0xC6);
67 /** * */
68 define("M_SOF7",0xC7);
69 /** * */
70 define("M_SOF9",0xC9);
71 /** * */
72 define("M_SOF10",0xCA);
73 /** * */
74 define("M_SOF11",0xCB);
75 /** * */
76 define("M_SOF13",0xCD);
77 /** * */
78 define("M_SOF14",0xCE);
79 /** * */
80 define("M_SOF15",0xCF);
81 /** * Start Of Image (beginning of datastream) */
82 define("M_SOI",0xD8);
83 /** * End Of Image (end of datastream) */
84 define("M_EOI",0xD9);
85 /** * Start Of Scan (begins compressed data) */
86 define("M_SOS",0xDA);
87 /** * Jfif marker */
88 define("M_JFIF",0xE0);
89 /** * Exif marker */
90 define("M_EXIF",0xE1);
91 /** * Image Title -- */
92 define("M_COM",0xFE);
93
94 define("NUM_FORMATS","12");
95
96 /** * Tag Data Format */
97 define("FMT_BYTE","1");
98 /** * ASCII */
99 define("FMT_STRING","2");
100 /** * Short */
101 define("FMT_USHORT","3");
102 /** * Long */
103 define("FMT_ULONG","4");
104 /** * Rational */
105 define("FMT_URATIONAL","5");
106 /** * Byte */
107 define("FMT_SBYTE","6");
108 /** * Undefined */
109 define("FMT_UNDEFINED","7");
110 /** * Short */
111 define("FMT_SSHORT","8");
112 /** * Long */
113 define("FMT_SLONG","9");
114 /** * Rational */
115 define("FMT_SRATIONAL","10");
116 /** * Single */
117 define("FMT_SINGLE","11");
118 /** * Double */
119 define("FMT_DOUBLE","12");
120
121 /** * Exif IFD */
122 define("TAG_EXIF_OFFSET","0x8769");
123 /** * Interoperability tag */
124 define("TAG_INTEROP_OFFSET","0xa005");
125 /** * Image input equipment manufacturer */
126 define("TAG_MAKE","0x010F");
127 /** * Image input equipment model */
128 define("TAG_MODEL","0x0110");
129 /** * Orientation of image */
130 define("TAG_ORIENTATION","0x0112");
131 /** * Exposure Time */
132 define("TAG_EXPOSURETIME","0x829A");
133 /** * F Number */
134 define("TAG_FNUMBER","0x829D");
135 /** * Shutter Speed */
136 define("TAG_SHUTTERSPEED","0x9201");
137 /** * Aperture */
138 define("TAG_APERTURE","0x9202");
139 /** * Aperture */
140 define("TAG_MAXAPERTURE","0x9205");
141 /** * Lens Focal Length */
142 define("TAG_FOCALLENGTH","0x920A");
143 /** * The date and time when the original image data was generated. */
144 define("TAG_DATETIME_ORIGINAL","0x9003");
145 /** * User Comments */
146 define("TAG_USERCOMMENT","0x9286");
147 /** * subject Location */
148 define("TAG_SUBJECT_DISTANCE","0x9206");
149 /** * Flash */
150 define("TAG_FLASH","0x9209");
151 /** * Focal Plane X Resolution */
152 define("TAG_FOCALPLANEXRES","0xa20E");
153 /** * Focal Plane Resolution Units */
154 define("TAG_FOCALPLANEUNITS","0xa210");
155 /** * Image Width */
156 define("TAG_EXIF_IMAGEWIDTH","0xA002");
157 /** * Image Height */
158 define("TAG_EXIF_IMAGELENGTH","0xA003");
159 /** * Exposure Bias */
160 define("TAG_EXPOSURE_BIAS","0x9204");
161 /** * Light Source */
162 define("TAG_WHITEBALANCE","0x9208");
163 /** * Metering Mode */
164 define("TAG_METERING_MODE","0x9207");
165 /** * Exposure Program */
166 define("TAG_EXPOSURE_PROGRAM","0x8822");
167 /** * ISO Equivalent Speed Rating */
168 define("TAG_ISO_EQUIVALENT","0x8827");
169 /** * Compressed Bits Per Pixel */
170 define("TAG_COMPRESSION_LEVEL","0x9102");
171 /** * Thumbnail Start Offset */
172 define("TAG_THUMBNAIL_OFFSET","0x0201");
173 /** * Thumbnail Length */
174 define("TAG_THUMBNAIL_LENGTH","0x0202");
175 /** * Image Marker */
176 define("PSEUDO_IMAGE_MARKER",0x123);
177 /** * Max Image Title Length */
178 define("MAX_COMMENT",2000);
179
180 define("TAG_ARTIST","0x013B");
181 define("TAG_COPYRIGHT","0x8298");
182
183 //--------------------------------
184
185 define("TAG_IMAGE_WD","0x0100"); // image width
186 define("TAG_IMAGE_HT","0x0101"); // image height
187 define("TAG_IMAGE_BPS","0x0102"); // Bits Per sample
188
189 define("TAG_IMAGE_PHOTO_INT","0x0106"); // photometricinterpretation
190 define("TAG_IMAGE_SOFFSET","0x0111"); // stripoffsets
191
192 define("TAG_IMAGE_SPP","0x0115"); // Samples per pixel - 277
193 define("TAG_IMAGE_RPS","0x0116"); // RowsPerStrip - 278
194 define("TAG_IMAGE_SBC","0x0117"); // StripByteCounts - 279
195
196 define("TAG_IMAGE_P_CONFIG","0x011C"); // Planar Configuration - 284
197
198 define("TAG_IMAGE_DESC","0x010E"); // image title
199 define("TAG_X_RESOLUTION","0x011A"); // Image resolution in width direction
200 define("TAG_Y_RESOLUTION","0x011B"); // Image resolution in height direction
201 define("TAG_RESOLUTION_UNIT","0x0128"); // Unit of X and Y resolution
202 define("TAG_SOFTWARE","0x0131"); // Software used
203 define("TAG_FILE_MODDATE","0x0132"); // DateTime File change date and time
204 define("TAG_YCBCR_POSITIONING","0x0213"); // Y and C positioning
205 define("TAG_EXIF_VERSION","0x9000"); // Exif version
206 define("TAG_DATE_TIME_DIGITIZED","0x9004"); // Date and time of digital data
207 define("TAG_COMPONENT_CONFIG","0x9101"); // Component configuration
208 define("TAG_MAKER_NOTE","0x927C");
209 define("TAG_SUB_SEC_TIME","0x9290");
210 define("TAG_SUB_SEC_TIME_ORIG","0x9291");
211 define("TAG_SUB_SEC_TIME_DIGITIZED","0x9292");
212 define("TAG_FLASHPIX_VER","0xA000"); //FlashPixVersion
213 define("TAG_COLOR_SPACE","0xA001"); //ColorSpace
214 define("TAG_RELATED_SOUND_FILE","0xA004"); //Related audio file
215
216 define("TAG_GPS_LATITUDE_REF","0x0001"); //
217 define("TAG_GPS_LATITUDE","0x0002"); //
218
219 define("TAG_SENSING_METHOD","0xA217"); // SensingMethod
220
221 define("TAG_SOUCE_TYPE","0xA300"); // FileSource
222 define("TAG_SCENE_TYPE","0xA301"); // SceneType
223
224 define("TAG_CFA_PATTERN","0xA302"); // CFA Pattern
225
226 /** Tags in EXIF 2.2 Only */
227 define("TAG_COMPRESS_SCHEME","0x0103"); //
228 define("TAG_CUSTOM_RENDERED","0xA401"); // CustomRendered
229 define("TAG_EXPOSURE_MODE","0xA402"); // Exposure mode ExposureMode
230 define("TAG_WHITE_BALANCE","0xA403"); // White balance WhiteBalance
231 define("TAG_DIGITAL_ZOOM_RATIO","0xA404"); // Digital zoom ratio DigitalZoomRatio
232 define("TAG_FLENGTH_IN35MM","0xA405"); // Focal length in 35 mm film FocalLengthIn35mmFilm
233 define("TAG_SCREEN_CAP_TYPE","0xA406"); // Scene capture type SceneCaptureType
234 define("TAG_GAIN_CONTROL","0xA407"); //Gain control
235 define("TAG_CONTRAST","0xA408"); // Contrast
236 define("TAG_SATURATION","0xA409"); // Saturation
237 define("TAG_SHARPNESS","0xA40A"); // Sharpness
238 define("TAG_DEVICE_SETTING_DESC","0xA40B"); // SDevice settings description DeviceSettingDescription
239 define("TAG_DIST_RANGE","0xA40C"); //Subject distance range SubjectDistanceRange
240
241 define("TAG_FOCALPLANE_YRESOL","0xA20F");; //FocalPlaneYResolution
242 define("TAG_BRIGHTNESS","0x9203");; //Brightness
243 //--------------------------------
244 /** error Description */
245 /**
246 1 - File does not exists!
247 2 -
248 3 - Filename not provided
249
250 10 - too many padding bytes
251 11 - "invalid marker"
252 12 - Premature end of file?
253
254
255 51 - "Illegal subdirectory link"
256 52 - "NOT EXIF FORMAT"
257 53 - "Invalid Exif alignment marker.\n"
258 54 - "Invalid Exif start (1)"
259
260 */
261
262
263 /**
264 * PHP Class to read, write and transfer EXIF information
265 * that most of the digital camera produces
266 * Currenty it can only read JPEG file.
267 */
268 /**
269 * @author Vinay Yadav (vinayRas) < vinay@sanisoft.com >
270 *
271 * @todo Writing exif information to the file.
272 * @todo Add EXIF audio reading methods (I think it exists!)
273 * @todo Support of additional tags.
274 * @todo Handling Unicode character in UserComment tag of EXif Information.
275 *
276 * @version 0.5
277 * @licence http://opensource.org/licenses/lgpl-license.php GNU LGPL
278 */
279 class phpExifReader {
280
281 /***
282 * Array containg all Exif and JPEG image attributes
283 * into regular expressions for themselves.
284 * $ImageInfo[TAG] = TAG_VALUE;
285 *
286 * @var array
287 * @access private
288 *
289 */
290 var $ImageInfo = array();
291
292 var $MotorolaOrder = 0;
293 var $ExifImageWidth = 0; //
294 var $FocalplaneXRes = 0; //
295 var $FocalplaneUnits = 0; //
296 var $sections = array();
297 var $currSection = 0; /** Stores total number fo Sections */
298
299 var $BytesPerFormat = array(0,1,1,2,4,8,1,1,2,4,8,4,8);
300
301 var $ReadMode = array(
302 "READ_EXIF" => 1,
303 "READ_IMAGE" => 2,
304 "READ_ALL" => 3
305 );
306
307 var $ImageReadMode = 3; /** related to $RealMode arrays values */
308 var $file = ""; /** JPEG file to parse for EXIF data */
309 var $newFile = 1; /** flag to check if the current file has been parsed or not. */
310
311 var $thumbnail = ""; /* Name of thumbnail */
312 var $thumbnailURL = ""; /* */
313
314 var $exifSection = -1; // market the exif section index oout of all sections
315
316 var $errno = 0;
317 var $errstr = "";
318
319 var $debug = false;
320
321 // Caching ralated variables
322 var $caching = false; /* Should cacheing of image thumnails be allowed? */
323 var $cacheDir = ""; /* Checkout constructor for default path. */
324
325 /**
326 * Constructor
327 * @param string File name to be parsed.
328 *
329 */
330 function phpExifReader($file = "") {
331 $this->timeStart = $this->getmicrotime();
332 if(!empty($file)) {
333 $this->file = $file;
334 }
335
336 /**
337 * Initialize some variables. Avoid lots of errors with fulll error_reporting
338 */
339 $this->ExifImageLength = 0;
340 $this->ImageInfo['h']["resolutionUnit"] = 0;
341
342 $this->ImageInfo[TAG_MAXAPERTURE] = 0;
343 $this->ImageInfo[TAG_ISO_EQUIVALENT] = 0;
344 $this->ImageInfo[TAG_ORIENTATION] = 0;
345
346 $this->ThumbnailSize = 0;
347
348 if($this->caching) {
349 $this->cacheDir = dirname(__FILE__)."/.cache_thumbs";
350
351 /**
352 * If Cache directory does not exists then attempt to create it.
353 */
354 if(!is_dir($this->cacheDir)) {
355 mkdir($this->cacheDir);
356 }
357
358 // Prepare the ame of thumbnail
359 if(is_dir($this->cacheDir)) {
360 $this->thumbnail = $this->cacheDir."/".basename($this->file);
361 $this->thumbnailURL = ".cache_thumbs/".basename($this->file);
362 }
363 }
364
365 /** check if file exists! */
366 if(!file_exists($this->file)) {
367 $this->errno = 1;
368 $this->errstr = "File '".$this->file."' does not exists!";
369 }
370 $this->currSection = 0;
371
372 $this->processFile();
373 }
374
375 /**
376 * Show Debugging information
377 *
378 * @param string Debugging message to display
379 * @param int Type of error (0 - Warning, 1 - Error)
380 * @return void
381 *
382 */
383 function debug($str,$TYPE = 0,$file="",$line=0) {
384 if($this->debug) {
385 echo "<br>[$file:$line:".($this->getDiffTime())."]$str";
386 flush();
387 if($TYPE == 1) {
388 exit;
389 }
390 }
391 }
392
393 /**
394 * Processes the whole file.
395 *
396 */
397 function processFile() {
398 /** dont reparse the whole file. */
399 if(!$this->newFile) return true;
400
401 if(!file_exists($this->file)) {
402 echo "<br>Error: File ".($this->file)."does not exists!";
403 exit;
404 }
405
406 $this->debug("Stating Processing of ".$this->newFile,0,__FILE__,__LINE__);
407
408 $i = 0; $exitAll = 0;
409 /** Open the JPEG in binary safe reading mode */
410 $fp = fopen($this->file,"rb");
411
412 $this->ImageInfo["h"]["FileName"] = $this->file;
413 $this->ImageInfo["h"]["FileSize"] = filesize($this->file); /** Size of the File */
414 $this->ImageInfo["h"]["FileDateTime"] = filectime($this->file); /** File node change time */
415
416 /** check whether jped image or not */
417 $a = fgetc($fp);
418 if (ord($a) != 0xff || ord(fgetc($fp)) != M_SOI){
419 $this->debug("Not a JPEG FILE",1);
420 $this->errorno = 1;
421 $this->errorstr = "File '".$this->file."' is not a JPEG File!";
422 }
423 $tmpTestLevel = 0;
424 /** Examines each byte one-by-one */
425 while(!feof($fp)) {
426 $data = array();
427 for ($a=0;$a<7;$a++){
428 $marker = fgetc($fp);
429 if (ord($marker) != 0xff) break;
430 if ($a >= 6){
431 $this->errno = 10;
432 $this->errstr = "too many padding bytes!";
433 $this->debug($this->errstr,1);
434 return false;
435 }
436 }
437
438 if (ord($marker) == 0xff){
439 // 0xff is legal padding, but if we get that many, something's wrong.
440 $this->errno = 10;
441 $this->errstr = "too many padding bytes!";
442 $this->debug($this->errstr,1);
443 }
444
445 $marker = ord($marker);
446 $this->sections[$this->currSection]["type"] = $marker;
447
448 // Read the length of the section.
449 $lh = ord(fgetc($fp));
450 $ll = ord(fgetc($fp));
451
452 $itemlen = ($lh << 8) | $ll;
453
454 if ($itemlen < 2){
455 $this->errno = 11;
456 $this->errstr = "invalid marker";
457 $this->debug($this->errstr,1);
458 }
459 $this->sections[$this->currSection]["size"] = $itemlen;
460
461 $tmpDataArr = array(); /** Temporary Array */
462
463 $tmpStr = fread($fp,$itemlen-2);
464 /*
465 $tmpDataArr[] = chr($lh);
466 $tmpDataArr[] = chr($ll);
467
468 $chars = preg_split('//', $tmpStr, -1, PREG_SPLIT_NO_EMPTY);
469 $tmpDataArr = array_merge($tmpDataArr,$chars);
470
471 $data = $tmpDataArr;
472 */
473 $data = chr($lh).chr($ll).$tmpStr;
474
475 $this->sections[$this->currSection]["data"] = $data;
476
477 $this->debug("<hr><h1>".$this->currSection.":</h1>");
478 //print_r($data);
479 $this->debug("<hr>");
480
481 //if(count($data) != $itemlen) {
482 if(strlen($data) != $itemlen) {
483 $this->errno = 12;
484 $this->errstr = "Premature end of file?";
485 $this->debug($this->errstr,1);
486 }
487
488 $this->currSection++; /** */
489
490 switch($marker) {
491 case M_SOS:
492 $this->debug("<br>Found '".M_SOS."' Section, Prcessing it... <br>");;
493 // If reading entire image is requested, read the rest of the data.
494 if ($this->ImageReadMode & $this->ReadMode["READ_IMAGE"]){
495 // Determine how much file is left.
496 $cp = ftell($fp);
497 fseek($fp,0, SEEK_END);
498 $ep = ftell($fp);
499 fseek($fp, $cp, SEEK_SET);
500
501 $size = $ep-$cp;
502 $got = fread($fp, $size);
503
504 $this->sections[$this->currSection]["data"] = $got;
505 $this->sections[$this->currSection]["size"] = $size;
506 $this->sections[$this->currSection]["type"] = PSEUDO_IMAGE_MARKER;
507 $this->currSection++;
508 $HaveAll = 1;
509 $exitAll =1;
510 }
511 $this->debug("<br>'".M_SOS."' Section, PROCESSED<br>");
512 break;
513 case M_COM: // Comment section
514 $this->debug("<br>Found '".M_COM."'(Comment) Section, Processing<br>");
515 $this->process_COM($data, $itemlen);
516 $this->debug("<br>'".M_COM."'(Comment) Section, PROCESSED<br>");
517
518 $tmpTestLevel++;
519 break;
520 case M_SOI:
521 $this->debug(" <br> === START OF IMAGE =====<br>");
522 break;
523 case M_EOI:
524 $this->debug(" <br>=== END OF IMAGE =====<br> ");
525 break;
526 case M_JFIF:
527 // Regular jpegs always have this tag, exif images have the exif
528 // marker instead, althogh ACDsee will write images with both markers.
529 // this program will re-create this marker on absence of exif marker.
530 // hence no need to keep the copy from the file.
531 //echo " <br> === M_JFIF =====<br>";
532 $this->sections[--$this->currSection]["data"] = "";
533 break;
534 case M_EXIF:
535 // Seen files from some 'U-lead' software with Vivitar scanner
536 // that uses marker 31 for non exif stuff. Thus make sure
537 // it says 'Exif' in the section before treating it as exif.
538 $this->debug("<br>Found '".M_EXIF."'(Exif) Section, Proccessing<br>");
539 $this->exifSection = $this->currSection-1;
540 if (($this->ImageReadMode & $this->ReadMode["READ_EXIF"]) && ($data[2].$data[3].$data[4].$data[5]) == "Exif"){
541 $this->process_EXIF($data, $itemlen);
542 }else{
543 // Discard this section.
544 $this->sections[--$this->currSection]["data"] = "";
545 }
546 $this->debug("<br>'".M_EXIF."'(Exif) Section, PROCESSED<br>");
547 $tmpTestLevel++;
548 break;
549 case M_SOF0:
550 case M_SOF1:
551 case M_SOF2:
552 case M_SOF3:
553 case M_SOF5:
554 case M_SOF6:
555 case M_SOF7:
556 case M_SOF9:
557 case M_SOF10:
558 case M_SOF11:
559 case M_SOF13:
560 case M_SOF14:
561 case M_SOF15:
562 $this->debug("<br>Found M_SOFn Section, Processing<br>");
563 $this->process_SOFn($data,$marker);
564 $this->debug("<br>M_SOFn Section, PROCESSED<br>");
565 break;
566 default:
567 $this->debug("DEFAULT: Jpeg section marker 0x$marker x size $itemlen\n");
568 }
569 $i++;
570 if($exitAll == 1) break;
571 //if($tmpTestLevel == 2) break;
572 }
573 fclose($fp);
574 $this->newFile = 0;
575 }
576
577 /**
578 * Changing / Assiging new file
579 * @param string JPEG file to process
580 *
581 */
582 function assign($file) {
583
584 if(!empty($file)) {
585 $this->file = $file;
586 }
587
588 /** check for existance of file! */
589 if(!file_exists($this->file)) {
590 $this->errorno = 1;
591 $this->errorstr = "File '".$this->file."' does not exists!";
592 }
593 $this->newFile = 1;
594 }
595
596 /**
597 * Process SOFn section of Image
598 * @param array An array containing whole section.
599 * @param hex Marker to specify the type of section.
600 *
601 */
602 function process_SOFn($data,$marker) {
603 $data_precision = 0;
604 $num_components = 0;
605
606 $data_precision = ord($data[2]);
607
608 if($this->debug) {
609 print("Image Dimension Calculation:");
610 print("((ord($data[3]) << 8) | ord($data[4]));");
611 }
612 $this->ImageInfo["h"]["Height"] = ((ord($data[3]) << 8) | ord($data[4]));
613 $this->ImageInfo["h"]["Width"] = ((ord($data[5]) << 8) | ord($data[6]));
614
615 $num_components = ord($data[7]);
616
617 if ($num_components == 3){
618 $this->ImageInfo["h"]["IsColor"] = 1;
619 }else{
620 $this->ImageInfo["h"]["IsColor"] = 0;
621 }
622
623 $this->ImageInfo["h"]["Process"] = $marker;
624 $this->debug("JPEG image is ".$this->ImageInfo["h"]["Width"]." * ".$this->ImageInfo["h"]["Height"].", $num_components color components, $data_precision bits per sample\n");
625 }
626
627 /**
628 * Process Comments
629 * @param array Section data
630 * @param int Length of the section
631 *
632 */
633 function process_COM($data,$length) {
634 if ($length > MAX_COMMENT) $length = MAX_COMMENT;
635 /** Truncate if it won't fit in our structure. */
636
637 $nch = 0; $Comment = "";
638 for ($a=2;$a<$length;$a++){
639 $ch = $data[$a];
640 if ($ch == '\r' && $data[$a+1] == '\n') continue; // Remove cr followed by lf.
641
642 $Comment .= $ch;
643 }
644 //$this->ImageInfo[M_COM] = $Comment;
645 $this->ImageInfo["h"]["imageComment"] = $Comment;
646 $this->debug("COM marker comment: $Comment\n");
647 }
648 /**
649 * Process one of the nested EXIF directories.
650 * @param string All directory information
651 * @param string whole Section
652 * @param int Length of exif section
653 *
654 */
655 function ProcessExifDir($DirStart, $OffsetBase, $ExifLength) {
656
657 $NumDirEntries = 0;
658 $ValuePtr = array();
659
660 $NumDirEntries = $this->Get16u($DirStart[0],$DirStart[1]);
661
662
663 $this->debug("<br>Directory with $NumDirEntries entries\n");
664
665 for ($de=0;$de<$NumDirEntries;$de++){
666 //$DirEntry = array_slice($DirStart,2+12*$de);
667 $DirEntry = substr($DirStart,2+12*$de);
668
669 $Tag = $this->Get16u($DirEntry[0],$DirEntry[1]);
670 $Format = $this->Get16u($DirEntry[2],$DirEntry[3]);
671 $Components = $this->Get32u($DirEntry[4],$DirEntry[5],$DirEntry[6],$DirEntry[7]);
672
673 /**
674 if ((Format-1) >= NUM_FORMATS) {
675 // (-1) catches illegal zero case as unsigned underflows to positive large.
676 ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
677 continue;
678 }
679 */
680
681 $ByteCount = $Components * $this->BytesPerFormat[$Format];
682
683 if ($ByteCount > 4){
684 $OffsetVal = $this->Get32u($DirEntry[8],$DirEntry[9],$DirEntry[10],$DirEntry[11]);
685 if ($OffsetVal+$ByteCount > $ExifLength){
686 $this->debug("Illegal value pointer($OffsetVal) for tag $Tag",1);
687 }
688 //$ValuePtr = array_slice($OffsetBase,$OffsetVal);
689 $ValuePtr = substr($OffsetBase,$OffsetVal);
690 } else {
691 //$ValuePtr = array_slice($DirEntry,8);
692 $ValuePtr = substr($DirEntry,8);
693 }
694
695 switch($Tag){
696
697 case TAG_MAKE:
698 $this->ImageInfo["h"]["make"] = substr($ValuePtr,0,$ByteCount);
699 break;
700
701 case TAG_MODEL:
702 $this->ImageInfo["h"]["model"] = substr($ValuePtr,0,$ByteCount);
703 break;
704
705 case TAG_DATETIME_ORIGINAL:
706 $this->ImageInfo[TAG_DATETIME_ORIGINAL] = substr($ValuePtr,0,$ByteCount);
707 $this->ImageInfo["h"]["DateTime"] = substr($ValuePtr,0,$ByteCount);
708 break;
709
710 case TAG_USERCOMMENT:
711 // Olympus has this padded with trailing spaces. Remove these first.
712 for ($a=$ByteCount;;){
713 $a--;
714 if ($ValuePtr[$a] == ' '){
715 //$ValuePtr[$a] = '\0';
716 } else {
717 break;
718 }
719 if ($a == 0) break;
720 }
721
722 // Copy the comment
723 if (($ValuePtr[0].$ValuePtr[1].$ValuePtr[2].$ValuePtr[3].$ValuePtr[4]) == "ASCII"){
724 for ($a=5;$a<10;$a++){
725 $c = $ValuePtr[$a];
726 if ($c != '\0' && $c != ' '){
727 $tmp = substr($ValuePtr,0,$ByteCount);
728 break;
729 }
730 }
731 } else if (($ValuePtr[0].$ValuePtr[1].$ValuePtr[2].$ValuePtr[3].$ValuePtr[4].$ValuePtr[5].$ValuePtr[6]) == "Unicode"){
732 $tmp = substr($ValuePtr,0,$ByteCount);
733 // * Handle Unicode characters here...
734 } else {
735 //$this->ImageInfo[TAG_USERCOMMENT] = implode("",array_slice($ValuePtr,0,$ByteCount));
736 $tmp = substr($ValuePtr,0,$ByteCount);
737 }
738 $this->ImageInfo['h']["exifComment"] = $tmp;
739 break;
740
741 case TAG_ARTIST:
742 $this->ImageInfo['h']["artist"] = substr($ValuePtr,0,$ByteCount);
743 break;
744
745 case TAG_COPYRIGHT:
746 $this->ImageInfo['h']["copyright"] = htmlentities(substr($ValuePtr,0,$ByteCount));
747 break;
748
749 case TAG_FNUMBER:
750 // Simplest way of expressing aperture, so I trust it the most.
751 // (overwrite previously computd value if there is one)
752 $tmp = $this->ConvertAnyFormat(substr($ValuePtr,0), $Format);
753 $this->ImageInfo['h']["fnumber"] = sprintf("f/%3.1f",(double)$tmp[0]);
754 break;
755
756 case TAG_APERTURE:
757 case TAG_MAXAPERTURE:
758 // More relevant info always comes earlier, so only use this field if we don't
759 // have appropriate aperture information yet.
760 if (!isset($this->ImageInfo['h']["aperture"])){
761 $tmpArr = $this->ConvertAnyFormat($ValuePtr, $Format);
762 $this->ImageInfo['h']["aperture"] = exp($tmpArr[0]*log(2)*0.5);
763 }
764 break;
765
766 case TAG_FOCALLENGTH:
767 // Nice digital cameras actually save the focal length as a function
768 // of how farthey are zoomed in.
769 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
770 $this->ImageInfo['h']["focalLength"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
771 if (isset($this->ImageInfo['h']["CCDWidth"])){
772 $this->ImageInfo['h']["focalLength"] .= sprintf("(35mm equivalent: %dmm)",(int)($tmp[0]/$this->ImageInfo['h']["CCDWidth"]*36 + 0.5));
773 }
774 break;
775
776 case TAG_SUBJECT_DISTANCE:
777 // Inidcates the distacne the autofocus camera is focused to.
778 // Tends to be less accurate as distance increases.
779 //$this->ImageInfo["h"]["Distance"] = $this->ConvertAnyFormat($ValuePtr, $Format);
780 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
781 $this->ImageInfo['h']["Distance"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
782 if ($this->ImageInfo['h']["Distance"] < 0){
783 $this->ImageInfo['h']["focusDistance"] = "Infinite";
784 } else {
785 $this->ImageInfo['h']["focusDistance"] = sprintf("%4.2fm",(double)$this->ImageInfo['h']["Distance"]);
786 }
787
788
789 break;
790
791 case TAG_EXPOSURETIME:
792 // Simplest way of expressing exposure time, so I trust it most.
793 // (overwrite previously computd value if there is one)
794 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
795 $this->ImageInfo['h']["exposureTime"] = sprintf("%6.3f s (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
796 if ($tmp[0] <= 0.5){
797 $this->ImageInfo['h']["exposureTime"] .= sprintf(" (1/%d)",(int)(0.5 + 1/$tmp[0]));
798 }
799 break;
800
801 case TAG_SHUTTERSPEED:
802 // More complicated way of expressing exposure time, so only use
803 // this value if we don't already have it from somewhere else.
804 if (isset($this->ImageInfo[TAG_EXPOSURETIME]) && $this->ImageInfo[TAG_EXPOSURETIME] == 0){
805 $sp = $this->ConvertAnyFormat($ValuePtr, $Format);
806 $this->ImageInfo[TAG_SHUTTERSPEED] = (1/exp($sp[0]*log(2)));
807 }
808 break;
809
810 case TAG_FLASH:
811 $this->ImageInfo["h"]["flashUsed"] = "No";
812 if ($this->ConvertAnyFormat($ValuePtr, $Format) & 7){
813 $this->ImageInfo["h"]["flashUsed"] = "Yes";
814 }
815 break;
816
817 case TAG_ORIENTATION:
818 $this->ImageInfo[TAG_ORIENTATION] = $this->ConvertAnyFormat($ValuePtr, $Format);
819 if ($this->ImageInfo[TAG_ORIENTATION] < 1 || $this->ImageInfo[TAG_ORIENTATION] > 8){
820 $this->debug(sprintf("Undefined rotation value %d", $this->ImageInfo[TAG_ORIENTATION], 0),1);
821 $this->ImageInfo[TAG_ORIENTATION] = 0;
822 }
823 break;
824
825 case TAG_EXIF_IMAGELENGTH:
826 // * Image height
827 $a = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
828 if ($this->ExifImageLength < $a) $this->ExifImageLength = $a;
829 $this->ImageInfo[TAG_EXIF_IMAGELENGTH] = $this->ExifImageLength;
830 $this->ImageInfo["h"]["Height"] = $this->ExifImageLength;
831 break;
832 case TAG_EXIF_IMAGEWIDTH:
833 // Use largest of height and width to deal with images that have been
834 // rotated to portrait format.
835 $a = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
836 if ($this->ExifImageWidth < $a) $this->ExifImageWidth = $a;
837 $this->ImageInfo[TAG_EXIF_IMAGEWIDTH] = $this->ExifImageWidth;
838 $this->ImageInfo["h"]["Width"] = $this->ExifImageWidth;
839
840 break;
841
842 case TAG_FOCALPLANEXRES:
843 $this->FocalplaneXRes = $this->ConvertAnyFormat($ValuePtr, $Format);
844 $this->FocalplaneXRes = $this->FocalplaneXRes[0];
845 $this->ImageInfo[TAG_FOCALPLANEXRES] = $this->FocalplaneXRes[0];
846 break;
847
848 case TAG_FOCALPLANEUNITS:
849 switch($this->ConvertAnyFormat($ValuePtr, $Format)){
850 case 1: $this->FocalplaneUnits = 25.4; break; // inch
851 case 2:
852 // According to the information I was using, 2 means meters.
853 // But looking at the Cannon powershot's files, inches is the only
854 // sensible value.
855 $this->FocalplaneUnits = 25.4;
856 break;
857
858 case 3: $this->FocalplaneUnits = 10; break; // centimeter
859 case 4: $this->FocalplaneUnits = 1; break; // milimeter
860 case 5: $this->FocalplaneUnits = .001; break; // micrometer
861 }
862 $this->ImageInfo[TAG_FOCALPLANEUNITS] = $this->FocalplaneUnits;
863 break;
864
865 // Remaining cases contributed by: Volker C. Schoech (schoech@gmx.de)
866
867 case TAG_EXPOSURE_BIAS:
868 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
869 $this->ImageInfo['h']["exposureBias"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
870 break;
871
872 case TAG_WHITEBALANCE:
873 $tmp = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
874 $tmpArr = array("1"=>"Sunny","2"=>"fluorescent","3"=>"incandescent");
875 $this->ImageInfo['h']["whiteBalance"] =
876 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Cloudy");
877 break;
878
879 case TAG_METERING_MODE:
880 $tmp = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
881
882 $tmpArr = array("2"=>"center weight","3"=>"spot","5"=>"matrix");
883 $this->ImageInfo['h']["meteringMode"] =
884 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
885 break;
886
887 case TAG_EXPOSURE_PROGRAM:
888 $tmp = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
889 $tmpArr = array("2"=>"program (auto)","3"=>"aperture priority (semi-auto)","4"=>"shutter priority (semi-auto)");
890 $this->ImageInfo['h']["exposure"] =
891 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
892
893 break;
894
895 case TAG_ISO_EQUIVALENT:
896 $tmp = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
897 if ( $tmp < 50 ) $tmp *= 200;
898 $this->ImageInfo['h']["isoEquiv"] = sprintf("%2d",(int)$tmp);
899 break;
900
901 case TAG_COMPRESSION_LEVEL:
902 $tmp = (int) $this->ConvertAnyFormat($ValuePtr, $Format);
903 $tmpArr = array("1"=>"Basic","2"=>"Normal","4"=>"Fine");
904 $this->ImageInfo['h']["jpegQuality"] =
905 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
906 break;
907
908 case TAG_THUMBNAIL_OFFSET:
909 $this->ThumbnailOffset = $this->ConvertAnyFormat($ValuePtr, $Format);
910 $this->DirWithThumbnailPtrs = $DirStart;
911 break;
912
913 case TAG_THUMBNAIL_LENGTH:
914 $this->ThumbnailSize = $this->ConvertAnyFormat($ValuePtr, $Format);
915 $this->ImageInfo[TAG_THUMBNAIL_LENGTH] = $this->ThumbnailSize;
916 break;
917
918 //----------------------------------------------
919 case TAG_IMAGE_DESC:
920 $this->ImageInfo['h']["imageDesc"] = substr($ValuePtr,0,$ByteCount);
921 break;
922 case TAG_X_RESOLUTION:
923 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
924 $this->ImageInfo['h']["xResolution"] = sprintf("%4.2f (%d/%d) %s",(double)$tmp[0],$tmp[1][0],$tmp[1][1],$this->ImageInfo['h']["resolutionUnit"]);
925 break;
926 case TAG_Y_RESOLUTION:
927 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
928 $this->ImageInfo['h']["yResolution"] = sprintf("%4.2f (%d/%d) %s",(double)$tmp[0],$tmp[1][0],$tmp[1][1],$this->ImageInfo['h']["resolutionUnit"]);
929 break;
930 case TAG_RESOLUTION_UNIT:
931 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
932 $tmpArr = array("2"=>"Inches","3"=>"Centimeters");
933
934 $this->ImageInfo['h']["resolutionUnit"] =
935 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
936 break;
937 case TAG_SOFTWARE:
938 $this->ImageInfo['h']["software"] = substr($ValuePtr,0,$ByteCount);
939 break;
940 case TAG_FILE_MODDATE;
941 $this->ImageInfo['h']["fileModifiedDate"] = substr($ValuePtr,0,$ByteCount);
942 break;
943 case TAG_YCBCR_POSITIONING:
944 $this->ImageInfo['h']["YCbCrPositioning"] = $this->ConvertAnyFormat($ValuePtr, $Format);
945 break;
946 case TAG_EXIF_VERSION:
947 $this->ImageInfo['h']["exifVersion"] = substr($ValuePtr,0,$ByteCount);
948 break;
949 case TAG_DATE_TIME_DIGITIZED:
950 $this->ImageInfo['h']["dateTimeDigitized"] = substr($ValuePtr,0,$ByteCount);
951 break;
952 case TAG_COMPONENT_CONFIG: // need more tests for this
953 $tmp = (int)$this->ConvertAnyFormat($ValuePtr, $Format);
954
955 $tmpArr = array("0"=>"Does Not Exists","1"=>"Y","2"=>"Cb","3"=>"Cr","4"=>"R","5"=>"G","6"=>"B");
956
957 if(strlen($tmp) < 4 ) {
958 $this->ImageInfo['h']["componentConfig"] = $tmpArr["0"];
959 } else {
960 for($i=0;$i<strlen($tmp);$i++) {
961 if($tmp["$i"] != 0) {
962 $this->ImageInfo['h']["componentConfig"] .= $tmpArr[$tmp["$i"]];
963 }
964 }
965 }
966 break;
967 case TAG_MAKER_NOTE:
968 //$this->ImageInfo['h']["makerNote"] = substr($ValuePtr,0,$ByteCount);
969 $this->ImageInfo['h']["makerNote"] = "NOT IMPLEMENTED";
970 break;
971 case TAG_SUB_SEC_TIME:
972 $this->ImageInfo['h']["subSectionTime"] = substr($ValuePtr,0,$ByteCount);
973 break;
974 case TAG_SUB_SEC_TIME_ORIG:
975 $this->ImageInfo['h']["subSectionTimeOriginal"] = substr($ValuePtr,0,$ByteCount);
976 break;
977 case TAG_SUB_SEC_TIME_DIGITIZED:
978 $this->ImageInfo['h']["subSectionTimeDigtized"] = substr($ValuePtr,0,$ByteCount);
979 break;
980 case TAG_FLASHPIX_VER:
981 $this->ImageInfo['h']["flashpixVersion"] = substr($ValuePtr,0,$ByteCount);
982 break;
983 case TAG_COLOR_SPACE:
984 $this->ImageInfo['h']["colorSpace"] = substr($ValuePtr,0,$ByteCount);
985 break;
986 case TAG_RELATED_SOUND_FILE:
987 $this->ImageInfo['h']["relatedSoundFile"] = substr($ValuePtr,0,$ByteCount);
988 break;
989 case TAG_GPS_LATITUDE_REF:
990 $this->ImageInfo['h']["GPSLatitudeRef"] = substr($ValuePtr,0,$ByteCount);
991 break;
992 case TAG_GPS_LATITUDE:
993 $this->ImageInfo['h']["GPSLatitude"] = substr($ValuePtr,0,$ByteCount);
994 break;
995 case TAG_SENSING_METHOD:
996 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
997 $tmpArr = array("1"=>"Not Defined","2"=>"One-chip color area sensor","3"=>"Two-chip color area sensor",
998 "4"=>"Three -chip color area sensor","5"=>"Color sequential area sensor",
999 "6"=>"Trilinear sensor", "7"=>"Color sequential linear sensor"
1000 );
1001
1002 $this->ImageInfo['h']["sensing"] =
1003 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1004 break;
1005 case TAG_SOUCE_TYPE:
1006 $this->ImageInfo['h']["sourceType"] = substr($ValuePtr,0,$ByteCount);
1007 break;
1008 case TAG_SCENE_TYPE:
1009 $this->ImageInfo['h']["sceneType"] = substr($ValuePtr,0,$ByteCount);
1010 break;
1011 case TAG_CFA_PATTERN:
1012 $this->ImageInfo['h']["CFAPattern"] = substr($ValuePtr,0,$ByteCount);
1013 break;
1014 case TAG_CUSTOM_RENDERED:
1015 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1016 $this->ImageInfo['h']["customRendered"] = ($tmp == 0) ? 'Normal Process' : ($tmp == 1 ? 'Custom Process' : 'Reserved');
1017 break;
1018 case TAG_EXPOSURE_MODE:
1019 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1020 $tmpArr = array('Auto Exposure','Manual Exposure','Auto Bracket');
1021 $this->ImageInfo['h']["exposureMode"] =
1022 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1023 break;
1024 case TAG_WHITE_BALANCE:
1025 $this->ImageInfo['h']["whiteBalance"] = $this->ConvertAnyFormat($ValuePtr, $Format);
1026 break;
1027 case TAG_DIGITAL_ZOOM_RATIO:
1028 $tmp = $this->ImageInfo['h']["zoomRatio"] = $this->ConvertAnyFormat($ValuePtr, $Format);
1029 $this->ImageInfo['h']["zoomRatio"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
1030 break;
1031 case TAG_FLENGTH_IN35MM:
1032 $this->ImageInfo['h']["flength35mm"] = $this->ConvertAnyFormat($ValuePtr, $Format);
1033 break;
1034 case TAG_SCREEN_CAP_TYPE:
1035 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1036 $tmpArr = array("Standard","Landscape","Portrait","Night Scene");
1037 $this->ImageInfo['h']["screenCaptureType"] =
1038 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1039 break;
1040 case TAG_GAIN_CONTROL:
1041 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1042 $tmpArr = array("None","Low Gain Up","High Gain Up","Low Gain Down","High Gain Down");
1043 $this->ImageInfo['h']["gainControl"] =
1044 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1045 break;
1046 case TAG_CONTRAST:
1047 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1048 $tmpArr = array("Normal","Soft","Hard");
1049 $this->ImageInfo['h']["contrast"] =
1050 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1051 break;
1052 case TAG_SATURATION:
1053 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1054 $tmpArr = array("Normal","Low Saturation","High Saturation");
1055 $this->ImageInfo['h']["saturation"] =
1056 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1057 break;
1058 case TAG_SHARPNESS:
1059 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1060 $tmpArr = array("Normal","Soft","Hard");
1061 $this->ImageInfo['h']["sharpness"] =
1062 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1063 break;
1064 case TAG_DIST_RANGE:
1065 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1066 $tmpArr = array("Unknown","Macro","Close View","Distant View");
1067 $this->ImageInfo['h']["distanceRange"] =
1068 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1069 break;
1070 case TAG_DEVICE_SETTING_DESC:
1071 /*
1072 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1073 $tmpArr = array("Unknown","Macro","Close View","Distant View");
1074 $this->ImageInfo['h']["distanceRange"] =
1075 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1076 */
1077 $this->ImageInfo['h']["deviceSettingDesc"] = "NOT IMPLEMENTED";
1078 break;
1079 case TAG_COMPRESS_SCHEME:
1080 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1081 $tmpArr = array("1"=>"Uncompressed","6"=>"JPEG compression (thumbnails only)");
1082 $this->ImageInfo['h']["compressScheme"] =
1083 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1084 break;
1085 case TAG_IMAGE_WD:
1086 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1087 $this->ImageInfo['h']["jpegImageWidth"] = $tmp;
1088 break;
1089 case TAG_IMAGE_HT:
1090 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1091 $this->ImageInfo['h']["jpegImageHeight"] = $tmp;
1092 break;
1093 case TAG_IMAGE_BPS:
1094 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1095 $this->ImageInfo['h']["jpegBitsPerSample"] = $tmp;
1096 break;
1097 case TAG_IMAGE_PHOTO_INT:
1098 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1099 $this->ImageInfo['h']["jpegPhotometricInt"] = $tmp;
1100 $tmpArr = array("2"=>"RGB","6"=>"YCbCr");
1101 $this->ImageInfo['h']["jpegPhotometricInt"] =
1102 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1103
1104 break;
1105 case TAG_IMAGE_SOFFSET:
1106 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1107 $this->ImageInfo['h']["jpegStripOffsets"] = $tmp;
1108 break;
1109 case TAG_IMAGE_SPP:
1110 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1111 $this->ImageInfo['h']["jpegSamplesPerPixel"] = $tmp;
1112 break;
1113 case TAG_IMAGE_RPS:
1114 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1115 $this->ImageInfo['h']["jpegRowsPerStrip"] = $tmp;
1116 break;
1117 case TAG_IMAGE_SBC:
1118 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1119 $this->ImageInfo['h']["jpegStripByteCounts"] = $tmp;
1120 break;
1121 case TAG_IMAGE_P_CONFIG:
1122 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1123 $tmpArr = array("1"=>"Chunky Format","2"=>"Planar Format");
1124 $this->ImageInfo['h']["jpegPlanarConfig"] =
1125 (isset($tmpArr["$tmp"]) ? $tmpArr["$tmp"] : "Reserved");
1126 break;
1127 case TAG_FOCALPLANE_YRESOL:
1128 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1129 $this->ImageInfo['h']["focalPlaneYResolution"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
1130 break;
1131 case TAG_BRIGHTNESS:
1132 $tmp = $this->ConvertAnyFormat($ValuePtr, $Format);
1133 $this->ImageInfo['h']["brightness"] = sprintf("%4.2f (%d/%d)",(double)$tmp[0],$tmp[1][0],$tmp[1][1]);
1134 break;
1135 //---------------------------------------------
1136 case TAG_EXIF_OFFSET:
1137 case TAG_INTEROP_OFFSET:
1138 {
1139
1140 $SubdirStart = substr($OffsetBase,$this->Get32u($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]));
1141 //if ($SubdirStart < $OffsetBase || $SubdirStart > $OffsetBase+$ExifLength){
1142 // debug("Illegal exif or interop ofset directory link",1);
1143 //}else{
1144 //echo "<h1>Calling sub-exif dir</h1>";
1145 $this->ProcessExifDir($SubdirStart, $OffsetBase, $ExifLength);
1146 //}
1147 continue;
1148 }
1149 default: {
1150 $this->debug("UNKNOWN TAG: $Tag");
1151 }
1152 }
1153 }
1154
1155 {
1156 // In addition to linking to subdirectories via exif tags,
1157 // there's also a potential link to another directory at the end of each
1158 // directory. this has got to be the result of a comitee!
1159 $tmpDirStart = substr($DirStart,2+12*$NumDirEntries);
1160 if (strlen($tmpDirStart) + 4 <= strlen($OffsetBase)+$ExifLength){
1161 $Offset = $this->Get32u($tmpDirStart[0],$tmpDirStart[1],$tmpDirStart[2],$tmpDirStart[3]);
1162 if ($Offset){
1163 $SubdirStart = substr($OffsetBase,$Offset);
1164 if (strlen($SubdirStart) > strlen($OffsetBase)+$ExifLength){
1165 if (strlen($SubdirStart) < strlen($OffsetBase)+$ExifLength+20){
1166 // Jhead 1.3 or earlier would crop the whole directory!
1167 // As Jhead produces this form of format incorrectness,
1168 // I'll just let it pass silently
1169 } else {
1170 $this->errno = 51;
1171 $this->errstr = "Illegal subdirectory link";
1172 $this->debug($this->errstr,1);
1173 }
1174 }else{
1175 if (strlen($SubdirStart) <= strlen($OffsetBase)+$ExifLength){
1176 $this->ProcessExifDir($SubdirStart, $OffsetBase, $ExifLength);
1177 }
1178 }
1179 }
1180 } else {
1181 // The exif header ends before the last next directory pointer.
1182 }
1183 }
1184
1185 /**
1186 * Check if thumbnail has been cached or not.
1187 * If yes! then read the file.
1188 */
1189 if(file_exists($this->thumbnail) && $this->caching && (filemtime($this->thumbnail) == filemtime($this->file) )) {
1190 $this->ImageInfo["h"]["Thumbnail"] = $this->thumbnail;
1191 $this->ImageInfo["h"]["ThumbnailSize"] = sprintf("%d bytes",filesize($this->thumbnail));
1192 } else{
1193 if ($this->ThumbnailSize && $this->ThumbnailOffset){
1194 if ($this->ThumbnailSize + $this->ThumbnailOffset <= $ExifLength){
1195 // The thumbnail pointer appears to be valid. Store it.
1196 $this->ImageInfo["h"]["Thumbnail"] = substr($OffsetBase,$this->ThumbnailOffset);
1197
1198 // Save the thumbnail /
1199 if($this->caching && is_dir($this->cacheDir)) {
1200 $this->saveThumbnail($this->thumbnail);
1201 $this->ImageInfo["h"]["Thumbnail"] = $this->thumbnail;
1202 }
1203 $this->ImageInfo["h"]["ThumbnailSize"] = sprintf("%d bytes",strlen($this->ImageInfo["h"]["Thumbnail"]));
1204 }
1205 }
1206 }
1207 }
1208
1209 /**
1210 * Process Exif data
1211 * @param array Section data as an array
1212 * @param int Length of the section (length of data array)
1213 *
1214 */
1215 function process_EXIF($data,$length) {
1216
1217 $this->debug("Exif header $length bytes long\n");
1218 if(($data[2].$data[3].$data[4].$data[5]) != "Exif") {
1219 $this->errno = 52;
1220 $this->errstr = "NOT EXIF FORMAT";
1221 $this->debug($this->errstr,1);
1222 }
1223
1224 $this->ImageInfo["h"]["FlashUsed"] = 0;
1225 /** If it s from a digicam, and it used flash, it says so. */
1226
1227 $this->FocalplaneXRes = 0;
1228 $this->FocalplaneUnits = 0;
1229 $this->ExifImageWidth = 0;
1230
1231 if(($data[8].$data[9]) == "II") {
1232 $this->debug("Exif section in Intel order\n");
1233 $this->MotorolaOrder = 0;
1234 } else if(($data[8].$data[9]) == "MM") {
1235 $this->debug("Exif section in Motorola order\n");
1236 $this->MotorolaOrder = 1;
1237 } else {
1238 $this->errno = 53;
1239 $this->errstr = "Invalid Exif alignment marker.\n";
1240 $this->debug($this->errstr,1);
1241 return;
1242 }
1243
1244 if($this->Get16u($data[10],$data[11]) != 0x2A || $this->Get32s($data[12],$data[13],$data[14],$data[15]) != 0x08) {
1245 $this->errno = 54;
1246 $this->errstr = "Invalid Exif start (1)";
1247 $this->debug($this->errstr,1);
1248 }
1249
1250 $DirWithThumbnailPtrs = NULL;
1251
1252 //$this->ProcessExifDir(array_slice($data,16),array_slice($data,8),$length);
1253 $this->ProcessExifDir(substr($data,16),substr($data,8),$length);
1254
1255 // Compute the CCD width, in milimeters. 2
1256 if ($this->FocalplaneXRes != 0){
1257 $this->ImageInfo["h"]["CCDWidth"] = sprintf("%4.2fmm",(float)($this->ExifImageWidth * $this->FocalplaneUnits / $this->FocalplaneXRes));
1258 }
1259
1260 $this->debug("Non settings part of Exif header: ".$length." bytes\n");
1261 } // end of function process_EXIF
1262
1263 /**
1264 * Converts two byte number into its equivalent int integer
1265 * @param int
1266 * @param int
1267 *
1268 */
1269 function Get16u($val,$by) {
1270 if($this->MotorolaOrder){
1271 return ((ord($val) << 8) | ord($by));
1272 } else {
1273 return ((ord($by) << 8) | ord($val));
1274 }
1275 }
1276
1277 /**
1278 * Converts 4-byte number into its equivalent integer
1279 *
1280 * @param int
1281 * @param int
1282 * @param int
1283 * @param int
1284 *
1285 * @return int
1286 */
1287 function Get32s($val1,$val2,$val3,$val4)
1288 {
1289 $val1 = ord($val1);
1290 $val2 = ord($val2);
1291 $val3 = ord($val3);
1292 $val4 = ord($val4);
1293
1294 if ($this->MotorolaOrder){
1295 return (($val1 << 24) | ($val2 << 16) | ($val3 << 8 ) | ($val4 << 0 ));
1296 }else{
1297 return (($val4 << 24) | ($val3 << 16) | ($val2 << 8 ) | ($val1 << 0 ));
1298 }
1299 }
1300 /**
1301 * Converts 4-byte number into its equivalent integer with the help of Get32s
1302 *
1303 * @param int
1304 * @param int
1305 * @param int
1306 * @param int
1307 *
1308 * @return int
1309 *
1310 */
1311 function get32u($val1,$val2,$val3,$val4) {
1312 return ($this->Get32s($val1,$val2,$val3,$val4) & 0xffffffff);
1313 }
1314
1315 //--------------------------------------------------------------------------
1316 // Evaluate number, be it int, rational, or float from directory.
1317 //--------------------------------------------------------------------------
1318 function ConvertAnyFormat($ValuePtr, $Format)
1319 {
1320 $Value = 0;
1321
1322 switch($Format){
1323 case FMT_SBYTE: $Value = $ValuePtr[0]; break;
1324 case FMT_BYTE: $Value = $ValuePtr[0]; break;
1325
1326 case FMT_USHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break;
1327 case FMT_ULONG: $Value = $this->Get32u($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break;
1328
1329 case FMT_URATIONAL:
1330 case FMT_SRATIONAL:
1331 {
1332
1333 $Num = $this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]);
1334 $Den = $this->Get32s($ValuePtr[4],$ValuePtr[5],$ValuePtr[6],$ValuePtr[7]);
1335 if ($Den == 0){
1336 $Value = 0;
1337 }else{
1338 $Value = (double) ($Num/$Den);
1339 }
1340 return array($Value,array($Num,$Den));
1341 break;
1342 }
1343
1344 case FMT_SSHORT: $Value = $this->Get16u($ValuePtr[0],$ValuePtr[1]); break;
1345 case FMT_SLONG: $Value = $this->Get32s($ValuePtr[0],$ValuePtr[1],$ValuePtr[2],$ValuePtr[3]); break;
1346
1347 // Not sure if this is correct (never seen float used in Exif format)
1348 case FMT_SINGLE: $Value = $ValuePtr[0]; break;
1349 case FMT_DOUBLE: $Value = $ValuePtr[0]; break;
1350 }
1351 return $Value;
1352 }
1353
1354 /**
1355 * Function to extract thumbnail from Exif data of the image.
1356 * and store it in a filename given by $ThumbFile
1357 *
1358 * @param String Files name to store the thumbnail
1359 *
1360 */
1361 function saveThumbnail($ThumbFile) {
1362 $ThumbFile = trim($ThumbFile);
1363 $file = basename($this->file);
1364
1365 if(empty($ThumbFile)) $ThumbFile = "th_$file";
1366
1367 if (!empty($this->ImageInfo["h"]["Thumbnail"])){
1368 $tp = fopen($ThumbFile,"wb");
1369 if(!$tp) {
1370 $this->errno = 2;
1371 $this->errstr = "Cannot Open file '$ThumbFile'";
1372 }
1373 fwrite($tp,$this->ImageInfo["h"]["Thumbnail"]);
1374 fclose($tp);
1375 touch($ThumbFile,filemtime($this->file));
1376 }
1377 //$this->thumbnailURL = $ThumbFile;
1378 $this->ImageInfo["h"]["Thumbnail"] = $ThumbFile;
1379 }
1380
1381 /**
1382 * Returns thumbnail url along with parameter supplied.
1383 * Should be called in src attribute of image
1384 *
1385 * @return string File URL
1386 *
1387 */
1388 function showThumbnail() {
1389 return "showThumbnail.php?file=".$this->file;
1390 //$this->ImageInfo["h"]["Thumbnail"]
1391 }
1392
1393 /**
1394 * Function to give back thumbail image
1395 * @return string full image
1396 *
1397 */
1398 function getThumbnail() {
1399 return $this->ImageInfo["h"]["Thumbnail"];
1400 }
1401
1402 /**
1403 *
1404 */
1405 function getImageInfo() {
1406
1407 $imgInfo = $this->ImageInfo["h"];
1408
1409 if ( !isset ( $imgInfo["Width"] ) ) return array ( "EXIF" => "NO" ) ; # Dummy entry to distinguish von empty (=never generated) one
1410
1411 $retArr = $imgInfo;
1412 $retArr["FileName"] = $imgInfo["FileName"];
1413 $retArr["FileSize"] = $imgInfo["FileSize"]." bytes";
1414
1415 $retArr["FileDateTime"] = date("d-M-Y H:i:s",$imgInfo["FileDateTime"]);
1416
1417 $retArr["resolution"] = $imgInfo["Width"]."x".$imgInfo["Height"];
1418
1419
1420 if ($this->ImageInfo[TAG_ORIENTATION] > 1){
1421 // Only print orientation if one was supplied, and if its not 1 (normal orientation)
1422
1423 // 1 - "The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side."
1424 // 2 - "The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side."
1425 // 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side."
1426 // 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side."
1427
1428 // 5 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual top."
1429 // 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top."
1430 // 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom."
1431 // 8 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual bottom."
1432
1433 // Note: The descriptions here are the same as the name of the command line
1434 // ption to pass to jpegtran to right the image
1435 $OrientTab = array(
1436 "Undefined",
1437 "Normal", // 1
1438 "flip horizontal", // left right reversed mirror
1439 "rotate 180", // 3
1440 "flip vertical", // upside down mirror
1441 "transpose", // Flipped about top-left <--> bottom-right axis.
1442 "rotate 90", // rotate 90 cw to right it.
1443 "transverse", // flipped about top-right <--> bottom-left axis
1444 "rotate 270", // rotate 270 to right it.
1445 );
1446
1447 $retArr["orientation"] = $OrientTab[$this->ImageInfo[TAG_ORIENTATION]];
1448 }
1449
1450 $retArr["color"] = ($imgInfo["IsColor"] == 0) ? "Black and white" : "Color";
1451
1452 if(isset($imgInfo["Process"])) {
1453 switch($imgInfo["Process"]) {
1454 case M_SOF0: $process = "Baseline";break;
1455 case M_SOF1: $process = "Extended sequential";break;
1456 case M_SOF2: $process = "Progressive";break;
1457 case M_SOF3: $process = "Lossless";break;
1458 case M_SOF5: $process = "Differential sequential";break;
1459 case M_SOF6: $process = "Differential progressive";break;
1460 case M_SOF7: $process = "Differential lossless";break;
1461 case M_SOF9: $process = "Extended sequential, arithmetic coding";break;
1462 case M_SOF10: $process = "Progressive, arithmetic coding";break;
1463 case M_SOF11: $process = "Lossless, arithmetic coding";break;
1464 case M_SOF13: $process = "Differential sequential, arithmetic coding";break;
1465 case M_SOF14: $process = "Differential progressive, arithmetic coding";break;
1466 case M_SOF15: $process = "Differential lossless, arithmetic coding";break;
1467 default: $process = "Unknown";
1468 }
1469 $retArr["jpegProcess"] = $process;
1470 }
1471
1472 if(file_exists($this->thumbnailURL)) {
1473 $retArr["Thumbnail"] = "<a href='$this->thumbnailURL'>$this->thumbnailURL</a>";
1474 }
1475
1476 return $retArr;
1477 }
1478
1479 /**
1480 * Returns time in microseconds
1481 */
1482 function getmicrotime(){
1483 list($usec, $sec) = explode(" ",microtime());
1484 return ((float)$usec + (float)$sec);
1485 }
1486
1487 /**
1488 * Get the time difference
1489 */
1490 function getDiffTime() {
1491 return ($this->getmicrotime() - $this->timeStart);
1492 }
1493 } // end of class
1494 ?>