]> granicus.if.org Git - php/commitdiff
+Support for Photographer/Editor Copyright as associative array as this is a new...
authorMarcus Boerger <helly@php.net>
Fri, 1 Mar 2002 04:01:26 +0000 (04:01 +0000)
committerMarcus Boerger <helly@php.net>
Fri, 1 Mar 2002 04:01:26 +0000 (04:01 +0000)
+New function exif_headername can be used to read the internal Tag namelist (was mainly created for debugging purpose but maybe somone writes code to create/update exif headers here).
+An internal version number is present.
+A testpage is supplied test.txt describes how the test works.
+The oldfunction read_exif_data has got an alias exif_read_data

As the old version of this module is very buggy i decided to implement the testpage (test.txt) and to create the alias. The test script only works with the alias as the old version does not pass tests. By the way it seems a good way to prepend 'exif_' to all functions in the module.

ext/exif/exif.c
ext/exif/php_exif.h
ext/exif/test.txt [new file with mode: 0644]

index 5db3728add2bdcb57bbf85b6ebe9f7d5a21fa9ef..e453b9f3286d2e379efeb73c8e33d16444ce913a 100644 (file)
 /* $Id$ */
 
 /* Much of the code in this module was borrowed from the public domain
-   jhead.c package with the author's consent.  The main changes have been
-   to eliminate all the global variables to make it thread safe and to wrap
-   it in the PHP 4 API.
-
-   Aug.3 2001 - Added support for multiple M_COM entries - Rasmus
-
-   Additional changes and fixes mainly to expand functionality for image
-   galleries.
-
-   Feb.26 2002 - Marcus
-
-                               Added UNICODE support for Comments
-                               Added Description,Artist
-                               Added missing memory deallocation
-                               Corrected error with multiple comments
-                               Corrected handling of ExifVersion, Tag has 4 ASCII
-                                   characters *WITHOUT* NUL
-                               Corrected handling of Thumbnailsize if current source
-                                   detects size < 0
-                               Changed all fields to char* that do not have a maximum
-                                   length in EXIF standard
-                       Undocumented second Parameter ReadAll frees memory to
-                       early -> moved to third position default changed to
-                       false -> faster
-                   New second Parameter [true|false] to specify whether or
-                                       not to to read thumbnails -> reading is timeconsumpting
-                                       suppose default should be false -> done so
-
-   ToDos - Copyright tag stores "<Photographer> '\0' <Editor> '\0'" but we
-                         stop at first '\0'
-                  JIS encoding for comments
-                  See if example images from http://www.exif.org have illegal
-                         thumbnail sizes or if code is corrupt.
-
-   The original header from the jhead.c file was:
+       jhead.c package with the author's consent.  The main changes have been
+       to eliminate all the global variables to make it thread safe and to wrap
+       it in the PHP 4 API.
+
+       Aug.3 2001 - Added support for multiple M_COM entries - Rasmus
+
+       Feb.26 2002 - Marcus
+
+               Additional changes and fixes mainly to expand functionality for image
+               galleries.
+
+               Added UNICODE support for Comments
+               Added Description,Artist
+               Added missing memory deallocation
+               Corrected error with multiple comments
+               Corrected handling of ExifVersion, Tag has 4 ASCII
+                       characters *WITHOUT* NUL
+               Corrected handling of Thumbnailsize if current source
+                       detects size < 0
+               Changed all fields to char* that do not have a maximum
+                       length in EXIF standard
+               Undocumented second Parameter ReadAll frees memory to
+                       early -> moved to third position default changed to
+                       false -> faster
+               New second Parameter [true|false] to specify whether or
+                       not to to read thumbnails -> reading is timeconsumpting
+                       suppose default should be false -> done so
+
+       Feb.28 2002 - Marcus
+
+               Support for Photographer/Editor Copyright as associative
+                       array as this is a new feature the change (optionally
+                       being an array) has to be mentioned in documentation.
+               New function exif_headername can be used to read the internal Tag
+                       namelist (was mainly created for debugging purpose but maybe
+                       somone writes code to create/update exif headers here).
+               A testpage is supplied test.txt describes how the test works.
+
+       ToDos - JIS encoding for comments
+                       See if example images from http://www.exif.org have illegal
+                               thumbnail sizes or if code is corrupt.
+                       Create/Update exif headers.
+                       Create/Remove/Update image thumbnails.
+
+       The original header from the jhead.c file was:
 
   --------------------------------------------------------------------------
    Program to pull the information out of various types of EFIF digital
@@ -69,7 +79,7 @@
 
    Matthias Wandel,  Dec 1999 - April 2000
   --------------------------------------------------------------------------
-  */
+*/
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -93,18 +103,21 @@ typedef unsigned char uchar;
 
 #define EXIF_MAX_COMMENTS 12
 
+/* EXIF standard defines Copyright as "<Photographer> [ '\0' <Editor> ] ['\0']" */
+#define EXIF_MAX_COPYRIGHT 2
+
 /* {{{ structs
    This structure stores Exif header image elements in a simple manner
    Used to store camera data as extracted from the various ways that it can be
    stored in a nexif header
 */
 typedef struct {
-       char  FileName     [120];
+       char  FileName[120];
        time_t FileDateTime;
        unsigned FileSize;
        char  *CameraMake;
        char  *CameraModel;
-       char  DateTime     [20];
+       char  DateTime[20];
        int   Height, Width;
        int   IsColor;
        int   FlashUsed;
@@ -124,7 +137,10 @@ typedef struct {
        char GPSinfo[48];
        int ISOspeed;
        char ExifVersion[8];
-       char *Copyright; /* maybe two strings see ToDos */
+       char *RawCopyright;
+       int lenRawCopyright;
+       char *Copyright[EXIF_MAX_COPYRIGHT];
+       int numCopyrights;
        char *Artist;
        char *Software;
        char *Thumbnail;
@@ -156,10 +172,14 @@ typedef struct {
  */
 function_entry exif_functions[] = {
        PHP_FE(read_exif_data, NULL)
+       PHP_FALIAS(exif_read_data,read_exif_data, NULL)
+       PHP_FE(exif_headername, NULL)
        {NULL, NULL, NULL}
 };
 /* }}} */
 
+#define EXIF_VERSION "2.0a $Revision$"
+
 PHP_MINFO_FUNCTION(exif);
 
 /* {{{ exif_module_entry
@@ -171,7 +191,7 @@ zend_module_entry exif_module_entry = {
        NULL, NULL,
        NULL, NULL,
        PHP_MINFO(exif),
-       NO_VERSION_YET,
+       EXIF_VERSION,
        STANDARD_MODULE_PROPERTIES
 };
 /* }}} */
@@ -186,6 +206,8 @@ PHP_MINFO_FUNCTION(exif)
 {
        php_info_print_table_start();
        php_info_print_table_row(2, "EXIF Support", "enabled" );
+       php_info_print_table_row(2, "EXIF Version", EXIF_VERSION );
+       php_info_print_table_row(2, "Supported EXIF Version", "02100");
        php_info_print_table_end();
 }
 /* }}} */
@@ -264,7 +286,7 @@ static void process_COM (ImageInfoType *ImageInfo, uchar *Data, int length)
        a = ImageInfo->numComments;
 
        (ImageInfo->Comments)[a] = emalloc(nch+1);
-  strcpy(ImageInfo->Comments[a], Comment);
+       strcpy(ImageInfo->Comments[a], Comment);
        (ImageInfo->numComments)++;
 }
 /* }}} */
@@ -468,10 +490,44 @@ static const struct {
   {    0xA217, "SensingMethod"},                       /* 0x9217    -  -    */
   {    0xA300, "FileSource"},
   {    0xA301, "SceneType"},
-  {      0, NULL}
+  {      0, NULL}                           /* Important for get_exif_headername() */
 } ;
 /* }}} */
 
+/* {{{ get_exif_headername
+    Get headername for tag_num or NULL if not defined */
+char * get_exif_headername(int tag_num)
+{
+       int i,t;
+
+       for (i=0;;i++) {
+               if ( (t=TagTable[i].Tag) == tag_num || !t) return TagTable[i].Desc;
+       }
+       return NULL;
+}
+/* }}} */
+
+/* {{{ proto string|false exif_headername(index)
+    Get headername for index or false if not defined */
+PHP_FUNCTION(exif_headername)
+{
+       pval **p_num;
+       int ac = ZEND_NUM_ARGS();
+       char *szTemp;
+
+       if ((ac < 1 || ac > 1) || zend_get_parameters_ex(ac, &p_num) == FAILURE) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_long_ex(p_num);
+       if ( (szTemp = get_exif_headername(Z_LVAL_PP(p_num))) == NULL) {
+               RETURN_BOOL(FALSE);
+       } else {
+               RETURN_STRING(szTemp, 1)
+       }
+}
+/* }}} */
+
 /* {{{ Get16u
  * Convert a 16 bit unsigned value from file's native byte order */
 static int Get16u(void *Short, int MotorolaOrder)
@@ -638,17 +694,38 @@ static int ProcessExifComment(char **pszInfoPtr,char *szValuePtr,int ByteCount)
 }
 /* }}} */
 
+/* {{{ CopyExifStringRaw
+ * Copy a string in Exif header to a character string returns length of allocated buffer if any. */
+static int CopyExifStringRaw(char **pszInfoPtr,char *szValuePtr,int ByteCount) {
+       /* we cannot use strlcpy - here the problem is that we have to copy NUL
+        * chars up to ByteCount, we also have to add a single NUL character to
+        * force end of string.
+        */
+       if (ByteCount) {
+               (*pszInfoPtr) = emalloc(ByteCount+1);
+               memcpy(*pszInfoPtr, szValuePtr, ByteCount);
+               (*pszInfoPtr)[ByteCount] = '\0';
+               return ByteCount+1;
+       }
+       return 0;
+}
+/* }}} */
+
 /* {{{ CopyExifString
- * Copy a string in Exif header to a character string. */
+ * Copy a string in Exif header to a character string returns length of allocated buffer if any. */
 static int CopyExifString(char **pszInfoPtr,char *szValuePtr,int ByteCount) {
        int l;
 
-       l = strlen(szValuePtr)+1;
-       if (ByteCount<l) l = ByteCount;
-       if (l>1) {
-         (*pszInfoPtr) = emalloc(l);
-         strlcpy(*pszInfoPtr, szValuePtr, l);
-         return 1;
+       /* we cannot use strlcpy - here the problem is that we cannot use strlen to
+        * determin length of string and we cannot use strlcpy with len=ByteCount+1
+        * because then we might get into an EXCEPTION if we exceed an aallocate
+        * memory page...
+        */
+       if (ByteCount && CopyExifStringRaw(pszInfoPtr,szValuePtr,ByteCount)) {
+               if ( (l = strlen(*pszInfoPtr)) < ByteCount) {
+                       (*pszInfoPtr)[l] = '\0';
+               }
+               return l+1;
        }
        return 0;
 }
@@ -659,7 +736,7 @@ static int CopyExifString(char **pszInfoPtr,char *szValuePtr,int ByteCount) {
 static void ProcessExifDir(ImageInfoType *ImageInfo, char *DirStart, char *OffsetBase, unsigned ExifLength, char *LastExifRefd, int ReadThumbnail)
 {
        int de;
-       int a;
+       int a,l;
        int NumDirEntries;
        int NextDirOffset;
 
@@ -723,6 +800,7 @@ static void ProcessExifDir(ImageInfoType *ImageInfo, char *DirStart, char *Offse
                }
 
                /* Extract useful components of tag */
+               /* php_error(E_NOTICE,"Scan tag: %s", get_exif_headername(Tag)); */
                switch(Tag) {
 
                        case TAG_MAKE:
@@ -743,7 +821,16 @@ static void ProcessExifDir(ImageInfoType *ImageInfo, char *DirStart, char *Offse
                                break;
 
                        case TAG_COPYRIGHT:
-                               CopyExifString(&ImageInfo->Copyright,ValuePtr,ByteCount);
+                               if (CopyExifStringRaw(&ImageInfo->RawCopyright,ValuePtr,ByteCount)) {
+                                       l = strlen(ImageInfo->RawCopyright);
+                               } else {
+                                       l = 0;
+                               }
+                               ImageInfo->lenRawCopyright = ByteCount;
+                               if ( ImageInfo->numCopyrights==0 && ByteCount>1 && l<ByteCount-1) {
+                                       CopyExifString(&((ImageInfo->Copyright)[ImageInfo->numCopyrights++]), ValuePtr, l);
+                                       CopyExifString(&((ImageInfo->Copyright)[ImageInfo->numCopyrights++]), ValuePtr+l+1, ByteCount-l-1);
+                           }
                                break;
 
                        case TAG_ARTIST:
@@ -1155,7 +1242,7 @@ int ReadJpegFile(ImageInfoType *ImageInfo, Section_t *Sections,
        }
 /*    CurrentFile = FileName; */
 
-/*    php_error(E_NOTICE,"EXIF: Process %s%s: %s", ReadThumbnail?"thumbs ":"", ReadAll?"All ":"", FileName); */
+/*    php_error(E_WARNING,"EXIF: Process %s%s: %s", ReadThumbnail?"thumbs ":"", ReadAll?"All ":"", FileName); */
        /* Start with an empty image information structure. */
        memset(ImageInfo, 0, sizeof(*ImageInfo));
        memset(Sections, 0, sizeof(*Sections));
@@ -1239,7 +1326,7 @@ int php_read_jpeg_exif(ImageInfoType *ImageInfo, char *FileName, int ReadThumbna
    Reads the EXIF header data from the JPEG identified by filename and optionally reads the internal thumbnails */
 PHP_FUNCTION(read_exif_data)
 {
-       pval **p_name, **p_readthumbnail, **p_readall, *tmpi;
+       pval **p_name, **p_readthumbnail, **p_readall;
        int i, ac = ZEND_NUM_ARGS(), ret, readthumbnail=0, readall=0;
        ImageInfoType ImageInfo;
        char tmp[64];
@@ -1335,11 +1422,28 @@ PHP_FUNCTION(read_exif_data)
        if (ImageInfo.ExifVersion[0]) {
                add_assoc_string(return_value, "ExifVersion", ImageInfo.ExifVersion, 1);
        }
-       if (ImageInfo.Copyright) {
-               if (ImageInfo.Copyright[0]) {
-                       add_assoc_string(return_value, "Copyright", ImageInfo.Copyright, 1);
+       if (ImageInfo.RawCopyright || ImageInfo.numCopyrights) {
+               if (ImageInfo.RawCopyright && !ImageInfo.numCopyrights && ImageInfo.lenRawCopyright) {
+                       add_assoc_stringl(return_value, "Copyright", ImageInfo.RawCopyright, ImageInfo.lenRawCopyright, 1);
+               }else{
+                       pval *tmpi;
+
+                       MAKE_STD_ZVAL(tmpi);
+                       array_init(tmpi);
+                       /*for(i=0; i<ImageInfo.numCopyrights; i++) {
+                        *      add_index_string(tmpi, i, (ImageInfo.Copyright)[i], 0);
+                        *}
+                        */
+                       add_assoc_string(tmpi, "Photographer", ImageInfo.Copyright[0], 1);
+                       add_assoc_string(tmpi, "Editor", ImageInfo.Copyright[1], 1);
+                       add_assoc_zval(return_value, "Copyright", tmpi);
+               }
+               if (ImageInfo.RawCopyright) {
+                       efree(ImageInfo.RawCopyright);
+               }
+               for(i=0; i<ImageInfo.numCopyrights; i++) {
+                       efree((ImageInfo.Copyright)[i]);
                }
-               efree(ImageInfo.Copyright);
        }
        if (ImageInfo.Software) {
                if (ImageInfo.Software[0]) {
@@ -1351,16 +1455,18 @@ PHP_FUNCTION(read_exif_data)
                if(ImageInfo.numComments==1) {
                        add_assoc_string(return_value, "Comments", (ImageInfo.Comments)[0], 1);
                } else {
+                       pval *tmpi;
+
                        MAKE_STD_ZVAL(tmpi);
                        array_init(tmpi);
                        for(i=0; i<ImageInfo.numComments; i++) {
                                add_index_string(tmpi, i, (ImageInfo.Comments)[i], 0);
                        }
-                       zend_hash_update(return_value->value.ht, "Comments", 9, &tmpi, sizeof(zval *), NULL);
+                       add_assoc_zval(return_value, "Comments", tmpi);
+                       /* zend_hash_add(return_value->value.ht, "Comments", 9, &tmpi, sizeof(zval *), NULL); */
                }
                for(i=0; i<ImageInfo.numComments; i++) {
                        efree((ImageInfo.Comments)[i]);
-                       (ImageInfo.Comments)[i] = NULL;
                }
        }
        if (ImageInfo.Description) {
index 83d0cc39ea513fc8de568930619728d0f0fd6cfb..1ee86dc2748833c45afa39c6c38cb57e3f8ac43c 100644 (file)
@@ -24,4 +24,5 @@ extern zend_module_entry exif_module_entry;
 #define phpext_exif_ptr &exif_module_entry
 
 PHP_FUNCTION(read_exif_data);
+PHP_FUNCTION(exif_headername);
 #endif
diff --git a/ext/exif/test.txt b/ext/exif/test.txt
new file mode 100644 (file)
index 0000000..d20427d
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+
+/* Test script for PHP module ext/exif
+ *
+ * (c) Marcus Boerger, 2002
+ *
+ * Rename the file to test.php and read the instructions. If the
+ * script cannot be executed or does not generate any output check
+ * you error log. In most cases this would mean you found an error
+ * if the rest of your php environment works fine.
+ *
+ * The original version of module exif has many errors and mostly
+ * fails on executing this script.
+ */
+
+$possible = array();
+
+/****************************************************************************/
+// message function is used for debugging purpose: just to se what happens
+function message($msg) {
+       error_log($msg,0);
+       echo "$msg\n";
+}
+
+function error_msg() {
+       if (array_key_exists('php_errormsg',$GLOBALS)) return $GLOBALS['php_errormsg'];
+       return 'php.ini: track_errors is off';
+}
+
+/****************************************************************************/
+// private to function search_file()
+function _search_file($root,&$possible,$path='') {
+       $sub = array();
+
+       //error_log("search_file($root,$path)",0);
+       if ($dir = @opendir($root.$path.'/')) {
+               while (($found = @readdir($dir)) !== false) {
+                       $type = @filetype($root.$path.'/'.$found);
+                       //error_log("search_file($root$path):$type=$found",0);
+                       switch( $type) {
+                               case 'file':
+                               $pos = strpos($found,'.');
+                               if ( $pos !== false && strtolower(substr($found,$pos+1))=='jpg') {
+                                       $possible[] = $root.$path.'/'.$found;
+                                       //error_log("search_file($root$path) add:$path/$found",0);
+                               }
+                               break;
+                               case 'dir':
+                               if ( $found!='.' && $found!='..') {
+                                       $sub[count($sub)] = $found;
+                               }
+                               break;
+                       }
+               }
+               @closedir($dir);
+               foreach( $sub as $idx => $found) {
+                       _search_file($root,$possible,$path.'/'.$found);
+               }
+       }
+}
+
+/****************************************************************************/
+// function: search_file($file,$ext)
+//
+// Searches for $file in document tree. The path is ignored.
+//
+function search_file() {
+       global $argv;
+       $possible = array();
+
+       if ( array_key_exists('SCRIPT_FILENAME',$_SERVER)) {
+               $path = $_SERVER['SCRIPT_FILENAME'];
+       } else {
+               $path = $argv[0];
+       }
+       if ( ($p=strpos($path,'?')) !== false) $path = substr($path,0,$p);
+       if ( ($p=strrpos($path,'/')) < strlen($path)-1) $path = substr($path,0,$p);
+       _search_file($path,$possible);
+       return $possible;
+}
+
+/****************************************************************************/
+// function: search_file($file,$ext)
+//
+// Searches for $file in document tree. The path is ignored.
+//
+function AddInfo($Name,$Value) {
+       $Value = nl2br($Value);
+       return "<tr><td>$Name</td><td>$Value&nbsp;</td></tr>\n";
+}
+
+$possible = search_file();
+
+?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional">
+<html>
+<head>
+<title>PHP moduls exif test page</title>
+<style type="text/css">
+body {
+       font-size: 12pt;
+}
+h1 {
+       font-size: 20pt;
+       font-weight:bold;
+}
+h2 {
+       font-size: 16pt;
+       font-weight:bold;
+}
+th {
+       text-align: left;
+}
+</style>
+</head>
+<body>
+<h1>Test script for PHP module ext/exif</h1>
+<p>
+(c) Marcus Boerger, 2002
+</p>
+<p>
+Images taken from <a href="http://www.exif.org">www.exif.org</a>,
+<a href="http://marcus-boerger.de">marcus-boerger.de</a>
+all rights reserved by their authors and artists, see exif headers.
+The files can be downloaded <a href="http://marcus-boerger.de/php/ext/exif/test/">here</a>.
+To start the test you simple have to put all images into the same directory as this script.
+The test will work with all files in that directory and all subdirectories. To test private
+images just put them into that directory.
+</p>
+<p>
+Youmay take a look at the test <a href="http://marcus-boerger.de/php/ext/exif/test.txt">source here</a>.
+</p>
+<p>
+This test just prooves that some exif headers can be scanned.
+If all files produce a header in output the module might be o.k.
+</p>
+<p>
+What to look for in detail:
+</p>
+<ul>
+<li>kodak-dc4800-plus-acdsee.jpg
+       <ul>
+       <li>should provide a long comment 'by marcus b&ouml;rger&lt;%04i&gt;'*n</li>
+       </ul>
+</li>
+<li>hp-photosmart
+       <ul>
+       <li>should provide a ***two line*** copyright notice</li>
+       </ul>
+</li>
+</ul>
+<h2>function exif_headername</h2>
+<table border='1' cellspacing='0' cellpadding='3' summary="EXIF headernames">
+<?php
+if (function_exists('exif_headername')) {
+?>
+<tr><td>ImageWidth</td><td><?=@exif_headername(0x0100)?></td><td><?=error_msg()?></td></tr>
+<tr><td>JPEGProc</td><td><?=@exif_headername(0x0200)?></td><td><?=error_msg()?></td></tr>
+<tr><td>SceneType</td><td><?=@exif_headername(0xA301)?></td><td><?=error_msg()?></td></tr>
+<tr><td>false</td><td><?=@exif_headername(0x0000)===false?'false':'value'?></td><td><?=error_msg()?></td></tr>
+<?php
+} else {
+       echo "<tr><td>function exif_headername is not supported</td></tr>\n";
+}
+?>
+</table>
+<br clear="all">
+<h2>function read_exif_data for <?=count($possible)?> images</h2>
+<table border='1' cellspacing='0' cellpadding='3' summary="EXIF information">
+<?php
+if (function_exists('read_exif_data')) {
+       $num = 0;
+       foreach($possible as $idx => $file) {
+               $num++;
+               $res   = '';
+               $len   = 2;
+               $image = @read_exif_data($file,false);
+               $error = error_msg();
+               //error_log("read_exif_data($file)",0);
+               foreach($image as $Name => $Value) {
+                       if ( $Name!='Thumbnail') {
+                               if ( is_array($Value)) {
+                                       $len++;
+                                       $res .= AddInfo($Name,'Array('.count($Value).')');
+                                       foreach( $Value as $idx => $Entry) {
+                                               $len++;
+                                               $res .= AddInfo($Name.':'.$idx,$Entry);
+                                       }
+                               } else {
+                                       $len++;
+                                       $res .= AddInfo($Name,$Value);
+                               }
+                       }
+               }
+               echo "<tr><td rowspan='$len' valign='top'>$num</td></tr><tr><th>$file</th><td>$error</td></tr>\n$res";
+       }
+} else {
+       echo "<tr><td>function read_exif_data is not supported</td></tr>\n";
+}
+?>
+</table>
+</body>
+</html>
\ No newline at end of file