source: documentation/trunk/packages/dokuwiki-2011-05-25a/inc/JpegMeta.php@ 30098

Last change on this file since 30098 was 25027, checked in by jmt12, 12 years ago

Adding the packages directory, and within it a configured version of dokuwiki all ready to run

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