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