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