Port exifer code from Ansel to Horde_Image_Exif_*
authorMichael J. Rubinsky <mrubinsk@horde.org>
Sat, 1 Aug 2009 00:54:30 +0000 (20:54 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Sat, 1 Aug 2009 00:54:30 +0000 (20:54 -0400)
12 files changed:
framework/Image/lib/Horde/Image/Exif.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Base.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Bundled.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Base.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Canon.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Fujufilm.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Gps.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Nikon.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Olympus.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Panasonic.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Parser/Sanyo.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Exif/Php.php [new file with mode: 0644]

diff --git a/framework/Image/lib/Horde/Image/Exif.php b/framework/Image/lib/Horde/Image/Exif.php
new file mode 100644 (file)
index 0000000..9bd9758
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ * General class for fetching and parsing EXIF information from images.
+ *
+ * Works equally well with either the built in php exif functions (if PHP
+ * compiled with exif support) or the (slower) bundled exif library.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+class Horde_Image_Exif
+{
+    static public function factory($driver = null)
+    {
+        return new Horde_Image_Exif_Php();
+    }
+
+    /**
+     * Converts from Intel to Motorola endien.  Just reverses the bytes
+     * (assumes hex is passed in)
+     *
+     * @param $num
+     * @return unknown_type
+     */
+    static public function Horde_Image_Exif::intel2Moto($num)
+    {
+        $len  = strlen($intel);
+        $moto = '';
+        for($i = 0; $i <= $len; $i += 2) {
+            $moto .= substr($intel, $len-$i, 2);
+        }
+
+        return $moto;
+    }
+
+}
diff --git a/framework/Image/lib/Horde/Image/Exif/Base.php b/framework/Image/lib/Horde/Image/Exif/Base.php
new file mode 100644 (file)
index 0000000..d8d68c9
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+/**
+ *
+ */
+abstract class Horde_Image_Exif_Base
+{
+    abstract public function getData($image);
+
+    /**
+     *
+     * @return unknown_type
+     */
+    static public function getFields()
+    {
+        return array('Make' => array('description' => _("Camera Make"), 'type' => 'text'),
+                     'Model' => array('description' => _("Camera Model"), 'type' => 'text'),
+                     'ImageType' => array('description' => _("Photo Type"), 'type' => 'text'),
+                     'ImageDescription' => array('description' => _("Photo Description"), 'type' => 'text'),
+                     'FileSize' => array('description' => _("File Size"), 'type' => 'number'),
+                     'DateTime' => array('description' => _("Date Photo Modified"), 'type' => 'date'),
+                     'DateTimeOriginal' => array('description' => _("Date Photo Taken"), 'type' => 'date'),
+                     'DateTimeDigitized' => array('description' => _("Date Photo Digitized"), 'type' => 'date'),
+                     'ExifImageWidth' => array('description' => _("Width"), 'type' => 'number'),
+                     'ExifImageLength' => array('description' => _("Height"), 'type' => 'number'),
+                     'XResolution' => array('description' => _("X Resolution"), 'type' => 'number'),
+                     'YResolution' => array('description' => _("Y Resolution"), 'type' => 'number'),
+                     'ResolutionUnit' => array('description' => _("Resolution Unit"), 'type' => 'text'),
+                     'ShutterSpeedValue' => array('description' => _("Shutter Speed"), 'type' => 'number'),
+                     'ExposureTime' => array('description' => _("Exposure"), 'type' => 'number'),
+                     'FocalLength' => array('description' => _("Focal Length"), 'type' => 'number'),
+                     'FocalLengthIn35mmFilm' => array('description' => _("Focal Length (35mm equiv)"), 'type' => 'number'),
+                     'ApertureValue' => array('description' => _("Aperture"), 'type' => 'number'),
+                     'FNumber' => array('description' => _("F-Number"), 'type' => 'number'),
+                     'ISOSpeedRatings' => array('description' => _("ISO Setting"), 'type' => 'number'),
+                     'ExposureBiasValue' => array('description' => _("Exposure Bias"), 'type' => 'number'),
+                     'ExposureMode' => array('description' => _("Exposure Mode"), 'type' => 'number'),
+                     'ExposureProgram' => array('description' => _("Exposure Program"), 'type' => 'number'),
+                     'MeteringMode' => array('description' => _("Metering Mode"), 'type' => 'number'),
+                     'Flash' => array('description' => _("Flash Setting"), 'type' => 'number'),
+                     'UserComment' => array('description' => _("User Comment"), 'type' => 'text'),
+                     'ColorSpace' => array('description' => _("Color Space"), 'type' => 'number'),
+                     'SensingMethod' => array('description' => _("Sensing Method"), 'type' => 'number'),
+                     'WhiteBalance' => array('description' => _("White Balance"), 'type' => 'number'),
+                     'Orientation' => array('description' => _("Camera Orientation"), 'type' => 'number'),
+                     'Copyright' => array('description' => _("Copyright"), 'type' => 'text'),
+                     'Artist' => array('description' => _("Artist"), 'type' => 'text'),
+                     'GPSLatitude' => array('description' => _("Latitude"), 'type' => 'gps'),
+                     'GPSLongitude' => array('description' => _("Longitude"), 'type' => 'gps'),
+                     'LightSource' => array('description' => _("Light source"), 'type' => 'number'),
+                     'FileSource' => array('description' => _("File source"), 'type' => 'number'),
+        );
+    }
+
+    /**
+     *
+     * @param $exif
+     * @return unknown_type
+     */
+    protected function _processData($exif)
+    {
+        $results = array();
+        if ($exif) {
+            $fields = self::getFields();
+
+            foreach ($fields as $field => $data) {
+                $value = isset($exif[$field]) ? $exif[$field] : '';
+                // Don't store empty fields.
+                if ($value === '') {
+                    continue;
+                }
+
+                /* Special handling of GPS data */
+                if ($data['type'] == 'gps') {
+                    $value = self::_parseGPSData($exif[$field]);
+                    if (!empty($exif[$field . 'Ref']) && ($exif[$field . 'Ref'] == 'S' || $exif[$field . 'Ref'] == 'W')) {
+                        $value = '-' . $value;
+                    }
+                }
+
+                /* Date fields are converted to a timestamp.*/
+                if ($data['type'] == 'date') {
+                    @list($ymd, $hms) = explode(' ', $value, 2);
+                    @list($year, $month, $day) = explode(':', $ymd, 3);
+                    if (!empty($hms) && !empty($year) && !empty($month) && !empty($day)) {
+                        $time = "$month/$day/$year $hms";
+                        $value = strtotime($time);
+                    }
+                }
+
+                $results[$field] = $value;
+            }
+        }
+    }
+
+    /**
+     * Parse the Longitude and Latitude values into a standardized format
+     * regardless of the source format.
+     *
+     * @param mixed $data  An array containing degrees, minutes, seconds
+     *                     in index 0, 1, 2 respectifully.
+     *
+     * @return double  The location data in a decimal format.
+     */
+    protected function _parseGPSData($data)
+    {
+        // According to EXIF standard, GPS data can be in the form of
+        // dd/1 mm/1 ss/1 or as a decimal reprentation.
+        if ($data[0] == 0) {
+            return 0;
+        }
+        $min = explode('/', $data[1]);
+        if (count($min) > 1) {
+            $min = $min[0] / $min[1];
+        } else {
+            $min = $min[0];
+        }
+
+        $sec = explode('/', $data[2]);
+        if (count($sec) > 1) {
+            $sec = $sec[0] / $sec[1];
+        } else {
+            $sec = $sec[0];
+        }
+
+        return self::_degToDecimal($data[0], $min, $sec);
+    }
+
+    /**
+     *
+     * @param $degrees
+     * @param $minutes
+     * @param $seconds
+     * @return unknown_type
+     */
+    protected function _degToDecimal($degrees, $minutes, $seconds)
+    {
+        $degs = (double)($degrees + ($minutes / 60) + ($seconds/3600));
+        return round($degs, 6);
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Bundled.php b/framework/Image/lib/Horde/Image/Exif/Bundled.php
new file mode 100644 (file)
index 0000000..ee13826
--- /dev/null
@@ -0,0 +1,671 @@
+<?php
+/**
+ * Class for dealing with Exif data using a bundled PHP library based on Exifer.
+ *
+ *
+ */
+class Horde_Image_Exif_Bundled extends Horde_Image_Exif_Base
+{
+    public function getData($image)
+    {
+        $raw = self::_exif_read_data($imageFile);
+        $exif = array();
+        foreach ($raw as $key => $value) {
+            if (($key == 'IFD0') || ($key == 'SubIFD')) {
+                foreach ($value as $subkey => $subvalue) {
+                    $exif[$subkey] = $subvalue;
+                }
+            } else {
+                $exif[$key] = $value;
+            }
+        }
+        // Not really an EXIF property, but an attribute nonetheless...
+        // PHP's exif functions return it, so add it here to be consistent.
+        $exif['FileSize'] = @filesize($imageFile);
+
+        return $this->_processData($exif);
+    }
+
+
+    /**
+     *
+     * @param $path
+     * @return unknown_type
+     */
+    static protected function _exif_read_data($path)
+    {
+        if ($path == '' || $path == 'none') {
+            return;
+        }
+
+        // the b is for windows machines to open in binary mode
+        $in = @fopen($path, 'rb');
+
+        // There may be an elegant way to do this with one file handle.
+        $seek = @fopen($path, 'rb');
+        $globalOffset = 0;
+        $result['Errors'] = 0;
+
+        // if the path was invalid, this error will catch it
+        if (!$in || !$seek) {
+            $result['Errors'] = 1;
+            $result['Error'][$result['Errors']] = _("The file could not be found.");
+            return $result;
+        }
+
+        // First 2 bytes of JPEG are 0xFFD8
+        $data = bin2hex(fread($in, 2));
+        if ($data == 'ffd8') {
+            $result['ValidJpeg'] = 1;
+        } else {
+            $result['ValidJpeg'] = 0;
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        $result['ValidIPTCData'] = 0;
+        $result['ValidJFIFData'] = 0;
+        $result['ValidEXIFData'] = 0;
+        $result['ValidAPP2Data'] = 0;
+        $result['ValidCOMData'] = 0;
+
+        // Next 2 bytes are MARKER tag (0xFFE#)
+        $data = bin2hex(fread($in, 2));
+        $size = bin2hex(fread($in, 2));
+
+        // LOOP THROUGH MARKERS TILL YOU GET TO FFE1  (exif marker)
+        while(!feof($in) && $data != 'ffe1' && $data != 'ffc0' && $data != 'ffd9') {
+            if ($data == 'ffe0') { // JFIF Marker
+                $result['ValidJFIFData'] = 1;
+                $result['JFIF']['Size'] = hexdec($size);
+
+                if (hexdec($size) - 2 > 0) {
+                    $data = fread($in, hexdec($size) - 2);
+                    $result['JFIF']['Data'] = $data;
+                }
+
+                $result['JFIF']['Identifier'] = substr($data, 0, 5);;
+                $result['JFIF']['ExtensionCode'] =  bin2hex(substr($data, 6, 1));
+
+                $globalOffset+=hexdec($size) + 2;
+
+            } elseif ($data == 'ffed') {  // IPTC Marker
+                $result['ValidIPTCData'] = 1;
+                $result['IPTC']['Size'] = hexdec($size);
+
+                if (hexdec($size) - 2 > 0) {
+                    $data = fread($in, hexdec($size)-2);
+                    $result['IPTC']['Data'] = $data ;
+                }
+                $globalOffset += hexdec($size) + 2;
+
+            } elseif ($data == 'ffe2') {  // EXIF extension Marker
+                $result['ValidAPP2Data'] = 1;
+                $result['APP2']['Size'] = hexdec($size);
+
+                if (hexdec($size)-2 > 0) {
+                    $data = fread($in, hexdec($size) - 2);
+                    $result['APP2']['Data'] = $data ;
+                }
+                $globalOffset+=hexdec($size) + 2;
+
+            } elseif ($data == 'fffe') {  // COM extension Marker
+                $result['ValidCOMData'] = 1;
+                $result['COM']['Size'] = hexdec($size);
+
+                if (hexdec($size)-2 > 0) {
+                    $data = fread($in, hexdec($size) - 2);
+                    $result['COM']['Data'] = $data ;
+                }
+                $globalOffset += hexdec($size) + 2;
+
+            } else if ($data == 'ffe1') {
+                $result['ValidEXIFData'] = 1;
+            }
+
+            $data = bin2hex(fread($in, 2));
+            $size = bin2hex(fread($in, 2));
+        }
+        // END MARKER LOOP
+
+        if ($data == 'ffe1') {
+            $result['ValidEXIFData'] = 1;
+        } else {
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        // Size of APP1
+        $result['APP1Size'] = hexdec($size);
+
+        // Start of APP1 block starts with 'Exif' header (6 bytes)
+        $header = fread($in, 6);
+
+        // Then theres a TIFF header with 2 bytes of endieness (II or MM)
+        $header = fread($in, 2);
+        if ($header==='II') {
+            $intel = 1;
+            $result['Endien'] = 'Intel';
+        } elseif ($header==='MM') {
+            $intel = 0;
+            $result['Endien'] = 'Motorola';
+        } else {
+            $intel = 1; // not sure what the default should be, but this seems reasonable
+            $result['Endien'] = 'Unknown';
+        }
+
+        // 2 bytes of 0x002a
+        $tag = bin2hex(fread( $in, 2 ));
+
+        // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header)
+        $offset = bin2hex(fread($in, 4));
+        if ($intel == 1) {
+            $offset = Horde_Image_Exif::intel2Moto($offset);
+        }
+
+        // Check for extremely large values here
+        if (hexdec($offset) > 100000) {
+            $result['ValidEXIFData'] = 0;
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        if (hexdec($offset) > 8) {
+            $unknown = fread($in, hexdec($offset) - 8);
+        }
+
+        // add 12 to the offset to account for TIFF header
+        $globalOffset += 12;
+
+        //===========================================================
+        // Start of IFD0
+        $num = bin2hex(fread($in, 2));
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $num = hexdec($num);
+        $result['IFD0NumTags'] = $num;
+
+        // 1000 entries is too much and is probably an error.
+        if ($num < 1000) {
+            for($i = 0; $i < $num; $i++) {
+                self::_readEntry($result, $in, $seek, $intel, 'IFD0', $globalOffset);
+            }
+        } else {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = 'Illegal size for IFD0';
+        }
+
+        // store offset to IFD1
+        $offset = bin2hex(fread($in, 4));
+        if ($intel == 1) {
+            $offset = Horde_Image_Exif::intel2Moto($offset);
+        }
+        $result['IFD1Offset'] = hexdec($offset);
+
+        // Check for SubIFD
+        if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) {
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        // seek to SubIFD (Value of ExifOffset tag) above.
+        $ExitOffset = $result['IFD0']['ExifOffset'];
+        $v = fseek($in, $globalOffset + $ExitOffset);
+        if ($v == -1) {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Couldnt Find SubIFD");
+        }
+
+        //===========================================================
+        // Start of SubIFD
+        $num = bin2hex(fread($in, 2));
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $num = hexdec($num);
+        $result['SubIFDNumTags'] = $num;
+
+        // 1000 entries is too much and is probably an error.
+        if ($num < 1000) {
+            for($i = 0; $i < $num; $i++) {
+                self::_readEntry($result, $in, $seek, $intel, 'SubIFD', $globalOffset);
+            }
+        } else {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Illegal size for SubIFD");
+        }
+
+        // Add the 35mm equivalent focal length:
+        // Now properly get this using the FocalLength35mmFilm tag
+        //$result['SubIFD']['FocalLength35mmEquiv'] = get35mmEquivFocalLength($result);
+
+        // Check for IFD1
+        if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) {
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        // seek to IFD1
+        $v = fseek($in, $globalOffset + $result['IFD1Offset']);
+        if ($v == -1) {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Couldnt Find IFD1");
+        }
+
+        //===========================================================
+        // Start of IFD1
+        $num = bin2hex(fread($in, 2));
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $num = hexdec($num);
+        $result['IFD1NumTags'] = $num;
+
+        // 1000 entries is too much and is probably an error.
+        if ($num < 1000) {
+            for($i = 0; $i < $num; $i++) {
+                self::_readEntry($result, $in, $seek, $intel, 'IFD1', $globalOffset);
+            }
+        } else {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Illegal size for IFD1");
+        }
+        // include the thumbnail raw data...
+        if ($result['IFD1']['JpegIFOffset'] > 0 &&
+            $result['IFD1']['JpegIFByteCount'] > 0) {
+
+            $v = fseek($seek, $globalOffset + $result['IFD1']['JpegIFOffset']);
+            if ($v == 0) {
+                $data = fread($seek, $result['IFD1']['JpegIFByteCount']);
+            } else if ($v == -1) {
+                $result['Errors'] = $result['Errors'] + 1;
+            }
+            $result['IFD1']['ThumbnailData'] = $data;
+        }
+
+        // Check for Interoperability IFD
+        if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) ||
+            $result['SubIFD']['ExifInteroperabilityOffset'] == 0) {
+
+            fclose($in);
+            fclose($seek);
+            return $result;
+        }
+
+        // Seek to InteroperabilityIFD
+        $v = fseek($in, $globalOffset + $result['SubIFD']['ExifInteroperabilityOffset']);
+        if ($v == -1) {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Couldnt Find InteroperabilityIFD");
+        }
+
+        //===========================================================
+        // Start of InteroperabilityIFD
+        $num = bin2hex(fread($in, 2));
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $num = hexdec($num);
+        $result['InteroperabilityIFDNumTags'] = $num;
+
+        // 1000 entries is too much and is probably an error.
+        if ($num < 1000) {
+            for($i = 0; $i < $num; $i++) {
+                self::_readEntry($result, $in, $seek, $intel, 'InteroperabilityIFD', $globalOffset);
+            }
+        } else {
+            $result['Errors'] = $result['Errors'] + 1;
+            $result['Error'][$result['Errors']] = _("Illegal size for InteroperabilityIFD");
+        }
+        fclose($in);
+        fclose($seek);
+        return $result;
+    }
+
+    /**
+     *
+     * @param $result
+     * @param $in
+     * @param $seek
+     * @param $intel
+     * @param $ifd_name
+     * @param $globalOffset
+     * @return unknown_type
+     */
+    static protected function _readEntry(&$result, $in, $seek, $intel, $ifd_name, $globalOffset)
+    {
+        // Still ok to read?
+        if (feof($in)) {
+            $result['Errors'] = $result['Errors'] + 1;
+            return;
+        }
+
+        // 2 byte tag
+        $tag = bin2hex(fread($in, 2));
+        if ($intel == 1) $tag = Horde_Image_Exif::intel2Moto($tag);
+        $tag_name = self::_lookupTag($tag);
+
+        // 2 byte datatype
+        $type = bin2hex(fread($in, 2));
+        if ($intel == 1) $type = Horde_Image_Exif::intel2Moto($type);
+        lookup_type($type, $size);
+
+        // 4 byte number of elements
+        $count = bin2hex(fread($in, 4));
+        if ($intel == 1) $count = Horde_Image_Exif::intel2Moto($count);
+        $bytesofdata = $size * hexdec($count);
+
+        // 4 byte value or pointer to value if larger than 4 bytes
+        $value = fread($in, 4 );
+
+        // if datatype is 4 bytes or less, its the value
+        if ($bytesofdata <= 4) {
+            $data = $value;
+        } elseif ($bytesofdata < 100000) {
+            // otherwise its a pointer to the value, so lets go get it
+            $value = bin2hex($value);
+            if ($intel == 1) {
+                $value = Horde_Image_Exif::intel2Moto($value);
+            }
+            // offsets are from TIFF header which is 12 bytes from the start of file
+            $v = fseek($seek, $globalOffset+hexdec($value));
+            if ($v == 0) {
+                $data = fread($seek, $bytesofdata);
+            } elseif ($v == -1) {
+                $result['Errors'] = $result['Errors'] + 1;
+            }
+        } else {
+            // bytesofdata was too big, so the exif had an error
+            $result['Errors'] = $result['Errors'] + 1;
+            return;
+        }
+
+        // if its a maker tag, we need to parse this specially
+        if ($tag_name == 'MakerNote') {
+            $make = $result['IFD0']['Make'];
+            if (strpos(strtolower($make), 'nikon') !== false) {
+                Horde_Image_Exif_Parser_Nikon::parse($data, $result);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } elseif (strpos(strtolower($make), 'olympus') !== false) {
+                Horde_Image_Exif_Parser_Olympus::parse($data, $result, $seek, $globalOffset);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } elseif (strpos(strtolower($make), 'canon') !== false) {
+                Horde_Image_Exif_Parser_Canon::parse($data, $result, $seek, $globalOffset);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } elseif (strpos(strtolower($make), 'fujifilm') !== false) {
+                Horde_Image_Exif_Parser_Fujifilm::parse($data, $result);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } elseif (strpos(strtolower($make), 'sanyo') !== false) {
+                Horde_Image_Exif_Parser_Sanyo::parse($data, $result, $seek, $globalOffset);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } elseif (strpos(strtolower($make), 'panasonic') !== false) {
+                Horde_Image_Exif_Parser_Panasonic::parse($data, $result, $seek, $globalOffset);
+                $result[$ifd_name]['KnownMaker'] = 1;
+            } else {
+                $result[$ifd_name]['KnownMaker'] = 0;
+            }
+        } elseif ($tag_name == 'GPSInfoOffset') {
+            $formated_data = self::_formatData($type, $tag, $intel, $data);
+            $result[$ifd_name]['GPSInfo'] = $formated_data;
+            Horde_Image_Exif_Parser_Gps::parse($data, $result, $formated_data, $seek, $globalOffset);
+        } else {
+            // Format the data depending on the type and tag
+            $formated_data = self::_formatData($type, $tag, $intel, $data);
+            $result[$ifd_name][$tag_name] = $formated_data;
+        }
+    }
+
+    /**
+     *
+     * @param $tag
+     * @return unknown_type
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch($tag)
+        {
+            // used by IFD0 'Camera Tags'
+            case '000b': $tag = 'ACDComment'; break;               // text string up to 999 bytes long
+            case '00fe': $tag = 'ImageType'; break;                // integer -2147483648 to 2147483647
+            case '0106': $tag = 'PhotometricInterpret'; break;     // ?? Please send sample image with this tag
+            case '010e': $tag = 'ImageDescription'; break;         // text string up to 999 bytes long
+            case '010f': $tag = 'Make'; break;                     // text string up to 999 bytes long
+            case '0110': $tag = 'Model'; break;                    // text string up to 999 bytes long
+            case '0112': $tag = 'Orientation'; break;              // integer values 1-9
+            case '0115': $tag = 'SamplePerPixel'; break;           // integer 0-65535
+            case '011a': $tag = 'xResolution'; break;              // positive rational number
+            case '011b': $tag = 'yResolution'; break;              // positive rational number
+            case '011c': $tag = 'PlanarConfig'; break;             // integer values 1-2
+            case '0128': $tag = 'ResolutionUnit'; break;           // integer values 1-3
+            case '0131': $tag = 'Software'; break;                 // text string up to 999 bytes long
+            case '0132': $tag = 'DateTime'; break;                 // YYYY:MM:DD HH:MM:SS
+            case '013b': $tag = 'Artist'; break;                   // text string up to 999 bytes long
+            case '013c': $tag = 'HostComputer'; break;             // text string
+            case '013e': $tag = 'WhitePoint'; break;               // two positive rational numbers
+            case '013f': $tag = 'PrimaryChromaticities'; break;    // six positive rational numbers
+            case '0211': $tag = 'YCbCrCoefficients'; break;        // three positive rational numbers
+            case '0213': $tag = 'YCbCrPositioning'; break;         // integer values 1-2
+            case '0214': $tag = 'ReferenceBlackWhite'; break;      // six positive rational numbers
+            case '8298': $tag = 'Copyright'; break;                // text string up to 999 bytes long
+            case '8649': $tag = 'PhotoshopSettings'; break;        // ??
+            case '8825': $tag = 'GPSInfoOffset'; break;
+            case '9286': $tag = 'UserCommentOld'; break;           // ??
+            case '8769': $tag = 'ExifOffset'; break;               // positive integer
+            // used by Exif SubIFD 'Image Tags'
+            case '829a': $tag = 'ExposureTime'; break;             // seconds or fraction of seconds 1/x
+            case '829d': $tag = 'FNumber'; break;                  // positive rational number
+            case '8822': $tag = 'ExposureProgram'; break;          // integer value 1-9
+            case '8824': $tag = 'SpectralSensitivity'; break;      // ??
+            case '8827': $tag = 'ISOSpeedRatings'; break;          // integer 0-65535
+            case '9000': $tag = 'ExifVersion'; break;              // ??
+            case '9003': $tag = 'DateTimeOriginal'; break;         // YYYY:MM:DD HH:MM:SS
+            case '9004': $tag = 'DateTimedigitized'; break;        // YYYY:MM:DD HH:MM:SS
+            case '9101': $tag = 'ComponentsConfiguration'; break;  // ??
+            case '9102': $tag = 'CompressedBitsPerPixel'; break;   // positive rational number
+            case '9201': $tag = 'ShutterSpeedValue'; break;        // seconds or fraction of seconds 1/x
+            case '9202': $tag = 'ApertureValue'; break;            // positive rational number
+            case '9203': $tag = 'BrightnessValue'; break;          // positive rational number
+            case '9204': $tag = 'ExposureBiasValue'; break;        // positive rational number (EV)
+            case '9205': $tag = 'MaxApertureValue'; break;         // positive rational number
+            case '9206': $tag = 'SubjectDistance'; break;          // positive rational number (meters)
+            case '9207': $tag = 'MeteringMode'; break;             // integer 1-6 and 255
+            case '9208': $tag = 'LightSource'; break;              // integer 1-255
+            case '9209': $tag = 'Flash'; break;                    // integer 1-255
+            case '920a': $tag = 'FocalLength'; break;              // positive rational number (mm)
+            case '9213': $tag = 'ImageHistory'; break;             // text string up to 999 bytes long
+            case '927c': $tag = 'MakerNote'; break;                // a bunch of data
+            case '9286': $tag = 'UserComment'; break;              // text string
+            case '9290': $tag = 'SubsecTime'; break;               // text string up to 999 bytes long
+            case '9291': $tag = 'SubsecTimeOriginal'; break;       // text string up to 999 bytes long
+            case '9292': $tag = 'SubsecTimeDigitized'; break;      // text string up to 999 bytes long
+            case 'a000': $tag = 'FlashPixVersion'; break;          // ??
+            case 'a001': $tag = 'ColorSpace'; break;               // values 1 or 65535
+            case 'a002': $tag = 'ExifImageWidth'; break;           // ingeter 1-65535
+            case 'a003': $tag = 'ExifImageHeight'; break;          // ingeter 1-65535
+            case 'a004': $tag = 'RelatedSoundFile'; break;         // text string 12 bytes long
+            case 'a005': $tag = 'ExifInteroperabilityOffset'; break;    // positive integer
+            case 'a20c': $tag = 'SpacialFreqResponse'; break;      // ??
+            case 'a20b': $tag = 'FlashEnergy'; break;              // positive rational number
+            case 'a20e': $tag = 'FocalPlaneXResolution'; break;    // positive rational number
+            case 'a20f': $tag = 'FocalPlaneYResolution'; break;    // positive rational number
+            case 'a210': $tag = 'FocalPlaneResolutionUnit'; break; // values 1-3
+            case 'a214': $tag = 'SubjectLocation'; break;          // two integers 0-65535
+            case 'a215': $tag = 'ExposureIndex'; break;            // positive rational number
+            case 'a217': $tag = 'SensingMethod'; break;            // values 1-8
+            case 'a300': $tag = 'FileSource'; break;               // integer
+            case 'a301': $tag = 'SceneType'; break;                // integer
+            case 'a302': $tag = 'CFAPattern'; break;               // undefined data type
+            case 'a401': $tag = 'CustomerRender'; break;           // values 0 or 1
+            case 'a402': $tag = 'ExposureMode'; break;             // values 0-2
+            case 'a403': $tag = 'WhiteBalance'; break;             // values 0 or 1
+            case 'a404': $tag = 'DigitalZoomRatio'; break;         // positive rational number
+            case 'a405': $tag = 'FocalLengthIn35mmFilm';break;
+            case 'a406': $tag = 'SceneCaptureMode'; break;         // values 0-3
+            case 'a407': $tag = 'GainControl'; break;              // values 0-4
+            case 'a408': $tag = 'Contrast'; break;                 // values 0-2
+            case 'a409': $tag = 'Saturation'; break;               // values 0-2
+            case 'a40a': $tag = 'Sharpness'; break;                // values 0-2
+
+            // used by Interoperability IFD
+            case '0001': $tag = 'InteroperabilityIndex'; break;    // text string 3 bytes long
+            case '0002': $tag = 'InteroperabilityVersion'; break;  // datatype undefined
+            case '1000': $tag = 'RelatedImageFileFormat'; break;   // text string up to 999 bytes long
+            case '1001': $tag = 'RelatedImageWidth'; break;        // integer in range 0-65535
+            case '1002': $tag = 'RelatedImageLength'; break;       // integer in range 0-65535
+
+            // used by IFD1 'Thumbnail'
+            case '0100': $tag = 'ImageWidth'; break;               // integer in range 0-65535
+            case '0101': $tag = 'ImageLength'; break;              // integer in range 0-65535
+            case '0102': $tag = 'BitsPerSample'; break;            // integers in range 0-65535
+            case '0103': $tag = 'Compression'; break;              // values 1 or 6
+            case '0106': $tag = 'PhotometricInterpretation'; break;// values 0-4
+            case '010e': $tag = 'ThumbnailDescription'; break;     // text string up to 999 bytes long
+            case '010f': $tag = 'ThumbnailMake'; break;            // text string up to 999 bytes long
+            case '0110': $tag = 'ThumbnailModel'; break;           // text string up to 999 bytes long
+            case '0111': $tag = 'StripOffsets'; break;             // ??
+            case '0112': $tag = 'ThumbnailOrientation'; break;     // integer 1-9
+            case '0115': $tag = 'SamplesPerPixel'; break;          // ??
+            case '0116': $tag = 'RowsPerStrip'; break;             // ??
+            case '0117': $tag = 'StripByteCounts'; break;          // ??
+            case '011a': $tag = 'ThumbnailXResolution'; break;     // positive rational number
+            case '011b': $tag = 'ThumbnailYResolution'; break;     // positive rational number
+            case '011c': $tag = 'PlanarConfiguration'; break;      // values 1 or 2
+            case '0128': $tag = 'ThumbnailResolutionUnit'; break;  // values 1-3
+            case '0201': $tag = 'JpegIFOffset'; break;
+            case '0202': $tag = 'JpegIFByteCount'; break;
+            case '0212': $tag = 'YCbCrSubSampling'; break;
+
+            // misc
+            case '00ff': $tag = 'SubfileType'; break;
+            case '012d': $tag = 'TransferFunction'; break;
+            case '013d': $tag = 'Predictor'; break;
+            case '0142': $tag = 'TileWidth'; break;
+            case '0143': $tag = 'TileLength'; break;
+            case '0144': $tag = 'TileOffsets'; break;
+            case '0145': $tag = 'TileByteCounts'; break;
+            case '014a': $tag = 'SubIFDs'; break;
+            case '015b': $tag = 'JPEGTables'; break;
+            case '828d': $tag = 'CFARepeatPatternDim'; break;
+            case '828e': $tag = 'CFAPattern'; break;
+            case '828f': $tag = 'BatteryLevel'; break;
+            case '83bb': $tag = 'IPTC/NAA'; break;
+            case '8773': $tag = 'InterColorProfile'; break;
+
+            case '8828': $tag = 'OECF'; break;
+            case '8829': $tag = 'Interlace'; break;
+            case '882a': $tag = 'TimeZoneOffset'; break;
+            case '882b': $tag = 'SelfTimerMode'; break;
+            case '920b': $tag = 'FlashEnergy'; break;
+            case '920c': $tag = 'SpatialFrequencyResponse'; break;
+            case '920d': $tag = 'Noise'; break;
+            case '9211': $tag = 'ImageNumber'; break;
+            case '9212': $tag = 'SecurityClassification'; break;
+            case '9214': $tag = 'SubjectLocation'; break;
+            case '9215': $tag = 'ExposureIndex'; break;
+            case '9216': $tag = 'TIFF/EPStandardID'; break;
+            case 'a20b': $tag = 'FlashEnergy'; break;
+
+            default: $tag = 'unknown:'.$tag; break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     *
+     * @param $type
+     * @param $tag
+     * @param $intel
+     * @param $data
+     * @return unknown_type
+     */
+    static protected function _formatData($type, $tag, $intel, $data)
+    {
+        if ($type == 'ASCII') {
+            // Search for a null byte and stop there.
+            if (($pos = strpos($data, chr(0))) !== false) {
+                $data = substr($data, 0, $pos);
+            }
+            // Format certain kinds of strings nicely (Camera make etc.)
+            if ($tag == '010f') {
+                $data = ucwords(strtolower(trim($data)));
+            }
+
+        } elseif ($type == 'URATIONAL' || $type == 'SRATIONAL') {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+
+            if ($intel == 1) {
+                $top = hexdec(substr($data,8,8)); // intel stores them bottom-top
+            } else {
+                $top = hexdec(substr($data,0,8)); // motorola stores them top-bottom
+            }
+
+            if ($intel == 1) {
+                $bottom = hexdec(substr($data,0,8));  // intel stores them bottom-top
+            } else {
+                $bottom = hexdec(substr($data,8,8));  // motorola stores them top-bottom
+            }
+
+            if ($type == 'SRATIONAL' && $top > 2147483647) {
+                // this makes the number signed instead of unsigned
+                $top = $top - 4294967296;
+            }
+            if ($bottom != 0) {
+                $data = $top / $bottom;
+            } elseif ($top == 0) {
+                $data = 0;
+            } else {
+                $data = $top . '/' . $bottom;
+            }
+
+            // Exposure Time
+            if ($tag == '829a') {
+                if ($bottom != 0) {
+                    $data = $top . '/' . $bottom;
+                } else {
+                    $data = 0;
+                }
+            }
+
+        } elseif ($type == 'USHORT' || $type == 'SSHORT' || $type == 'ULONG' ||
+                  $type == 'SLONG' || $type == 'FLOAT' || $type == 'DOUBLE') {
+
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            if ($intel == 0 && ($type == 'USHORT' || $type == 'SSHORT')) {
+                $data = substr($data, 0, 4);
+            }
+            $data = hexdec($data);
+            if ($type == 'SSHORT' && $data > 32767) {
+                // this makes the number signed instead of unsigned
+                $data = $data - 65536;
+            }
+            if ($type == 'SLONG' && $data > 2147483647) {
+                // this makes the number signed instead of unsigned
+                $data = $data - 4294967296;
+            }
+        } elseif ($type == 'UNDEFINED') {
+            // ExifVersion,FlashPixVersion,InteroperabilityVersion
+            if ($tag == '9000' || $tag == 'a000' || $tag == '0002') {
+                $data = sprintf(_("version %d"), $data / 100);
+            }
+        } else {
+            $data = bin2hex($data);
+            if ($intel == 1) $data = Horde_Image_Exif::intel2Moto($data);
+        }
+
+        return $data;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Base.php b/framework/Image/lib/Horde/Image/Exif/Parser/Base.php
new file mode 100644 (file)
index 0000000..7d164eb
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ *
+ */
+class Horde_Image_Exif_Parser_Base
+{
+    /**
+     *
+     * @param $type
+     * @param $size
+     * @return unknown_type
+     */
+    static protected function _lookupType(&$type,&$size) {
+        switch($type) {
+            case '0001': $type = 'UBYTE'; $size=1; break;
+            case '0002': $type = 'ASCII'; $size=1; break;
+            case '0003': $type = 'USHORT'; $size=2; break;
+            case '0004': $type = 'ULONG'; $size=4; break;
+            case '0005': $type = 'URATIONAL'; $size=8; break;
+            case '0006': $type = 'SBYTE'; $size=1; break;
+            case '0007': $type = 'UNDEFINED'; $size=1; break;
+            case '0008': $type = 'SSHORT'; $size=2; break;
+            case '0009': $type = 'SLONG'; $size=4; break;
+            case '000a': $type = 'SRATIONAL'; $size=8; break;
+            case '000b': $type = 'FLOAT'; $size=4; break;
+            case '000c': $type = 'DOUBLE'; $size=8; break;
+            default: $type = 'error:'.$type; $size=0; break;
+        }
+
+        return $type;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Canon.php b/framework/Image/lib/Horde/Image/Exif/Parser/Canon.php
new file mode 100644 (file)
index 0000000..460f462
--- /dev/null
@@ -0,0 +1,538 @@
+<?php
+/**
+ *
+ * @author mrubinsk
+ */
+
+/**
+ *   Exifer
+ *   Extracts EXIF information from digital photos.
+ *
+ *   Copyright 2003 Jake Olefsky
+ *   http://www.offsky.com/software/exif/index.php
+ *   jake@olefsky.com
+ *
+ *   Please see exif.php for the complete information about this software.
+ *
+ *   ------------
+ *
+ *   This program is free software; you can redistribute it and/or modify it under the terms of
+ *   the GNU General Public License as published by the Free Software Foundation; either version 2
+ *   of the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ *   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *   See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+ */
+class Horde_Image_Exif_Parser_Canon extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     *Looks up the name of the tag for the MakerNote (Depends on Manufacturer)
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch($tag) {
+        case '0001': $tag = 'Settings 1'; break;
+        case '0004': $tag = 'Settings 4'; break;
+        case '0006': $tag = 'ImageType'; break;
+        case '0007': $tag = 'FirmwareVersion'; break;
+        case '0008': $tag = 'ImageNumber'; break;
+        case '0009': $tag = 'OwnerName'; break;
+        case '000c': $tag = 'CameraSerialNumber'; break;
+        case '000f': $tag = 'CustomFunctions'; break;
+        default: $tag = sprintf(_("Unknown: (%s)"), $tag); break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     * Formats Data for the data type
+     */
+    static protected function _formatData($type, $tag, $intel, $data, $exif, &$result)
+    {
+        $place = 0;
+
+        if ($type == 'ASCII') {
+            $result = $data = str_replace('\0', '', $data);
+        } elseif ($type == 'URATIONAL' || $type == 'SRATIONAL') {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            $top = hexdec(substr($data, 8, 8));
+            $bottom = hexdec(substr($data, 0, 8));
+            if ($bottom != 0) {
+                $data = $top / $bottom;
+            } elseif ($top == 0) {
+                $data = 0;
+            } else {
+                $data = $top . '/' . $bottom;
+            }
+
+            if ($tag == '0204') { //DigitalZoom
+                $data = $data . 'x';
+            }
+        } elseif ($type == 'USHORT' || $type == 'SSHORT' || $type == 'ULONG' ||
+                  $type == 'SLONG' || $type == 'FLOAT' || $type == 'DOUBLE') {
+
+            $data = bin2hex($data);
+            $result['RAWDATA'] = $data;
+
+            if ($tag == '0001') { //first chunk
+                $result['Bytes'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;
+                if ($result['Bytes'] != strlen($data) / 2) {
+                    return $result; //Bad chunk
+                }
+                $result['Macro'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//1
+                switch($result['Macro']) {
+                case 1: $result['Macro'] = _("Macro"); break;
+                case 2: $result['Macro'] = _("Normal"); break;
+                default: $result['Macro'] = _("Unknown");
+                }
+                $result['SelfTimer'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//2
+                switch($result['SelfTimer']) {
+                case 0: $result['SelfTimer'] = _("Off"); break;
+                default: $result['SelfTimer'] .= _("/10s");
+                }
+                $result['Quality'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//3
+                switch($result['Quality']) {
+                case 2: $result['Quality'] = _("Normal"); break;
+                case 3: $result['Quality'] = _("Fine"); break;
+                case 5: $result['Quality'] = _("Superfine"); break;
+                default: $result['Quality'] = _("Unknown");
+                }
+                $result['Flash'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//4
+                switch($result['Flash']) {
+                case 0: $result['Flash'] = _("Off"); break;
+                case 1: $result['Flash'] = _("Auto"); break;
+                case 2: $result['Flash'] = _("On"); break;
+                case 3: $result['Flash'] = _("Red Eye Reduction"); break;
+                case 4: $result['Flash'] = _("Slow Synchro"); break;
+                case 5: $result['Flash'] = _("Auto + Red Eye Reduction"); break;
+                case 6: $result['Flash'] = _("On + Red Eye Reduction"); break;
+                case 16: $result['Flash'] = _("External Flash"); break;
+                default: $result['Flash'] = _("Unknown");
+                }
+                $result['DriveMode'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//5
+                switch($result['DriveMode']) {
+                case 0: $result['DriveMode'] = _("Single/Timer"); break;
+                case 1: $result['DriveMode'] = _("Continuous"); break;
+                default: $result['DriveMode'] = _("Unknown");
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//6
+                $result['FocusMode'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//7
+                switch($result['FocusMode']) {
+                case 0: $result['FocusMode'] = _("One-Shot"); break;
+                case 1: $result['FocusMode'] = _("AI Servo"); break;
+                case 2: $result['FocusMode'] = _("AI Focus"); break;
+                case 3: $result['FocusMode'] = _("Manual Focus"); break;
+                case 4: $result['FocusMode'] = _("Single"); break;
+                case 5: $result['FocusMode'] = _("Continuous"); break;
+                case 6: $result['FocusMode'] = _("Manual Focus"); break;
+                default: $result['FocusMode'] = _("Unknown");
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//8
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place,4 )));
+                $place+=4;//9
+                $result['ImageSize'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//10
+                switch($result['ImageSize']) {
+                case 0: $result['ImageSize'] = _("Large"); break;
+                case 1: $result['ImageSize'] = _("Medium"); break;
+                case 2: $result['ImageSize'] = _("Small"); break;
+                default: $result['ImageSize'] = _("Unknown");
+                }
+                $result['EasyShooting'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//11
+                switch($result['EasyShooting']) {
+                case 0: $result['EasyShooting'] = _("Full Auto"); break;
+                case 1: $result['EasyShooting'] = _("Manual"); break;
+                case 2: $result['EasyShooting'] = _("Landscape"); break;
+                case 3: $result['EasyShooting'] = _("Fast Shutter"); break;
+                case 4: $result['EasyShooting'] = _("Slow Shutter"); break;
+                case 5: $result['EasyShooting'] = _("Night"); break;
+                case 6: $result['EasyShooting'] = _("Black & White"); break;
+                case 7: $result['EasyShooting'] = _("Sepia"); break;
+                case 8: $result['EasyShooting'] = _("Portrait"); break;
+                case 9: $result['EasyShooting'] = _("Sport"); break;
+                case 10: $result['EasyShooting'] = _("Macro/Close-Up"); break;
+                case 11: $result['EasyShooting'] = _("Pan Focus"); break;
+                default: $result['EasyShooting'] = _("Unknown");
+                }
+                $result['DigitalZoom'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//12
+                switch($result['DigitalZoom']) {
+                case 0:
+                case 65535: $result['DigitalZoom'] = _("None"); break;
+                case 1: $result['DigitalZoom'] = _("2x"); break;
+                case 2: $result['DigitalZoom'] = _("4x"); break;
+                default: $result['DigitalZoom'] = _("Unknown");
+                }
+                $result['Contrast'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//13
+                switch($result['Contrast']) {
+                case 0: $result['Contrast'] = _("Normal"); break;
+                case 1: $result['Contrast'] = _("High"); break;
+                case 65535: $result['Contrast'] = _("Low"); break;
+                default: $result['Contrast'] = _("Unknown");
+                }
+                $result['Saturation'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//14
+                switch($result['Saturation']) {
+                   case 0: $result['Saturation'] = _("Normal"); break;
+                    case 1: $result['Saturation'] = _("High"); break;
+                    case 65535: $result['Saturation'] = _("Low"); break;
+                    default: $result['Saturation'] = _("Unknown");
+                }
+                $result['Sharpness'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//15
+                switch($result['Sharpness']) {
+                case 0: $result['Sharpness'] = _("Normal"); break;
+                case 1: $result['Sharpness'] = _("High"); break;
+                case 65535: $result['Sharpness'] = _("Low"); break;
+                default: $result['Sharpness'] = _("Unknown");
+                }
+                $result['ISO'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//16
+                switch($result['ISO']) {
+                case 32767:
+                case 0:
+                    $result['ISO'] = isset($exif['SubIFD']['ISOSpeedRatings']) ?
+                        $exif['SubIFD']['ISOSpeedRatings'] :
+                        'Unknown';
+                     break;
+                case 15:
+                    $result['ISO'] = _("Auto");
+                    break;
+                case 16:
+                    $result['ISO'] = 50;
+                     break;
+                case 17:
+                    $result['ISO'] = 100;
+                     break;
+                case 18:
+                    $result['ISO'] = 200;
+                     break;
+                case 19:
+                    $result['ISO'] = 400;
+                     break;
+                default:
+                    $result['ISO'] = _("Unknown");
+                }
+                $result['MeteringMode'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//17
+                switch($result['MeteringMode']) {
+                case 3: $result['MeteringMode'] = _("Evaluative"); break;
+                case 4: $result['MeteringMode'] = _("Partial"); break;
+                case 5: $result['MeteringMode'] = _("Center-weighted"); break;
+                default: $result['MeteringMode'] = _("Unknown");
+                }
+                $result['FocusType'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//18
+                switch($result['FocusType']) {
+                case 0: $result['FocusType'] = _("Manual"); break;
+                case 1: $result['FocusType'] = _("Auto"); break;
+                case 3: $result['FocusType'] = _("Close-up (Macro)"); break;
+                case 8: $result['FocusType'] = _("Locked (Pan Mode)"); break;
+                default: $result['FocusType'] = _("Unknown");
+                }
+                $result['AFPointSelected'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//19
+                switch($result['AFPointSelected']) {
+                case 12288: $result['AFPointSelected'] = _("Manual Focus"); break;
+                case 12289: $result['AFPointSelected'] = _("Auto Selected"); break;
+                case 12290: $result['AFPointSelected'] = _("Right"); break;
+                case 12291: $result['AFPointSelected'] = _("Center"); break;
+                case 12292: $result['AFPointSelected'] = _("Left"); break;
+                default: $result['AFPointSelected'] = _("Unknown");
+                }
+                $result['ExposureMode'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//20
+                switch($result['ExposureMode']) {
+                case 0: $result['ExposureMode'] = _("EasyShoot"); break;
+                case 1: $result['ExposureMode'] = _("Program"); break;
+                case 2: $result['ExposureMode'] = _("Tv"); break;
+                case 3: $result['ExposureMode'] = _("Av"); break;
+                case 4: $result['ExposureMode'] = _("Manual"); break;
+                case 5: $result['ExposureMode'] = _("Auto-DEP"); break;
+                default: $result['ExposureMode'] = _("Unknown");
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//21
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//22
+                $result['LongFocalLength'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//23
+                $result['LongFocalLength'] .=  'focal units';
+                $result['ShortFocalLength'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//24
+                $result['ShortFocalLength'] .= ' focal units';
+                $result['FocalUnits'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//25
+                 $result['FocalUnits'] .= ' per mm';
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//26
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//27
+                $result['FlashActivity'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//28
+                switch($result['FlashActivity']) {
+                case 0: $result['FlashActivity'] = _("Flash Did Not Fire"); break;
+                case 1: $result['FlashActivity'] = _("Flash Fired"); break;
+                default: $result['FlashActivity'] = _("Unknown");
+                }
+                $result['FlashDetails'] = str_pad(base_convert(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)), 16, 2), 16, '0', STR_PAD_LEFT);
+                $place += 4;//29
+                $flashDetails = array();
+                if (substr($result['FlashDetails'], 1, 1) == 1) {
+                    $flashDetails[] = _("External E-TTL");
+                }
+                if (substr($result['FlashDetails'], 2, 1) == 1) {
+                    $flashDetails[] = _("Internal Flash");
+                }
+                if (substr($result['FlashDetails'], 4, 1) == 1) {
+                    $flashDetails[] = _("FP sync used");
+                }
+                if (substr($result['FlashDetails'], 8, 1) == 1) {
+                    $flashDetails[] = _("2nd(rear)-curtain sync used");
+                 }
+                if (substr($result['FlashDetails'], 12, 1) == 1) {
+                    $flashDetails[] = _("1st curtain sync");
+                }
+                $result['FlashDetails'] = implode(',', $flashDetails);
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//30
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//31
+                $anotherFocusMode = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//32
+                if (strpos(strtoupper($exif['IFD0']['Model']), 'G1') !== false) {
+                    switch($anotherFocusMode) {
+                    case 0: $result['FocusMode'] = _("Single"); break;
+                    case 1: $result['FocusMode'] = _("Continuous"); break;
+                    default: $result['FocusMode'] = _("Unknown");
+                    }
+                }
+
+            } elseif ($tag=='0004') { //second chunk
+                $result['Bytes']=hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//0
+                if ($result['Bytes'] != strlen($data) / 2) {
+                    return $result; //Bad chunk
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//1
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//2
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//3
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//4
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//5
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//6
+                $result['WhiteBalance'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//7
+                switch($result['WhiteBalance']) {
+                case 0: $result['WhiteBalance'] = _("Auto"); break;
+                case 1: $result['WhiteBalance'] = _("Sunny"); break;
+                case 2: $result['WhiteBalance'] = _("Cloudy"); break;
+                case 3: $result['WhiteBalance'] = _("Tungsten"); break;
+                case 4: $result['WhiteBalance'] = _("Fluorescent"); break;
+                case 5: $result['WhiteBalance'] = _("Flash"); break;
+                case 6: $result['WhiteBalance'] = _("Custom"); break;
+                default: $result['WhiteBalance'] = _("Unknown");
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//8
+                $result['SequenceNumber'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//9
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//10
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//11
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data ,$place, 4)));
+                $place += 4;//12
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//13
+                $result['AFPointUsed']=hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//14
+                $afPointUsed = array();
+                if ($result['AFPointUsed'] & 0x0001) {
+                    $afPointUsed[] = _("Right"); //bit 0
+                }
+                if ($result['AFPointUsed'] & 0x0002) {
+                    $afPointUsed[] = _("Center"); //bit 1
+                }
+                if ($result['AFPointUsed'] & 0x0004) {
+                    $afPointUsed[] = _("Left"); //bit 2
+                }
+                if ($result['AFPointUsed'] & 0x0800) {
+                    $afPointUsed[] = 12; //bit 12
+                }
+                if ($result['AFPointUsed'] & 0x1000) {
+                    $afPointUsed[] = 13; //bit 13
+                }
+                if ($result['AFPointUsed'] & 0x2000) {
+                    $afPointUsed[] = 14; //bit 14
+                }
+                if ($result['AFPointUsed'] & 0x4000) {
+                    $afPointUsed[] = 15; //bit 15
+                }
+                $result['AFPointUsed'] = implode(',', $afPointUsed);
+                $result['FlashBias'] = Horde_Image_Exif::intel2Moto(substr($data, $place, 4));
+                $place += 4;//15
+                switch($result['FlashBias']) {
+                case 'ffc0': $result['FlashBias'] = '-2 EV'; break;
+                case 'ffcc': $result['FlashBias'] = '-1.67 EV'; break;
+                case 'ffd0': $result['FlashBias'] = '-1.5 EV'; break;
+                case 'ffd4': $result['FlashBias'] = '-1.33 EV'; break;
+                case 'ffe0': $result['FlashBias'] = '-1 EV'; break;
+                case 'ffec': $result['FlashBias'] = '-0.67 EV'; break;
+                case 'fff0': $result['FlashBias'] = '-0.5 EV'; break;
+                case 'fff4': $result['FlashBias'] = '-0.33 EV'; break;
+                case '0000': $result['FlashBias'] = '0 EV'; break;
+                case '000c': $result['FlashBias'] = '0.33 EV'; break;
+                case '0010': $result['FlashBias'] = '0.5 EV'; break;
+                case '0014': $result['FlashBias'] = '0.67 EV'; break;
+                case '0020': $result['FlashBias'] = '1 EV'; break;
+                case '002c': $result['FlashBias'] = '1.33 EV'; break;
+                case '0030': $result['FlashBias'] = '1.5 EV'; break;
+                case '0034': $result['FlashBias'] = '1.67 EV'; break;
+                case '0040': $result['FlashBias'] = '2 EV'; break;
+                default: $result['FlashBias'] = _("Unknown");
+                }
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//16
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//17
+                $result['Unknown'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//18
+                $result['SubjectDistance'] = hexdec(Horde_Image_Exif::intel2Moto(substr($data, $place, 4)));
+                $place += 4;//19
+                $result['SubjectDistance'] .= '/100 m';
+
+            } elseif ($tag=='0008') { //image number
+                if ($intel == 1) {
+                    $data = Horde_Image_Exif::intel2Moto($data);
+                }
+                $data = hexdec($data);
+                $result = round($data / 10000) . '-' . $data % 10000;
+            } elseif ($tag == '000c') { //camera serial number
+                if ($intel == 1) {
+                    $data = Horde_Image_Exif::intel2Moto($data);
+                }
+                $data = hexdec($data);
+                $result = '#' . bin2hex(substr($data, 0, 16)) . substr($data, 16, 16);
+            }
+
+        } elseif ($type != 'UNDEFINED') {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Cannon Special data section
+     * Useful:  http://www.burren.cx/david/canon.html
+     * http://www.burren.cx/david/canon.html
+     * http://www.ozhiker.com/electronics/pjmt/jpeg_info/canon_mn.html
+     */
+    static public function parse($block, &$result, $seek, $globalOffset)
+    {
+        $place = 0; //current place
+        if ($result['Endien'] == 'Intel') {
+            $intel = 1;
+        } else {
+            $intel = 0;
+        }
+
+        $model = $result['IFD0']['Model'];
+
+        //Get number of tags (2 bytes)
+        $num = bin2hex(substr($block, $place, 2));
+        $place += 2;
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = hexdec($num);
+
+        //loop thru all tags  Each field is 12 bytes
+        for ($i = 0; $i < hexdec($num); $i++) {
+             //2 byte tag
+            $tag = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $tag = Horde_Image_Exif::intel2Moto($tag);
+            }
+            $tag_name = self::_lookupTag($tag);
+
+            //2 byte type
+            $type = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $type = Horde_Image_Exif::intel2Moto($type);
+            }
+            self::_lookupType($type, $size);
+
+            //4 byte count of number of data units
+            $count = bin2hex(substr($block, $place, 4));
+            $place += 4;
+            if ($intel == 1) {
+                $count = Horde_Image_Exif::intel2Moto($count);
+            }
+            $bytesofdata = $size * hexdec($count);
+
+            if ($bytesofdata <= 0) {
+                return; //if this value is 0 or less then we have read all the tags we can
+            }
+
+                //4 byte value of data or pointer to data
+            $value = substr($block, $place, 4);
+            $place += 4;
+
+            if ($bytesofdata <= 4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if ($intel == 1) {
+                    $value = Horde_Image_Exif::intel2Moto($value);
+                }
+                $v = fseek($seek, $globalOffset+hexdec($value));  //offsets are from TIFF header which is 12 bytes from the start of the file
+                if (isset($GLOBALS['exiferFileSize'])) {
+                    $exiferFileSize = $GLOBALS['exiferFileSize'];
+                } else {
+                    $exiferFileSize = 0;
+                }
+                if ($v == 0 && $bytesofdata < $exiferFileSize) {
+                    $data = fread($seek, $bytesofdata);
+                } elseif ($v == -1) {
+                    $result['Errors'] = $result['Errors']++;
+                    $data = '';
+                } else {
+                    $data = '';
+                }
+            }
+            $result['SubIFD']['MakerNote'][$tag_name] = ''; // insure the index exists
+            $formated_data = self::_formatData($type, $tag, $intel, $data, $result, $result['SubIFD']['MakerNote'][$tag_name]);
+            $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+        }
+
+    }
+
+}
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Fujufilm.php b/framework/Image/lib/Horde/Image/Exif/Parser/Fujufilm.php
new file mode 100644 (file)
index 0000000..5f34196
--- /dev/null
@@ -0,0 +1,247 @@
+<?php
+/**
+ *
+ */
+
+/**
+ *   Exifer
+ *   Extracts EXIF information from digital photos.
+ *
+ *   Copyright � 2003 Jake Olefsky
+ *   http://www.offsky.com/software/exif/index.php
+ *   jake@olefsky.com
+ *
+ *   Please see exif.php for the complete information about this software.
+ *
+ *   ------------
+ *
+ *  This program is free software; you can redistribute it and/or modify it under the terms of
+ *    the GNU General Public License as published by the Free Software Foundation; either version 2
+ *    of the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ *   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *   See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+ */
+class Horde_Image_Exif_Parser_Fujifilm extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     * Looks up the name of the tag for the MakerNote (Depends on Manufacturer)
+     */
+    static protected function _lookupTag($tag) {
+
+        switch($tag) {
+            case "0000": $tag = "Version";break;
+            case "1000": $tag = "Quality";break;
+            case "1001": $tag = "Sharpness";break;
+            case "1002": $tag = "WhiteBalance";break;
+            case "1003": $tag = "Color";break;
+            case "1004": $tag = "Tone";break;
+            case "1010": $tag = "FlashMode";break;
+            case "1011": $tag = "FlashStrength";break;
+            case "1020": $tag = "Macro";break;
+            case "1021": $tag = "FocusMode";break;
+            case "1030": $tag = "SlowSync";break;
+            case "1031": $tag = "PictureMode";break;
+            case "1032": $tag = "Unknown";break;
+            case "1100": $tag = "ContinuousTakingBracket";break;
+            case "1200": $tag = "Unknown";break;
+            case "1300": $tag = "BlurWarning";break;
+            case "1301": $tag = "FocusWarning";break;
+            case "1302": $tag = "AEWarning";break;
+
+            default: $tag = "unknown:".$tag;break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     *
+     * @param $type
+     * @param $tag
+     * @param $intel
+     * @param $data
+     * @return unknown_type
+     */
+    static protected function _formatData($type, $tag, $intel, $data)
+    {
+        if($type=="ASCII") {
+
+
+        } else if($type=="URATIONAL" || $type=="SRATIONAL") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $top = hexdec(substr($data,8,8));
+            $bottom = hexdec(substr($data,0,8));
+            if($bottom!=0) $data=$top/$bottom;
+            else if($top==0) $data = 0;
+            else $data=$top."/".$bottom;
+
+            if($tag=="1011") { //FlashStrength
+                $data=$data." EV";
+            }
+
+        } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $data=hexdec($data);
+
+            if($tag=="1001") { //Sharpness
+                if($data == 1) $data = _("Soft");
+                else if($data == 2) $data = _("Soft");
+                else if($data == 3) $data = _("Normal");
+                else if($data == 4) $data = _("Hard");
+                else if($data == 5) $data = _("Hard");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="1002") { //WhiteBalance
+                if($data == 0) $data = _("Auto");
+                else if($data == 256) $data = _("Daylight");
+                else if($data == 512) $data = _("Cloudy");
+                else if($data == 768) $data = _("DaylightColor-fluorescence");
+                else if($data == 769) $data = _("DaywhiteColor-fluorescence");
+                else if($data == 770) $data = _("White-fluorescence");
+                else if($data == 1024) $data = _("Incandescense");
+                else if($data == 3840) $data = _("Custom");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="1003") { //Color
+                if($data == 0) $data = _("Chroma Saturation Normal(STD)");
+                else if($data == 256) $data = _("Chroma Saturation High");
+                else if($data == 512) $data = _("Chroma Saturation Low(ORG)");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1004") { //Tone
+                if($data == 0) $data = _("Contrast Normal(STD)");
+                else if($data == 256) $data = _("Contrast High(HARD)");
+                else if($data == 512) $data = _("Contrast Low(ORG)");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1010") { //FlashMode
+                if($data == 0) $data = _("Auto");
+                else if($data == 1) $data = _("On");
+                else if($data == 2) $data = _("Off");
+                else if($data == 3) $data = _("Red-Eye Reduction");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1020") { //Macro
+                if($data == 0) $data = _("Off");
+                else if($data == 1) $data = _("On");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1021") { //FocusMode
+                if($data == 0) $data = _("Auto");
+                else if($data == 1) $data = _("Manual");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1030") { //SlowSync
+                if($data == 0) $data = _("Off");
+                else if($data == 1) $data = _("On");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1031") { //PictureMode
+                if($data == 0) $data = _("Auto");
+                else if($data == 1) $data = _("Portrait");
+                else if($data == 2) $data = _("Landscape");
+                else if($data == 4) $data = _("Sports");
+                else if($data == 5) $data = _("Night");
+                else if($data == 6) $data = _("Program AE");
+                else if($data == 256) $data = _("Aperture Prority AE");
+                else if($data == 512) $data = _("Shutter Priority");
+                else if($data == 768) $data = _("Manual Exposure");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1100") { //ContinuousTakingBracket
+                if($data == 0) $data = _("Off");
+                else if($data == 1) $data = _("On");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1300") { //BlurWarning
+                if($data == 0) $data = _("No Warning");
+                else if($data == 1) $data = _("Warning");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1301") { //FocusWarning
+                if($data == 0) $data = _("Auto Focus Good");
+                else if($data == 1) $data = _("Out of Focus");
+                else $data = _("Unknown: ").$data;
+            }
+            if($tag=="1302") { //AEWarning
+                if($data == 0) $data = _("AE Good");
+                else if($data == 1) $data = _("Over Exposure");
+                else $data = _("Unknown: ").$data;
+            }
+        } else if($type=="UNDEFINED") {
+
+
+
+        } else {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+        }
+
+        return $data;
+    }
+
+    /**
+     *
+     * @param $block
+     * @param $result
+     * @return unknown_type
+     */
+    static public function parse($block, &$result)
+    {
+        $intel=1;
+
+        $model = $result['IFD0']['Model'];
+
+        $place=8; //current place
+        $offset=8;
+
+
+        $num = bin2hex(substr($block,$place,4));$place+=4;
+        if($intel==1) $num = Horde_Image_Exif::intel2Moto($num);
+        $result['SubIFD']['MakerNote']['Offset'] = hexdec($num);
+
+            //Get number of tags (2 bytes)
+        $num = bin2hex(substr($block,$place,2));$place+=2;
+        if($intel==1) $num = Horde_Image_Exif::intel2Moto($num);
+        $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = hexdec($num);
+
+        //loop thru all tags  Each field is 12 bytes
+        for($i=0;$i<hexdec($num);$i++) {
+
+                //2 byte tag
+            $tag = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $tag = Horde_Image_Exif::intel2Moto($tag);
+            $tag_name = self::_lookupTag($tag);
+
+                //2 byte type
+            $type = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $type = Horde_Image_Exif::intel2Moto($type);
+            self::_lookupType($type,$size);
+
+                //4 byte count of number of data units
+            $count = bin2hex(substr($block,$place,4));$place+=4;
+            if($intel==1) $count = Horde_Image_Exif::intel2Moto($count);
+            $bytesofdata = $size*hexdec($count);
+
+                //4 byte value of data or pointer to data
+            $value = substr($block,$place,4);$place+=4;
+
+
+            if($bytesofdata<=4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if($intel==1) $value = Horde_Image_Exif::intel2Moto($value);
+                $data = substr($block,hexdec($value)-$offset,$bytesofdata*2);
+            }
+            $formated_data = self::_formatData($type,$tag,$intel,$data);
+            $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Gps.php b/framework/Image/lib/Horde/Image/Exif/Parser/Gps.php
new file mode 100644 (file)
index 0000000..ab8df41
--- /dev/null
@@ -0,0 +1,277 @@
+<?php
+/**
+ *
+ */
+
+/**
+    Exifer
+    Extracts EXIF information from digital photos.
+
+    Copyright © 2003 Jake Olefsky
+    http://www.offsky.com/software/exif/index.php
+    jake@olefsky.com
+
+    Please see exif.php for the complete information about this software.
+
+    ------------
+
+    This program is free software; you can redistribute it and/or modify it under the terms of
+    the GNU General Public License as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+*/
+class Horde_Image_Exif_Parser_Gps extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     * Looks up the name of the tag
+     *
+     * @param unknown_type $tag
+     * @return string
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch($tag) {
+            case "0000": return "Version";
+            case "0001": return "LatitudeRef";              //north or south
+            case "0002": return "Latitude";                                //dd mm.mm or dd mm ss
+            case "0003": return "LongitudeRef";                        //east or west
+            case "0004": return "Longitude";                           //dd mm.mm or dd mm ss
+            case "0005": return "AltitudeRef";                     //sea level or below sea level
+            case "0006": return "Altitude";                                    //positive rational number
+            case "0007": return "Time";                                                //three positive rational numbers
+            case "0008": return "Satellite";                           //text string up to 999 bytes long
+            case "0009": return "ReceiveStatus";                       //in progress or interop
+            case "000a": return "MeasurementMode";                 //2D or 3D
+            case "000b": return "MeasurementPrecision";            //positive rational number
+            case "000c": return "SpeedUnit";                           //KPH, MPH, knots
+            case "000d": return "ReceiverSpeed";                       //positive rational number
+            case "000e": return "MovementDirectionRef";            //true or magnetic north
+            case "000f": return "MovementDirection";           //positive rational number
+            case "0010": return "ImageDirectionRef";           //true or magnetic north
+            case "0011": return "ImageDirection";                      //positive rational number
+            case "0012": return "GeodeticSurveyData";          //text string up to 999 bytes long
+            case "0013": return "DestLatitudeRef";                 //north or south
+            case "0014": return "DestinationLatitude";         //three positive rational numbers
+            case "0015": return "DestLongitudeRef";                //east or west
+            case "0016": return "DestinationLongitude";                //three positive rational numbers
+            case "0017": return "DestBearingRef";                      //true or magnetic north
+            case "0018": return "DestinationBearing";          //positive rational number
+            case "0019": return "DestDistanceRef";                 //km, miles, knots
+            case "001a": return "DestinationDistance";     //positive rational number
+            case "001b": return "ProcessingMethod";
+            case "001c": return "AreaInformation";
+            case "001d": return "Datestamp";                       //text string 10 bytes long
+            case "001e": return "DifferentialCorrection";  //integer in range 0-65535
+            default: return "unknown:".$tag;
+        }
+
+    }
+
+    /**
+     * Formats a rational number
+     */
+    static protected function _rational($data, $intel)
+    {
+
+        if ($intel == 1) {
+            $top = hexdec(substr($data, 8, 8));        //intel stores them bottom-top
+        } else {
+            $top = hexdec(substr($data, 0, 8));                //motorola stores them top-bottom
+        }
+
+        if ($intel == 1) {
+            $bottom = hexdec(substr($data, 0, 8));
+        } else {
+            $bottom = hexdec(substr($data, 8, 8));
+        }
+
+        if ($bottom!=0) {
+            $data = $top / $bottom;
+        } elseif ($top == 0) {
+            $data = 0;
+        } else {
+            $data = $top . "/" . $bottom;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Formats Data for the data type
+     */
+    static protected function _formatData($type, $tag, $intel, $data)
+    {
+
+        if($type == "ASCII") {
+            // Latitude Reference, Longitude Reference
+            if ($tag == "0001" || $tag == "0003") {
+                $data = ($data{1} == $data{2} && $data{1} == $data{3}) ? $data{0} : $data;
+            }
+        } elseif ($type == "URATIONAL" || $type == "SRATIONAL") {
+            $data = bin2hex($data);
+            if ($intel ==1 ) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            if ($intel == 1) {
+                $top = hexdec(substr($data, 8, 8)); //intel stores them bottom-top
+            } else {
+                $top = hexdec(substr($data, 0, 8));    //motorola stores them top-bottom
+            }
+
+            if ($intel == 1) {
+                $bottom = hexdec(substr($data, 0, 8));
+            } else {
+                $bottom = hexdec(substr($data, 8, 8));
+            }
+
+            if ($type == "SRATIONAL" && $top > 2147483647) {
+                 // make the number signed instead of unsigned
+                $top = $top - 4294967296;
+            }
+
+            //Latitude, Longitude
+            if ($tag=="0002" || $tag=="0004") {
+                if ($intel == 1) {
+                    $seconds = self::_rational(substr($data, 0, 16), $intel);
+                    $hour = self::_rational(substr($data, 32, 16), $intel);
+                } else {
+                    $hour = self::_rational(substr($data, 0, 16), $intel);
+                    $seconds = self::_rational(substr($data, 32, 16), $intel);
+                }
+                $minutes = self::_rational(substr($data, 16, 16), $intel);
+                $data = array($hour, $minutes, $seconds);
+            } elseif ($tag == "0007") { //Time
+                $seconds = self::_rational(substr($data, 0, 16), $intel);
+                $minutes = self::_rational(substr($data, 16, 16), $intel);
+                $hour = self::_rational(substr($data, 32, 16), $intel);
+                $data = $hour . ":" . $minutes . ":" . $seconds;
+            } else {
+                if ($bottom != 0) {
+                    $data = $top / $bottom;
+                } elseif ($top == 0) {
+                    $data = 0;
+                } else {
+                    $data = $top . "/" . $bottom;
+                }
+                if ($tag == "0006") {
+                    $data .= 'm';
+                }
+            }
+        } elseif ($type == "USHORT" || $type == "SSHORT" || $type == "ULONG" ||
+                  $type == "SLONG" || $type == "FLOAT" || $type == "DOUBLE") {
+
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            $data = hexdec($data);
+        } elseif ($type == "UNDEFINED") {
+        } elseif ($type == "UBYTE") {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $num = Horde_Image_Exif::intel2Moto($data);
+            }
+            if ($tag == "0000") { // VersionID
+                $data =  hexdec(substr($data, 0, 2))
+                         . '.' . hexdec(substr($data, 2, 2))
+                         . '.' . hexdec(substr($data, 4, 2))
+                         . '.'. hexdec(substr($data, 6, 2));
+            } elseif ($tag == "0005") { // Altitude Reference
+                if ($data == "00000000") {
+                    $data = 'Above Sea Level';
+                } elseif ($data == "01000000") {
+                    $data = 'Below Sea Level';
+                }
+            }
+        } else {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * GPS Special data section
+     * Useful websites
+     * http://drewnoakes.com/code/exif/sampleOutput.html
+     * http://www.geosnapper.com
+     */
+    static public function parse($block,&$result,$offset,$seek, $globalOffset)
+    {
+        if ($result['Endien'] == "Intel") {
+            $intel = 1;
+        } else {
+            $intel = 0;
+        }
+
+        //offsets are from TIFF header which is 12 bytes from the start of the file
+        $v = fseek($seek, $globalOffset + $offset);
+        if ($v == -1) {
+            $result['Errors'] = $result['Errors']++;
+        }
+
+        $num = bin2hex(fread($seek, 2));
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $num = hexdec($num);
+        $result['GPS']['NumTags'] = $num;
+        $block = fread($seek, $num * 12);
+        $place = 0;
+
+        //loop thru all tags  Each field is 12 bytes
+        for ($i = 0; $i < $num; $i++) {
+            //2 byte tag
+            $tag = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $tag = Horde_Image_Exif::intel2Moto($tag);
+            }
+            $tag_name = self::_lookpTag($tag);
+
+            //2 byte datatype
+            $type = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $type = Horde_Image_Exif::intel2Moto($type);
+            }
+            self::_lookupType($type, $size);
+
+            //4 byte number of elements
+            $count = bin2hex(substr($block, $place, 4));
+            $place += 4;
+            if ($intel==1) {
+                $count = Horde_Image_Exif::intel2Moto($count);
+            }
+            $bytesofdata = $size * hexdec($count);
+
+            //4 byte value or pointer to value if larger than 4 bytes
+            $value = substr($block, $place, 4);
+            $place += 4;
+
+            if ($bytesofdata <= 4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if ($intel == 1) {
+                    $value = Horde_Image_Exif::intel2Moto($value);
+                }
+                //offsets are from TIFF header which is 12 bytes from the start of the file
+                $v = fseek($seek, $globalOffset + hexdec($value));
+                if ($v == 0) {
+                    $data = fread($seek, $bytesofdata);
+                } elseif ($v == -1) {
+                    $result['Errors'] = $result['Errors']++;
+                }
+            }
+            $result['GPS' . $tag_name] = self::_formatData($type, $tag, $intel, $data);
+        }
+    }
+
+}
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Nikon.php b/framework/Image/lib/Horde/Image/Exif/Parser/Nikon.php
new file mode 100644 (file)
index 0000000..543ebdb
--- /dev/null
@@ -0,0 +1,318 @@
+<?php
+/**
+ *
+ */
+
+/*
+    Exifer
+    Extracts EXIF information from digital photos.
+
+    Copyright � 2003 Jake Olefsky
+    http://www.offsky.com/software/exif/index.php
+    jake@olefsky.com
+
+    Please see exif.php for the complete information about this software.
+
+    ------------
+
+    This program is free software; you can redistribute it and/or modify it under the terms of
+    the GNU General Public License as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+*/
+class Horde_Image_Exif_Parser_Nikon extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     *
+     * @param $tag
+     * @param $model
+     * @return unknown_type
+     */
+    static protected function _lookupTag($tag,$model)
+    {
+        if($model==0) {
+            switch($tag) {
+                case "0003": $tag = "Quality";break;
+                case "0004": $tag = "ColorMode";break;
+                case "0005": $tag = "ImageAdjustment";break;
+                case "0006": $tag = "CCDSensitivity";break;
+                case "0007": $tag = "WhiteBalance";break;
+                case "0008": $tag = "Focus";break;
+                case "0009": $tag = "Unknown2";break;
+                case "000a": $tag = "DigitalZoom";break;
+                case "000b": $tag = _("Converter");break;
+
+                default: $tag = "unknown:".$tag;break;
+            }
+        } else if($model==1) {
+            switch($tag) {
+                case "0002": $tag = "ISOSetting";break;
+                case "0003": $tag = "ColorMode";break;
+                case "0004": $tag = "Quality";break;
+                case "0005": $tag = "Whitebalance";break;
+                case "0006": $tag = "ImageSharpening";break;
+                case "0007": $tag = "FocusMode";break;
+                case "0008": $tag = "FlashSetting";break;
+                case "0009": $tag = "FlashMode";break;
+                case "000b": $tag = "WhiteBalanceFine";break;
+                case "000f": $tag = "ISOSelection";break;
+                case "0013": $tag = "ISOSelection2";break;
+                case "0080": $tag = "ImageAdjustment";break;
+                case "0081": $tag = "ToneCompensation";break;
+                case "0082": $tag = "Adapter";break;
+                case "0083": $tag = "LensType";break;
+                case "0084": $tag = "LensInfo";break;
+                case "0085": $tag = "ManualFocusDistance";break;
+                case "0086": $tag = "DigitalZoom";break;
+                case "0087": $tag = "FlashUsed";break;
+                case "0088": $tag = "AFFocusPosition";break;
+                case "008d": $tag = "ColorMode";break;
+                case "0090": $tag = "LightType";break;
+                case "0094": $tag = "Saturation";break;
+                case "0095": $tag = "NoiseReduction";break;
+                case "0010": $tag = "DataDump";break;
+
+                default: $tag = "unknown:".$tag;break;
+            }
+        }
+
+        return $tag;
+    }
+
+    /**
+     *
+     * @param $type
+     * @param $tag
+     * @param $intel
+     * @param $model
+     * @param $data
+     * @return unknown_type
+     */
+    static protected function _formatData($type,$tag,$intel,$model,$data)
+    {
+        if($type=="ASCII") {
+
+
+        } else if($type=="URATIONAL" || $type=="SRATIONAL") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $top = hexdec(substr($data,8,8));
+            $bottom = hexdec(substr($data,0,8));
+            if($bottom!=0) $data=$top/$bottom;
+            else if($top==0) $data = 0;
+            else $data=$top."/".$bottom;
+
+                     if($tag=="0085" && $model==1) { //ManualFocusDistance
+                $data=$data." m";
+            }
+            if($tag=="0086" && $model==1) { //DigitalZoom
+                $data=$data."x";
+            }
+            if($tag=="000a" && $model==0) { //DigitalZoom
+                $data=$data."x";
+            }
+        } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $data=hexdec($data);
+
+            if($tag=="0003" && $model==0) { //Quality
+                if($data == 1) $data = _("VGA Basic");
+                else if($data == 2) $data = _("VGA Normal");
+                else if($data == 3) $data = _("VGA Fine");
+                else if($data == 4) $data = _("SXGA Basic");
+                else if($data == 5) $data = _("SXGA Normal");
+                else if($data == 6) $data = _("SXGA Fine");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0004" && $model==0) { //Color
+                if($data == 1) $data = _("Color");
+                else if($data == 2) $data = _("Monochrome");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0005" && $model==0) { //Image Adjustment
+                if($data == 0) $data = _("Normal");
+                else if($data == 1) $data = _("Bright+");
+                else if($data == 2) $data = _("Bright-");
+                else if($data == 3) $data = _("Contrast+");
+                else if($data == 4) $data = _("Contrast-");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0006" && $model==0) { //CCD Sensitivity
+                if($data == 0) $data = "ISO-80";
+                else if($data == 2) $data = "ISO-160";
+                else if($data == 4) $data = "ISO-320";
+                else if($data == 5) $data = "ISO-100";
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0007" && $model==0) { //White Balance
+                if($data == 0) $data = _("Auto");
+                else if($data == 1) $data = _("Preset");
+                else if($data == 2) $data = _("Daylight");
+                else if($data == 3) $data = _("Incandescense");
+                else if($data == 4) $data = _("Flourescence");
+                else if($data == 5) $data = _("Cloudy");
+                else if($data == 6) $data = _("SpeedLight");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="000b" && $model==0) { //Converter
+                if($data == 0) $data = _("None");
+                else if($data == 1) $data = _("Fisheye");
+                else $data = _("Unknown").": ".$data;
+            }
+        } else if($type=="UNDEFINED") {
+
+            if($tag=="0001" && $model==1) { //Unknown (Version?)
+                $data=$data/100;
+            }
+            if($tag=="0088" && $model==1) { //AF Focus Position
+                $temp = _("Center");
+                $data = bin2hex($data);
+                $data = str_replace("01","Top",$data);
+                $data = str_replace("02","Bottom",$data);
+                $data = str_replace("03","Left",$data);
+                $data = str_replace("04","Right",$data);
+                $data = str_replace("00","",$data);
+                if(strlen($data)==0) $data = $temp;
+            }
+
+        } else {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+
+            if($tag=="0083" && $model==1) { //Lens Type
+                    $data = hexdec(substr($data,0,2));
+                if($data == 0) $data = _("AF non D");
+                else if($data == 1) $data = _("Manual");
+                else if($data == 2) $data = "AF-D or AF-S";
+                else if($data == 6) $data = "AF-D G";
+                else if($data == 10) $data = "AF-D VR";
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0087" && $model==1) { //Flash type
+                    $data = hexdec(substr($data,0,2));
+                if($data == 0) $data = _("Did Not Fire");
+                else if($data == 4) $data = _("Unknown");
+                else if($data == 7) $data = _("External");
+                else if($data == 9) $data = _("On Camera");
+                else $data = _("Unknown").": ".$data;
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     *
+     * @param $block
+     * @param $result
+     * @return unknown_type
+     */
+    static public function parse($block,&$result)
+    {
+        if($result['Endien']=="Intel") $intel=1;
+        else $intel=0;
+
+        $model = $result['IFD0']['Model'];
+
+        //these 6 models start with "Nikon".  Other models dont.
+        if($model=="E700\0" || $model=="E800\0" || $model=="E900\0" || $model=="E900S\0" || $model=="E910\0" || $model=="E950\0") {
+            $place=8; //current place
+            $model = 0;
+
+            //Get number of tags (2 bytes)
+            $num = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $num = Horde_Image_Exif::intel2Moto($num);
+            $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = hexdec($num);
+
+            //loop thru all tags  Each field is 12 bytes
+            for($i=0;$i<hexdec($num);$i++) {
+                //2 byte tag
+                $tag = bin2hex(substr($block,$place,2));$place+=2;
+                if($intel==1) $tag = Horde_Image_Exif::intel2Moto($tag);
+                $tag_name = self::_lookupTag($tag, $model);
+
+                //2 byte type
+                $type = bin2hex(substr($block,$place,2));$place+=2;
+                if($intel==1) $type = Horde_Image_Exif::intel2Moto($type);
+                self::_lookupType($type,$size);
+
+                //4 byte count of number of data units
+                $count = bin2hex(substr($block,$place,4));$place+=4;
+                if($intel==1) $count = Horde_Image_Exif::intel2Moto($count);
+                $bytesofdata = $size*hexdec($count);
+
+                //4 byte value of data or pointer to data
+                $value = substr($block,$place,4);$place+=4;
+
+                //if tag is 0002 then its the ASCII value which we know is at 140 so calc offset
+                //THIS HACK ONLY WORKS WITH EARLY NIKON MODELS
+                if($tag=="0002") $offset = hexdec($value)-140;
+                if($bytesofdata<=4) {
+                    $data = $value;
+                } else {
+                    $value = bin2hex($value);
+                    if($intel==1) $value = Horde_Image_Exif::intel2Moto($value);
+                    $data = substr($block,hexdec($value)-$offset,$bytesofdata*2);
+                }
+                $formated_data = self::_formatData($type,$tag,$intel,$model,$data);
+                $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+            }
+
+        } else {
+            $place=0;//current place
+            $model = 1;
+
+            $nikon = substr($block,$place,8);$place+=8;
+            $endien = substr($block,$place,4);$place+=4;
+
+            //2 bytes of 0x002a
+            $tag = bin2hex(substr($block,$place,2));$place+=2;
+
+            //Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header)
+            $offset = bin2hex(substr($block,$place,4));$place+=4;
+            if($intel==1) $offset = Horde_Image_Exif::intel2Moto($offset);
+            if(hexdec($offset)>8) $place+=$offset-8;
+
+            //Get number of tags (2 bytes)
+            $num = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $num = Horde_Image_Exif::intel2Moto($num);
+
+            //loop thru all tags  Each field is 12 bytes
+            for($i=0;$i<hexdec($num);$i++) {
+                //2 byte tag
+                $tag = bin2hex(substr($block,$place,2));$place+=2;
+                if($intel==1) $tag = Horde_Image_Exif::intel2Moto($tag);
+                $tag_name = self::_lookupTag($tag, $model);
+
+                //2 byte type
+                $type = bin2hex(substr($block,$place,2));$place+=2;
+                if($intel==1) $type = Horde_Image_Exif::intel2Moto($type);
+                self::_lookupType($type,$size);
+
+                //4 byte count of number of data units
+                $count = bin2hex(substr($block,$place,4));$place+=4;
+                if($intel==1) $count = Horde_Image_Exif::intel2Moto($count);
+                $bytesofdata = $size*hexdec($count);
+
+                //4 byte value of data or pointer to data
+                $value = substr($block,$place,4);$place+=4;
+
+                if($bytesofdata<=4) {
+                    $data = $value;
+                } else {
+                    $value = bin2hex($value);
+                    if($intel==1) $value = Horde_Image_Exif::intel2Moto($value);
+                    $data = substr($block,hexdec($value)+hexdec($offset)+2,$bytesofdata);
+                }
+                $formated_data = self::_formatData($type,$tag,$intel,$model,$data);
+                $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Olympus.php b/framework/Image/lib/Horde/Image/Exif/Parser/Olympus.php
new file mode 100644 (file)
index 0000000..99f6010
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ *
+ */
+
+/*
+    Exifer
+    Extracts EXIF information from digital photos.
+
+    Copyright � 2003 Jake Olefsky
+    http://www.offsky.com/software/exif/index.php
+    jake@olefsky.com
+
+    Please see exif.php for the complete information about this software.
+
+    ------------
+
+    This program is free software; you can redistribute it and/or modify it under the terms of
+    the GNU General Public License as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+*/
+class Horde_Image_Exif_Parser_Olympus extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     *
+     * @param $tag
+     * @return unknown_type
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch($tag) {
+            case "0200": $tag = "SpecialMode";break;
+            case "0201": $tag = "JpegQual";break;
+            case "0202": $tag = "Macro";break;
+            case "0203": $tag = "Unknown1";break;
+            case "0204": $tag = "DigiZoom";break;
+            case "0205": $tag = "Unknown2";break;
+            case "0206": $tag = "Unknown3";break;
+            case "0207": $tag = "SoftwareRelease";break;
+            case "0208": $tag = "PictInfo";break;
+            case "0209": $tag = "CameraID";break;
+            case "0f00": $tag = "DataDump";break;
+
+            default: $tag = "unknown:".$tag;break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     *
+     * @param $type
+     * @param $tag
+     * @param $intel
+     * @param $data
+     * @return unknown_type
+     */
+    static protected function _formatData($type,$tag,$intel,$data)
+    {
+        if($type=="ASCII") {
+
+        } else if($type=="URATIONAL" || $type=="SRATIONAL") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $top = hexdec(substr($data,8,8));
+            $bottom = hexdec(substr($data,0,8));
+            if($bottom!=0) $data=$top/$bottom;
+            else if($top==0) $data = 0;
+            else $data=$top."/".$bottom;
+
+            if($tag=="0204") { //DigitalZoom
+                $data=$data."x";
+            }
+            if($tag=="0205") { //Unknown2
+                $data=$top."/".$bottom;
+            }
+        } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $data=hexdec($data);
+
+            if($tag=="0201") { //JPEGQuality
+                if($data == 1) $data = "SQ";
+                else if($data == 2) $data = "HQ";
+                else if($data == 3) $data = "SHQ";
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0202") { //Macro
+                if($data == 0) $data = "Normal";
+                else if($data == 1) $data = "Macro";
+                else $data = _("Unknown").": ".$data;
+            }
+        } else if($type=="UNDEFINED") {
+
+        } else {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+        }
+
+        return $data;
+    }
+
+    /**
+     *
+     * @param $block
+     * @param $result
+     * @param $seek
+     * @param $globalOffset
+     * @return unknown_type
+     */
+    static public function parse($block, &$result, $seek, $globalOffset)
+    {
+        if($result['Endien']=="Intel") $intel = 1;
+        else $intel = 0;
+
+        $model = $result['IFD0']['Model'];
+
+        // New header for new DSLRs - Check for it because the
+        // number of bytes that count the IFD fields differ in each case.
+        // Fixed by Zenphoto 2/24/08
+        $new = false;
+        if (substr($block, 0, 8) == "OLYMPUS\x00") {
+            $new = true;
+        } else if (substr($block, 0, 7) == "OLYMP\x00\x01"
+            || substr($block, 0, 7) == "OLYMP\x00\x02") {
+            $new = false;
+        } else {
+            // Header does not match known Olympus headers.
+            // This is not a valid OLYMPUS Makernote.
+            return false;
+        }
+
+        // Offset of IFD entry after Olympus header.
+        $place = 8;
+        $offset = 8;
+
+        // Get number of tags (1 or 2 bytes, depending on New or Old makernote)
+        $countfieldbits = $new ? 1 : 2;
+        // New makernote repeats 1-byte value twice, so increment $place by 2 in either case.
+        $num = bin2hex(substr($block, $place, $countfieldbits)); $place += 2;
+        if ($intel == 1) $num = Horde_Image_Exif::intel2Moto($num);
+        $ntags = hexdec($num);
+        $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = $ntags;
+
+        //loop thru all tags  Each field is 12 bytes
+        for($i=0; $i < $ntags; $i++) {
+            //2 byte tag
+            $tag = bin2hex(substr($block, $place,2));
+            $place += 2;
+            if ($intel == 1) $tag = Horde_Image_Exif::intel2Moto($tag);
+            $tag_name = self::_lookupTag($tag);
+
+            //2 byte type
+            $type = bin2hex(substr($block, $place,2));
+            $place += 2;
+            if ($intel == 1) $type = Horde_Image_Exif::intel2Moto($type);
+            self::_lookupType($type,$size);
+
+            //4 byte count of number of data units
+            $count = bin2hex(substr($block, $place,4));
+            $place+=4;
+            if ($intel == 1) $count = Horde_Image_Exif::intel2Moto($count);
+            $bytesofdata = $size * hexdec($count);
+
+            //4 byte value of data or pointer to data
+            $value = substr($block, $place,4);
+            $place += 4;
+
+
+            if ($bytesofdata <= 4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if($intel==1) $value = Horde_Image_Exif::intel2Moto($value);
+                $v = fseek($seek,$globalOffset+hexdec($value));  //offsets are from TIFF header which is 12 bytes from the start of the file
+                if(isset($GLOBALS['exiferFileSize']) && $v == 0 && $bytesofdata < $GLOBALS['exiferFileSize']) {
+                    $data = fread($seek, $bytesofdata);
+                } else {
+                    $result['Errors'] = $result['Errors']++;
+                    $data = '';
+                }
+            }
+            $formated_data = self::_formatData($type,$tag,$intel,$data);
+            $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Panasonic.php b/framework/Image/lib/Horde/Image/Exif/Parser/Panasonic.php
new file mode 100644 (file)
index 0000000..b26dfca
--- /dev/null
@@ -0,0 +1,408 @@
+<?php
+/**
+ *
+ */
+
+/**
+ *   Exifer
+ *   Extracts EXIF information from digital photos.
+ *
+ *   Copyright 2003 Jake Olefsky
+ *   http://www.offsky.com/software/exif/index.php
+ *   jake@olefsky.com
+ *
+ *   Please see exif.php for the complete information about this software.
+ *
+ *   ------------
+ *
+ *   This program is free software; you can redistribute it and/or modify it under the terms of
+ *   the GNU General Public License as published by the Free Software Foundation; either version 2
+ *   of the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ *   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *   See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+ */
+class Horde_Image_Exif_Parser_Panasonic extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     * Looks up the name of the tag for the MakerNote (Depends on Manufacturer)
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch ($tag) {
+        case '0001': $tag = 'Quality'; break;
+        case '0002': $tag = 'FirmwareVersion'; break;
+        case '0003': $tag = 'WhiteBalance'; break;
+        case '0007': $tag = 'FocusMode'; break;
+        case '000f': $tag = 'AFMode'; break;
+        case '001a': $tag = 'ImageStabilizer'; break;
+        case '001c': $tag = 'MacroMode'; break;
+        case '001f': $tag = 'ShootingMode'; break;
+        case '0020': $tag = 'Audio'; break;
+        case '0021': $tag = 'DataDump'; break;
+        case '0023': $tag = 'WhiteBalanceBias'; break;
+        case '0024': $tag = 'FlashBias'; break;
+        case '0025': $tag = 'SerialNumber'; break;
+        case '0028': $tag = 'ColourEffect'; break;
+        case '002a': $tag = 'BurstMode'; break;
+        case '002b': $tag = 'SequenceNumber'; break;
+        case '002c': $tag = 'Contrast'; break;
+        case '002d': $tag = 'NoiseReduction'; break;
+        case '002e': $tag = 'SelfTimer'; break;
+        case '0030': $tag = 'Rotation'; break;
+        case '0032': $tag = 'ColorMode'; break;
+        case '0036': $tag = 'TravelDay'; break;
+        default: $tag = 'unknown:' . $tag; break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     * Formats Data for the data type
+     */
+    static protected function _formatData($type,$tag,$intel,$data)
+    {
+        if ($type == 'UBYTE' || $type == 'SBYTE') {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            $data = hexdec($data);
+            if ($tag == '000f') { //AFMode
+                if ($data == 256) {
+                    $data = _("9-area-focusing");
+                } elseif ($data == 16) {
+                    $data = _("1-area-focusing");
+                } elseif ($data == 4096) {
+                    $data = _("3-area-focusing (High speed)");
+                } elseif ($data == 4112) {
+                    $data = _("1-area-focusing (High speed)");
+                } elseif ($data == 16) {
+                    $data = _("1-area-focusing");
+                } elseif ($data == 1) {
+                    $data = _("Spot-focusing");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+
+        } elseif ($type == 'URATIONAL' || $type == 'SRATIONAL') {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+            $top = hexdec(substr($data, 8, 8));
+            $bottom = hexdec(substr($data, 0, 8));
+            if ($bottom!=0) {
+                $data = $top / $bottom;
+            } elseif ($top == 0) {
+                $data = 0;
+            } else {
+                $data = $top . '/' . $bottom;
+            }
+
+        } elseif ($type == 'USHORT' || $type == 'SSHORT' || $type == 'ULONG' ||
+                  $type == 'SLONG' || $type == 'FLOAT' || $type == 'DOUBLE') {
+
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+
+            $data = hexdec($data);
+            if ($tag == '0001') { //Image Quality
+                if ($data == 2) {
+                    $data = _("High");
+                } elseif ($data == 3) {
+                    $data = _("Standard");
+                } elseif ($data == 6) {
+                    $data = _("Very High");
+                } elseif ($data == 7) {
+                    $data = _("RAW");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '0003') { //White Balance
+                if ($data == 1) {
+                    $data = _("Auto");
+                } elseif ($data == 2) {
+                    $data = _("Daylight");
+                } elseif ($data == 3) {
+                    $data = _("Cloudy");
+                } elseif ($data == 4) {
+                    $data = _("Halogen");
+                } elseif ($data == 5) {
+                    $data = _("Manual");
+                } elseif ($data == 8) {
+                    $data = _("Flash");
+                } elseif ($data == 10) {
+                    $data = _("Black and White");
+                } elseif ($data == 11) {
+                    $data = _("Manual");
+                } else {
+                    $data = sprintf(_("Unknown(%s)"), $data);
+                }
+            }
+            if ($tag=='0007') { //Focus Mode
+                if ($data == 1) {
+                    $data = _("Auto");
+                } elseif ($data == 2) {
+                    $data = _("Manual");
+                } elseif ($data == 4) {
+                    $data = _("Auto, Focus button");
+                } elseif ($data == 5) {
+                    $data = _("Auto, Continuous");
+                } else {
+                    $data = sprintf(_("Unknown(%s)"), $data);
+                }
+            }
+            if ($tag == '001a') { //Image Stabilizer
+                if ($data == 2) {
+                    $data = _("Mode 1");
+                } elseif ($data == 3) {
+                    $data = _("Off");
+                } elseif ($data == 4) {
+                    $data = _("Mode 2");
+                } else {
+                    $data = sprintf(_("Unknown(%s)"), $data);
+                }
+            }
+            if ($tag == '001c') { //Macro mode
+                if ($data == 1) {
+                    $data = _("On");
+                } elseif ($data == 2) {
+                    $data = _("Off");
+                } else {
+                    $data = sprintf(_("Unknown(%s)"), $data);
+                }
+            }
+            if ($tag == '001f') { //Shooting Mode
+                if ($data == 1) {
+                    $data = _("Normal");
+                } elseif ($data == 2) {
+                    $data = _("Portrait");
+                } elseif ($data == 3) {
+                    $data = _("Scenery");
+                } elseif ($data == 4) {
+                    $data = _("Sports");
+                } elseif ($data == 5) {
+                    $data = _("Night Portrait");
+                } elseif ($data == 6) {
+                    $data = _("Program");
+                } elseif ($data == 7) {
+                    $data = _("Aperture Priority");
+                } elseif ($data == 8) {
+                    $data = _("Shutter Priority");
+                } elseif ($data == 9) {
+                    $data = _("Macro");
+                } elseif ($data == 11) {
+                    $data = _("Manual");
+                } elseif ($data == 13) {
+                    $data = _("Panning");
+                } elseif ($data == 14) {
+                    $data = _("Simple");
+                } elseif ($data == 18) {
+                    $data = _("Fireworks");
+                } elseif ($data == 19) {
+                    $data = _("Party");
+                } elseif ($data == 20) {
+                    $data = _("Snow");
+                } elseif ($data == 21) {
+                    $data = _("Night Scenery");
+                } elseif ($data == 22) {
+                    $data = _("Food");
+                } elseif ($data == 23) {
+                    $data = _("Baby");
+                } elseif ($data == 27) {
+                    $data = _("High Sensitivity");
+                } elseif ($data == 29) {
+                    $data = _("Underwater");
+                } elseif ($data == 33) {
+                    $data = _("Pet");
+                } else {
+                    $data = sprintf(_("Unknown(%s)"), $data);
+                }
+            }
+            if ($tag == '0020') { //Audio
+                if ($data == 1) {
+                    $data = _("Yes");
+                } elseif ($data == 2) {
+                    $data = _("No");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '0023') { //White Balance Bias
+                $data = $data . ' EV';
+            }
+            if ($tag == '0024') { //Flash Bias
+                $data = $data;
+            }
+            if ($tag == '0028') { //Colour Effect
+                if ($data == 1) {
+                    $data = _("Off");
+                } elseif ($data == 2) {
+                    $data = _("Warm");
+                } elseif ($data == 3) {
+                    $data = _("Cool");
+                } elseif ($data == 4) {
+                    $data = _("Black and White");
+                } elseif ($data == 5) {
+                    $data = _("Sepia");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '002a') { //Burst Mode
+                if ($data == 0) {
+                    $data = _("Off");
+                } elseif ($data == 1) {
+                    $data = _("Low/High Quality");
+                } elseif ($data == 2) {
+                    $data = _("Infinite");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '002c') { //Contrast
+                if ($data == 0) {
+                    $data = _("Standard");
+                } elseif ($data == 1) {
+                    $data = _("Low");
+                } elseif ($data == 2) {
+                    $data = _("High");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '002d') { //Noise Reduction
+                if ($data == 0) {
+                    $data = _("Standard");
+                } elseif ($data == 1) {
+                    $data = _("Low");
+                } elseif ($data == 2) {
+                    $data = _("High");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '002e') { //Self Timer
+                if ($data == 1) {
+                    $data = _("Off");
+                } elseif ($data == 2) {
+                    $data = _("10s");
+                } elseif ($data == 3) {
+                    $data = _("2s");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '0030') { //Rotation
+                if ($data == 1) {
+                    $data = _("Horizontal (normal)");
+                } elseif ($data == 6) {
+                    $data = _("Rotate 90 CW");
+                } elseif ($data == 8) {
+                    $data = _("Rotate 270 CW");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '0032') { //Color Mode
+                if ($data == 0) {
+                    $data = _("Normal");
+                } elseif ($data == 1) {
+                    $data = _("Natural");
+                } else {
+                    $data = sprintf(_("Unknown (%s)"), $data);
+                }
+            }
+            if ($tag == '0036') { //Travel Day
+                $data = $data;
+            }
+        } elseif ($type != "UNDEFINED") {
+            $data = bin2hex($data);
+            if ($intel == 1) {
+                $data = Horde_Image_Exif::intel2Moto($data);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Panasonic Special data section
+     */
+     static public function parse($block, &$result)
+     {
+        $intel = 1;
+        $model = $result['IFD0']['Model'];
+        $place = 8; //current place
+        $offset = 8;
+
+        $num = bin2hex(substr($block, $place, 4));
+        $place += 4;
+
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $result['SubIFD']['MakerNote']['Offset'] = hexdec($num);
+
+        //Get number of tags (2 bytes)
+        $num = bin2hex(substr($block, $place, 2));
+        $place+=2;
+
+        if ($intel == 1) {
+            $num = Horde_Image_Exif::intel2Moto($num);
+        }
+        $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = hexdec($num);
+
+        //loop thru all tags  Each field is 12 bytes
+        for($i = 0; $i < hexdec($num); $i++) {
+            //2 byte tag
+            $tag = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $tag = Horde_Image_Exif::intel2Moto($tag);
+            }
+            $tag_name = self::_lookupTag($tag);
+
+            //2 byte type
+            $type = bin2hex(substr($block, $place, 2));
+            $place += 2;
+            if ($intel == 1) {
+                $type = Horde_Image_Exif::intel2Moto($type);
+            }
+            self::_lookupType($type, $size);
+
+            //4 byte count of number of data units
+            $count = bin2hex(substr($block, $place, 4));
+            $place += 4;
+            if ($intel == 1) {
+                $count = Horde_Image_Exif::intel2Moto($count);
+            }
+            $bytesofdata = $size * hexdec($count);
+
+            //4 byte value of data or pointer to data
+            $value = substr($block, $place, 4);
+            $place += 4;
+
+            if ($bytesofdata <= 4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if ($intel == 1) {
+                    $value = Horde_Image_Exif::intel2Moto($value);
+                }
+                $data = substr($block, hexdec($value) - $offset, $bytesofdata * 2);
+            }
+            $formated_data = self::_formatData($type, $tag, $intel, $data);
+            $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Parser/Sanyo.php b/framework/Image/lib/Horde/Image/Exif/Parser/Sanyo.php
new file mode 100644 (file)
index 0000000..84f171b
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+/**
+ *
+ */
+
+/*
+    Exifer
+    Extracts EXIF information from digital photos.
+
+    Copyright � 2003 Jake Olefsky
+    http://www.offsky.com/software/exif/index.php
+    jake@olefsky.com
+
+    Please see exif.php for the complete information about this software.
+
+    ------------
+
+    This program is free software; you can redistribute it and/or modify it under the terms of
+    the GNU General Public License as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html
+*/
+class Horde_Image_Exif_Parser_Sanyo extends Horde_Image_Exif_Parser_Base
+{
+    /**
+     *
+     * @param $tag
+     * @return unknown_type
+     */
+    static protected function _lookupTag($tag)
+    {
+        switch($tag) {
+            case "0200": $tag = "SpecialMode";break;
+            case "0201": $tag = "Quality";break;
+            case "0202": $tag = "Macro";break;
+            case "0203": $tag = "Unknown";break;
+            case "0204": $tag = "DigiZoom";break;
+            case "0f00": $tag = "DataDump";break;
+            default: $tag = "unknown:".$tag;break;
+        }
+
+        return $tag;
+    }
+
+    /**
+     *
+     * @param $type
+     * @param $tag
+     * @param $intel
+     * @param $data
+     * @return unknown_type
+     */
+    static protected function _formatData($type,$tag,$intel,$data)
+    {
+        if($type=="ASCII") {
+
+
+        } else if($type=="URATIONAL" || $type=="SRATIONAL") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $top = hexdec(substr($data,8,8));
+            $bottom = hexdec(substr($data,0,8));
+            if($bottom!=0) $data=$top/$bottom;
+            else if($top==0) $data = 0;
+            else $data=$top."/".$bottom;
+
+
+        } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+            $data=hexdec($data);
+
+            if($tag=="0200") { //SpecialMode
+                if($data == 0) $data = _("Normal");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0201") { //Quality
+                if($data == 2) $data = _("High");
+                else $data = _("Unknown").": ".$data;
+            }
+            if($tag=="0202") { //Macro
+                if($data == 0) $data = _("Normal");
+                else $data = _("Unknown").": ".$data;
+            }
+        } else if($type=="UNDEFINED") {
+
+
+
+        } else {
+            $data = bin2hex($data);
+            if($intel==1) $data = Horde_Image_Exif::intel2Moto($data);
+        }
+
+        return $data;
+    }
+
+    /**
+     *
+     * @param $block
+     * @param $result
+     * @param $seek
+     * @param $globalOffset
+     * @return unknown_type
+     */
+    static public function parse($block,&$result,$seek, $globalOffset)
+    {
+        if($result['Endien']=="Intel") $intel=1;
+        else $intel=0;
+
+        $model = $result['IFD0']['Model'];
+
+        $place=8; //current place
+        $offset=8;
+
+            //Get number of tags (2 bytes)
+        $num = bin2hex(substr($block,$place,2));$place+=2;
+        if($intel==1) $num = Horde_Image_Exif::intel2Moto($num);
+        $result['SubIFD']['MakerNote']['MakerNoteNumTags'] = hexdec($num);
+
+        //loop thru all tags  Each field is 12 bytes
+        for($i=0;$i<hexdec($num);$i++) {
+
+                //2 byte tag
+            $tag = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $tag = Horde_Image_Exif::intel2Moto($tag);
+            $tag_name = self::_lookupTag($tag);
+
+                //2 byte type
+            $type = bin2hex(substr($block,$place,2));$place+=2;
+            if($intel==1) $type = Horde_Image_Exif::intel2Moto($type);
+            self::_lookupType($type,$size);
+
+                //4 byte count of number of data units
+            $count = bin2hex(substr($block,$place,4));$place+=4;
+            if($intel==1) $count = Horde_Image_Exif::intel2Moto($count);
+            $bytesofdata = $size*hexdec($count);
+
+                //4 byte value of data or pointer to data
+            $value = substr($block,$place,4);$place+=4;
+
+
+            if($bytesofdata<=4) {
+                $data = $value;
+            } else {
+                $value = bin2hex($value);
+                if($intel==1) $value = Horde_Image_Exif::intel2Moto($value);
+                $v = fseek($seek,$globalOffset+hexdec($value));  //offsets are from TIFF header which is 12 bytes from the start of the file
+                if($v==0) {
+                    $data = fread($seek, $bytesofdata);
+                } else if($v==-1) {
+                    $result['Errors'] = $result['Errors']++;
+                }
+            }
+            $formated_data = self::_formatData($type,$tag,$intel,$data);
+            $result['SubIFD']['MakerNote'][$tag_name] = $formated_data;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Exif/Php.php b/framework/Image/lib/Horde/Image/Exif/Php.php
new file mode 100644 (file)
index 0000000..e68606e
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Exif driver for Horde_Image utilizing PHP's compiled-in exif functions
+ *
+ */
+class Horde_Image_Exif_Php extends Horde_Image_Exif_Base
+{
+    public function getData($image)
+    {
+        $exif = @exif_read_data($image, 0, false);
+
+        return $this->_processData($exif);
+    }
+
+}