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