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