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