phpExifRW
[ class tree: phpExifRW ] [ index: phpExifRW ] [ all elements ]

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 ?>

Documentation generated on Sat, 2 Aug 2003 16:19:43 +0530 by phpDocumentor 1.2.1