2000-05-24 18:33:18 +08:00
|
|
|
/*
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| PHP version 4.0 |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| Copyright (c) 1997, 1998, 1999, 2000 The PHP Group |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| This source file is subject to version 2.02 of the PHP license, |
|
|
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
|
|
| available at through the world-wide-web at |
|
|
|
|
| http://www.php.net/license/2_02.txt. |
|
|
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| Authors: Rasmus Lerdorf <rasmus@php.net> |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
*/
|
|
|
|
/* 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.
|
|
|
|
|
|
|
|
The original header from the jhead.c file was:
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
Program to pull the information out of various types of EFIF digital
|
|
|
|
camera files and show it in a reasonably consistent way
|
|
|
|
|
|
|
|
Version 0.9
|
|
|
|
|
|
|
|
Compiles with MSVC on Windows, or with GCC on Linux
|
|
|
|
|
|
|
|
Compileing under linux: Must include math library.
|
|
|
|
Use: cc -lm -O3 -o jhead jhead.c
|
|
|
|
|
|
|
|
Matthias Wandel, Dec 1999 - April 2000
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "php.h"
|
|
|
|
|
2000-06-10 16:48:01 +08:00
|
|
|
#if HAVE_EXIF
|
2000-05-24 18:33:18 +08:00
|
|
|
|
|
|
|
#include "php_exif.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "php_ini.h"
|
|
|
|
#include "ext/standard/php_string.h"
|
|
|
|
#include "ext/standard/info.h"
|
|
|
|
|
|
|
|
typedef unsigned char uchar;
|
|
|
|
|
|
|
|
#ifndef TRUE
|
|
|
|
#define TRUE 1
|
|
|
|
#define FALSE 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
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];
|
|
|
|
time_t FileDateTime;
|
|
|
|
unsigned FileSize;
|
|
|
|
char CameraMake [32];
|
|
|
|
char CameraModel [64];
|
|
|
|
char DateTime [20];
|
|
|
|
int Height, Width;
|
|
|
|
int IsColor;
|
|
|
|
int FlashUsed;
|
|
|
|
float FocalLength;
|
|
|
|
float ExposureTime;
|
|
|
|
float ApertureFNumber;
|
|
|
|
float Distance;
|
|
|
|
float CCDWidth;
|
|
|
|
char Comments[200];
|
|
|
|
double FocalplaneXRes;
|
|
|
|
double FocalplaneUnits;
|
|
|
|
int ExifImageWidth;
|
|
|
|
int MotorolaOrder;
|
2000-05-30 00:34:19 +08:00
|
|
|
int Orientation;
|
|
|
|
char GPSinfo[48];
|
|
|
|
int ISOspeed;
|
|
|
|
char ExifVersion[16];
|
|
|
|
char Copyright[32];
|
|
|
|
char Software[32];
|
|
|
|
char *Thumbnail;
|
|
|
|
int ThumbnailSize;
|
2000-05-24 18:33:18 +08:00
|
|
|
} ImageInfoType;
|
|
|
|
|
|
|
|
/* This structure is used to store a section of a Jpeg file. */
|
|
|
|
typedef struct {
|
|
|
|
uchar *Data;
|
|
|
|
int Type;
|
|
|
|
unsigned Size;
|
|
|
|
} Section_t;
|
|
|
|
|
|
|
|
#define EXIT_FAILURE 1
|
|
|
|
#define EXIT_SUCCESS 0
|
|
|
|
|
|
|
|
function_entry exif_functions[] = {
|
|
|
|
PHP_FE(read_exif_data, NULL)
|
|
|
|
{0}
|
|
|
|
};
|
|
|
|
|
|
|
|
PHP_MINFO_FUNCTION(exif);
|
|
|
|
|
|
|
|
zend_module_entry exif_module_entry = {
|
|
|
|
"exif",
|
|
|
|
exif_functions,
|
|
|
|
NULL, NULL,
|
|
|
|
NULL, NULL,
|
|
|
|
PHP_MINFO(exif),
|
|
|
|
STANDARD_MODULE_PROPERTIES
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef COMPILE_DL_EXIF
|
|
|
|
ZEND_GET_MODULE(exif)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
PHP_MINFO_FUNCTION(exif) {
|
|
|
|
php_info_print_table_start();
|
|
|
|
php_info_print_table_row(2, "EXIF Support", "enabled" );
|
|
|
|
php_info_print_table_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
JPEG markers consist of one or more 0xFF bytes, followed by a marker
|
|
|
|
code byte (which is not an FF). Here are the marker codes of interest
|
|
|
|
in this program. (See jdmarker.c for a more complete list.)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define M_SOF0 0xC0 /* Start Of Frame N */
|
|
|
|
#define M_SOF1 0xC1 /* N indicates which compression process */
|
|
|
|
#define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */
|
|
|
|
#define M_SOF3 0xC3
|
|
|
|
#define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */
|
|
|
|
#define M_SOF6 0xC6
|
|
|
|
#define M_SOF7 0xC7
|
|
|
|
#define M_SOF9 0xC9
|
|
|
|
#define M_SOF10 0xCA
|
|
|
|
#define M_SOF11 0xCB
|
|
|
|
#define M_SOF13 0xCD
|
|
|
|
#define M_SOF14 0xCE
|
|
|
|
#define M_SOF15 0xCF
|
|
|
|
#define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */
|
|
|
|
#define M_EOI 0xD9 /* End Of Image (end of datastream) */
|
|
|
|
#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */
|
|
|
|
#define M_EXIF 0xE1
|
|
|
|
#define M_COM 0xFE /* COMment */
|
|
|
|
|
|
|
|
|
|
|
|
#define PSEUDO_IMAGE_MARKER 0x123; /* Extra value. */
|
|
|
|
|
|
|
|
/*
|
|
|
|
Get 16 bits motorola order (always) for jpeg header stuff.
|
|
|
|
*/
|
|
|
|
static int Get16m(void *Short)
|
|
|
|
{
|
|
|
|
return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Process a COM marker.
|
|
|
|
We want to print out the marker contents as legible text;
|
|
|
|
we must guard against random junk and varying newline representations.
|
|
|
|
*/
|
|
|
|
static void process_COM (ImageInfoType *ImageInfo, uchar *Data, int length)
|
|
|
|
{
|
|
|
|
int ch;
|
|
|
|
char Comment[250];
|
|
|
|
int nch;
|
|
|
|
int a;
|
|
|
|
|
|
|
|
nch = 0;
|
|
|
|
|
|
|
|
if (length > 200) length = 200; /* Truncate if it won't fit in our structure. */
|
|
|
|
|
|
|
|
for (a=2;a<length;a++) {
|
|
|
|
ch = Data[a];
|
|
|
|
|
|
|
|
if (ch == '\r' && Data[a+1] == '\n') continue; /* Remove cr followed by lf. */
|
|
|
|
|
|
|
|
if (isprint(ch) || ch == '\n' || ch == '\t') {
|
|
|
|
Comment[nch++] = (char)ch;
|
|
|
|
} else {
|
|
|
|
Comment[nch++] = '?';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Comment[nch] = '\0'; /* Null terminate */
|
|
|
|
|
|
|
|
/*
|
|
|
|
if (ShowTags) {
|
|
|
|
printf("COM marker comment: %s\n",Comment);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
strcpy(ImageInfo->Comments,Comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Process a SOFn marker. This is useful for the image dimensions */
|
|
|
|
static void process_SOFn (ImageInfoType *ImageInfo, uchar *Data, int marker)
|
|
|
|
{
|
|
|
|
int data_precision, num_components;
|
|
|
|
const char *process;
|
|
|
|
|
|
|
|
data_precision = Data[2];
|
|
|
|
ImageInfo->Height = Get16m(Data+3);
|
|
|
|
ImageInfo->Width = Get16m(Data+5);
|
|
|
|
num_components = Data[7];
|
|
|
|
|
|
|
|
if (num_components == 3) {
|
|
|
|
ImageInfo->IsColor = 1;
|
|
|
|
} else {
|
|
|
|
ImageInfo->IsColor = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (marker) {
|
|
|
|
case M_SOF0: process = "Baseline"; break;
|
|
|
|
case M_SOF1: process = "Extended sequential"; break;
|
|
|
|
case M_SOF2: process = "Progressive"; break;
|
|
|
|
case M_SOF3: process = "Lossless"; break;
|
|
|
|
case M_SOF5: process = "Differential sequential"; break;
|
|
|
|
case M_SOF6: process = "Differential progressive"; break;
|
|
|
|
case M_SOF7: process = "Differential lossless"; break;
|
|
|
|
case M_SOF9: process = "Extended sequential, arithmetic coding"; break;
|
|
|
|
case M_SOF10: process = "Progressive, arithmetic coding"; break;
|
|
|
|
case M_SOF11: process = "Lossless, arithmetic coding"; break;
|
|
|
|
case M_SOF13: process = "Differential sequential, arithmetic coding"; break;
|
|
|
|
case M_SOF14: process = "Differential progressive, arithmetic coding"; break;
|
|
|
|
case M_SOF15: process = "Differential lossless, arithmetic coding"; break;
|
|
|
|
default: process = "Unknown"; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Describes format descriptor
|
|
|
|
*/
|
|
|
|
static int ExifBytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
|
|
|
|
#define NUM_FORMATS 12
|
|
|
|
|
|
|
|
#define FMT_BYTE 1
|
|
|
|
#define FMT_STRING 2
|
|
|
|
#define FMT_USHORT 3
|
|
|
|
#define FMT_ULONG 4
|
|
|
|
#define FMT_URATIONAL 5
|
|
|
|
#define FMT_SBYTE 6
|
|
|
|
#define FMT_UNDEFINED 7
|
|
|
|
#define FMT_SSHORT 8
|
|
|
|
#define FMT_SLONG 9
|
|
|
|
#define FMT_SRATIONAL 10
|
|
|
|
#define FMT_SINGLE 11
|
|
|
|
#define FMT_DOUBLE 12
|
|
|
|
|
|
|
|
/*
|
|
|
|
Describes tag values
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define TAG_EXIF_OFFSET 0x8769
|
|
|
|
#define TAG_INTEROP_OFFSET 0xa005
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
#define TAG_COMPRESSION 0x0103
|
|
|
|
|
2000-05-24 18:33:18 +08:00
|
|
|
#define TAG_MAKE 0x010F
|
|
|
|
#define TAG_MODEL 0x0110
|
2000-05-30 00:34:19 +08:00
|
|
|
#define TAG_ORIENTATION 0x0112
|
|
|
|
|
|
|
|
#define TAG_SOFTWARE 0x0131
|
|
|
|
|
|
|
|
#define TAG_THUMBOFFSET 0x0201
|
|
|
|
#define TAG_THUMBSIZE 0x0202
|
|
|
|
|
|
|
|
#define TAG_COPYRIGHT 0x8298
|
2000-05-24 18:33:18 +08:00
|
|
|
|
|
|
|
#define TAG_EXPOSURETIME 0x829A
|
|
|
|
#define TAG_FNUMBER 0x829D
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
#define TAG_GPSINFO 0x8825
|
|
|
|
#define TAG_ISOSPEED 0x8827
|
|
|
|
#define TAG_EXIFVERSION 0x9000
|
|
|
|
|
2000-05-24 18:33:18 +08:00
|
|
|
#define TAG_SHUTTERSPEED 0x9201
|
|
|
|
#define TAG_APERTURE 0x9202
|
|
|
|
#define TAG_MAXAPERTURE 0x9205
|
|
|
|
#define TAG_FOCALLENGTH 0x920A
|
|
|
|
|
|
|
|
#define TAG_DATETIME_ORIGINAL 0x9003
|
|
|
|
#define TAG_USERCOMMENT 0x9286
|
|
|
|
|
|
|
|
#define TAG_SUBJECT_DISTANCE 0x9206
|
|
|
|
#define TAG_LIGHT_SOURCE 0x9208
|
|
|
|
#define TAG_FLASH 0x9209
|
|
|
|
|
|
|
|
#define TAG_FOCALPLANEXRES 0xa20E
|
|
|
|
#define TAG_FOCALPLANEUNITS 0xa210
|
|
|
|
#define TAG_IMAGEWIDTH 0xA002
|
|
|
|
|
|
|
|
static const struct {
|
|
|
|
unsigned short Tag;
|
|
|
|
char *Desc;
|
|
|
|
} TagTable[] = {
|
|
|
|
{ 0x100, "ImageWidth"},
|
|
|
|
{ 0x101, "ImageLength"},
|
|
|
|
{ 0x102, "BitsPerSample"},
|
|
|
|
{ 0x103, "Compression"},
|
|
|
|
{ 0x106, "PhotometricInterpretation"},
|
|
|
|
{ 0x10A, "FillOrder"},
|
|
|
|
{ 0x10D, "DocumentName"},
|
|
|
|
{ 0x10E, "ImageDescription"},
|
|
|
|
{ 0x10F, "Make"},
|
|
|
|
{ 0x110, "Model"},
|
|
|
|
{ 0x111, "StripOffsets"},
|
|
|
|
{ 0x112, "Orientation"},
|
|
|
|
{ 0x115, "SamplesPerPixel"},
|
|
|
|
{ 0x116, "RowsPerStrip"},
|
|
|
|
{ 0x117, "StripByteCounts"},
|
|
|
|
{ 0x11A, "XResolution"},
|
|
|
|
{ 0x11B, "YResolution"},
|
|
|
|
{ 0x11C, "PlanarConfiguration"},
|
|
|
|
{ 0x128, "ResolutionUnit"},
|
|
|
|
{ 0x12D, "TransferFunction"},
|
|
|
|
{ 0x131, "Software"},
|
|
|
|
{ 0x132, "DateTime"},
|
|
|
|
{ 0x13B, "Artist"},
|
|
|
|
{ 0x13E, "WhitePoint"},
|
|
|
|
{ 0x13F, "PrimaryChromaticities"},
|
|
|
|
{ 0x156, "TransferRange"},
|
|
|
|
{ 0x200, "JPEGProc"},
|
|
|
|
{ 0x201, "JPEGInterchangeFormat"},
|
|
|
|
{ 0x202, "JPEGInterchangeFormatLength"},
|
|
|
|
{ 0x211, "YCbCrCoefficients"},
|
|
|
|
{ 0x212, "YCbCrSubSampling"},
|
|
|
|
{ 0x213, "YCbCrPositioning"},
|
|
|
|
{ 0x214, "ReferenceBlackWhite"},
|
|
|
|
{ 0x828D, "CFARepeatPatternDim"},
|
|
|
|
{ 0x828E, "CFAPattern"},
|
|
|
|
{ 0x828F, "BatteryLevel"},
|
|
|
|
{ 0x8298, "Copyright"},
|
|
|
|
{ 0x829A, "ExposureTime"},
|
|
|
|
{ 0x829D, "FNumber"},
|
|
|
|
{ 0x83BB, "IPTC/NAA"},
|
|
|
|
{ 0x8769, "ExifOffset"},
|
|
|
|
{ 0x8773, "InterColorProfile"},
|
|
|
|
{ 0x8822, "ExposureProgram"},
|
|
|
|
{ 0x8824, "SpectralSensitivity"},
|
|
|
|
{ 0x8825, "GPSInfo"},
|
|
|
|
{ 0x8827, "ISOSpeedRatings"},
|
|
|
|
{ 0x8828, "OECF"},
|
|
|
|
{ 0x9000, "ExifVersion"},
|
|
|
|
{ 0x9003, "DateTimeOriginal"},
|
|
|
|
{ 0x9004, "DateTimeDigitized"},
|
|
|
|
{ 0x9101, "ComponentsConfiguration"},
|
|
|
|
{ 0x9102, "CompressedBitsPerPixel"},
|
|
|
|
{ 0x9201, "ShutterSpeedValue"},
|
|
|
|
{ 0x9202, "ApertureValue"},
|
|
|
|
{ 0x9203, "BrightnessValue"},
|
|
|
|
{ 0x9204, "ExposureBiasValue"},
|
|
|
|
{ 0x9205, "MaxApertureValue"},
|
|
|
|
{ 0x9206, "SubjectDistance"},
|
|
|
|
{ 0x9207, "MeteringMode"},
|
|
|
|
{ 0x9208, "LightSource"},
|
|
|
|
{ 0x9209, "Flash"},
|
|
|
|
{ 0x920A, "FocalLength"},
|
|
|
|
{ 0x927C, "MakerNote"},
|
|
|
|
{ 0x9286, "UserComment"},
|
|
|
|
{ 0x9290, "SubSecTime"},
|
|
|
|
{ 0x9291, "SubSecTimeOriginal"},
|
|
|
|
{ 0x9292, "SubSecTimeDigitized"},
|
|
|
|
{ 0xA000, "FlashPixVersion"},
|
|
|
|
{ 0xA001, "ColorSpace"},
|
|
|
|
{ 0xA002, "ExifImageWidth"},
|
|
|
|
{ 0xA003, "ExifImageLength"},
|
|
|
|
{ 0xA005, "InteroperabilityOffset"},
|
|
|
|
{ 0xA20B, "FlashEnergy"}, /* 0x920B in TIFF/EP */
|
|
|
|
{ 0xA20C, "SpatialFrequencyResponse"}, /* 0x920C - - */
|
|
|
|
{ 0xA20E, "FocalPlaneXResolution"}, /* 0x920E - - */
|
|
|
|
{ 0xA20F, "FocalPlaneYResolution"}, /* 0x920F - - */
|
|
|
|
{ 0xA210, "FocalPlaneResolutionUnit"}, /* 0x9210 - - */
|
|
|
|
{ 0xA214, "SubjectLocation"}, /* 0x9214 - - */
|
|
|
|
{ 0xA215, "ExposureIndex"}, /* 0x9215 - - */
|
|
|
|
{ 0xA217, "SensingMethod"}, /* 0x9217 - - */
|
|
|
|
{ 0xA300, "FileSource"},
|
|
|
|
{ 0xA301, "SceneType"},
|
|
|
|
{ 0, NULL}
|
|
|
|
} ;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Convert a 16 bit unsigned value from file's native byte order */
|
|
|
|
static int Get16u(void *Short, int MotorolaOrder)
|
|
|
|
{
|
|
|
|
if (MotorolaOrder) {
|
|
|
|
return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
|
|
|
|
} else {
|
|
|
|
return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert a 32 bit signed value from file's native byte order */
|
|
|
|
static int Get32s(void *Long, int MotorolaOrder)
|
|
|
|
{
|
|
|
|
if (MotorolaOrder) {
|
|
|
|
return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16)
|
|
|
|
| (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 );
|
|
|
|
} else {
|
|
|
|
return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16)
|
|
|
|
| (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert a 32 bit unsigned value from file's native byte order */
|
|
|
|
static unsigned Get32u(void *Long, int MotorolaOrder)
|
|
|
|
{
|
|
|
|
return (unsigned)Get32s(Long, MotorolaOrder) & 0xffffffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Evaluate number, be it int, rational, or float from directory. */
|
|
|
|
static double ConvertAnyFormat(void *ValuePtr, int Format, int MotorolaOrder)
|
|
|
|
{
|
|
|
|
double Value;
|
|
|
|
Value = 0;
|
|
|
|
|
|
|
|
switch(Format) {
|
|
|
|
case FMT_SBYTE: Value = *(signed char *)ValuePtr; break;
|
|
|
|
case FMT_BYTE: Value = *(uchar *)ValuePtr; break;
|
|
|
|
|
|
|
|
case FMT_USHORT: Value = Get16u(ValuePtr,MotorolaOrder); break;
|
|
|
|
case FMT_ULONG: Value = Get32u(ValuePtr,MotorolaOrder); break;
|
|
|
|
|
|
|
|
case FMT_URATIONAL:
|
|
|
|
case FMT_SRATIONAL:
|
|
|
|
{
|
|
|
|
int Num,Den;
|
|
|
|
Num = Get32s(ValuePtr,MotorolaOrder);
|
|
|
|
Den = Get32s(4+(char *)ValuePtr,MotorolaOrder);
|
|
|
|
if (Den == 0) {
|
|
|
|
Value = 0;
|
|
|
|
} else {
|
|
|
|
Value = (double)Num/Den;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr,MotorolaOrder); break;
|
|
|
|
case FMT_SLONG: Value = Get32s(ValuePtr,MotorolaOrder); break;
|
|
|
|
|
|
|
|
/* Not sure if this is correct (never seen float used in Exif format) */
|
|
|
|
case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break;
|
|
|
|
case FMT_DOUBLE: Value = *(double *)ValuePtr; break;
|
|
|
|
}
|
|
|
|
return Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process one of the nested EXIF directories. */
|
|
|
|
static void ProcessExifDir(ImageInfoType *ImageInfo, char *DirStart, char *OffsetBase, unsigned ExifLength, char *LastExifRefd)
|
|
|
|
{
|
|
|
|
int de;
|
|
|
|
int a;
|
|
|
|
int NumDirEntries;
|
|
|
|
|
|
|
|
NumDirEntries = Get16u(DirStart, ImageInfo->MotorolaOrder);
|
|
|
|
|
|
|
|
if ((DirStart+2+NumDirEntries*12) > (OffsetBase+ExifLength)) {
|
|
|
|
php_error(E_ERROR,"Illegally sized directory");
|
|
|
|
}
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
|
2000-05-24 18:33:18 +08:00
|
|
|
/*
|
|
|
|
if (ShowTags) {
|
|
|
|
printf("Directory with %d entries\n",NumDirEntries);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (de=0;de<NumDirEntries;de++) {
|
|
|
|
int Tag, Format, Components;
|
|
|
|
char *ValuePtr;
|
|
|
|
int ByteCount;
|
|
|
|
char *DirEntry;
|
|
|
|
DirEntry = DirStart+2+12*de;
|
|
|
|
|
|
|
|
Tag = Get16u(DirEntry, ImageInfo->MotorolaOrder);
|
|
|
|
Format = Get16u(DirEntry+2, ImageInfo->MotorolaOrder);
|
|
|
|
Components = Get32u(DirEntry+4, ImageInfo->MotorolaOrder);
|
|
|
|
|
|
|
|
if ((Format-1) >= NUM_FORMATS) {
|
|
|
|
/* (-1) catches illegal zero case as unsigned underflows to positive large. */
|
|
|
|
php_error(E_ERROR,"Illegal format code in EXIF dir");
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteCount = Components * ExifBytesPerFormat[Format];
|
|
|
|
|
|
|
|
if (ByteCount > 4) {
|
|
|
|
unsigned OffsetVal;
|
|
|
|
OffsetVal = Get32u(DirEntry+8, ImageInfo->MotorolaOrder);
|
|
|
|
/* If its bigger than 4 bytes, the dir entry contains an offset. */
|
|
|
|
if (OffsetVal+ByteCount > ExifLength) {
|
|
|
|
/* Bogus pointer offset and / or bytecount value */
|
|
|
|
/* printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength); */
|
|
|
|
|
|
|
|
php_error(E_ERROR,"Illegal pointer offset value in EXIF");
|
|
|
|
}
|
|
|
|
ValuePtr = OffsetBase+OffsetVal;
|
|
|
|
} else {
|
|
|
|
/* 4 bytes or less and value is in the dir entry itself */
|
|
|
|
ValuePtr = DirEntry+8;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LastExifRefd < ValuePtr+ByteCount) {
|
|
|
|
/*
|
|
|
|
Keep track of last byte in the exif header that was actually referenced.
|
|
|
|
That way, we know where the discardable thumbnail data begins.
|
|
|
|
*/
|
|
|
|
LastExifRefd = ValuePtr+ByteCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Extract useful components of tag */
|
|
|
|
switch(Tag) {
|
|
|
|
|
|
|
|
case TAG_MAKE:
|
|
|
|
strncpy(ImageInfo->CameraMake, ValuePtr, 31);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_MODEL:
|
|
|
|
strncpy(ImageInfo->CameraModel, ValuePtr, 63);
|
|
|
|
break;
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
case TAG_GPSINFO:
|
|
|
|
strncpy(ImageInfo->GPSinfo, ValuePtr, 47);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_EXIFVERSION:
|
|
|
|
strncpy(ImageInfo->ExifVersion, ValuePtr, 15);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_COPYRIGHT:
|
|
|
|
strncpy(ImageInfo->Copyright, ValuePtr, 31);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_SOFTWARE:
|
|
|
|
strncpy(ImageInfo->Software, ValuePtr, 31);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_ORIENTATION:
|
|
|
|
ImageInfo->Orientation = (int)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_ISOSPEED:
|
|
|
|
ImageInfo->ISOspeed = (int)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
2000-05-24 18:33:18 +08:00
|
|
|
case TAG_DATETIME_ORIGINAL:
|
|
|
|
strncpy(ImageInfo->DateTime, ValuePtr, 19);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_USERCOMMENT:
|
|
|
|
/* Olympus has this padded with trailing spaces. Remove these first. */
|
|
|
|
for (a=ByteCount;;) {
|
|
|
|
a--;
|
|
|
|
if ((ValuePtr)[a] == ' ') {
|
|
|
|
(ValuePtr)[a] = '\0';
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (a == 0) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy the comment */
|
|
|
|
if (memcmp(ValuePtr, "ASCII",5) == 0) {
|
|
|
|
for (a=5;a<10;a++) {
|
|
|
|
int c;
|
|
|
|
c = (ValuePtr)[a];
|
|
|
|
if (c != '\0' && c != ' ') {
|
|
|
|
strncpy(ImageInfo->Comments, a+ValuePtr, 199);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
strncpy(ImageInfo->Comments, ValuePtr, 199);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_FNUMBER:
|
|
|
|
/* Simplest way of expressing aperture, so I trust it the most.
|
|
|
|
(overwrite previously computd value if there is one) */
|
|
|
|
ImageInfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_APERTURE:
|
|
|
|
case TAG_MAXAPERTURE:
|
|
|
|
/* More relevant info always comes earlier, so only use this field if we don't
|
|
|
|
have appropriate aperture information yet. */
|
|
|
|
if (ImageInfo->ApertureFNumber == 0) {
|
|
|
|
ImageInfo->ApertureFNumber
|
|
|
|
= (float)exp(ConvertAnyFormat(ValuePtr, Format, ImageInfo->MotorolaOrder)*log(2)*0.5);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_FOCALLENGTH:
|
|
|
|
/* Nice digital cameras actually save the focal length as a function
|
|
|
|
of how farthey are zoomed in. */
|
|
|
|
ImageInfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_SUBJECT_DISTANCE:
|
|
|
|
/* Inidcates the distacne the autofocus camera is focused to.
|
|
|
|
Tends to be less accurate as distance increases. */
|
|
|
|
ImageInfo->Distance = (float)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_EXPOSURETIME:
|
|
|
|
/* Simplest way of expressing exposure time, so I trust it most.
|
|
|
|
(overwrite previously computd value if there is one) */
|
|
|
|
ImageInfo->ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_SHUTTERSPEED:
|
|
|
|
/* More complicated way of expressing exposure time, so only use
|
|
|
|
this value if we don't already have it from somewhere else. */
|
|
|
|
if (ImageInfo->ExposureTime == 0) {
|
|
|
|
ImageInfo->ExposureTime
|
|
|
|
= (float)(1/exp(ConvertAnyFormat(ValuePtr, Format, ImageInfo->MotorolaOrder)*log(2)));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_FLASH:
|
|
|
|
if (ConvertAnyFormat(ValuePtr, Format, ImageInfo->MotorolaOrder)) {
|
|
|
|
ImageInfo->FlashUsed = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_IMAGEWIDTH:
|
|
|
|
ImageInfo->ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_FOCALPLANEXRES:
|
|
|
|
ImageInfo->FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_FOCALPLANEUNITS:
|
|
|
|
switch((int)ConvertAnyFormat(ValuePtr, Format,ImageInfo->MotorolaOrder)) {
|
|
|
|
case 1: ImageInfo->FocalplaneUnits = 25.4; break; /* inch */
|
|
|
|
case 2:
|
|
|
|
/* According to the information I was using, 2 measn meters.
|
|
|
|
But looking at the Cannon powershot's files, inches is the only
|
|
|
|
sensible value. */
|
|
|
|
ImageInfo->FocalplaneUnits = 25.4;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: ImageInfo->FocalplaneUnits = 10; break; /* centimeter */
|
|
|
|
case 4: ImageInfo->FocalplaneUnits = 1; break; /* milimeter */
|
|
|
|
case 5: ImageInfo->FocalplaneUnits = .001; break; /* micrometer */
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TAG_LIGHT_SOURCE:
|
|
|
|
/* Rarely set or useful. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET) {
|
|
|
|
char *SubdirStart;
|
|
|
|
SubdirStart = OffsetBase + Get32u(ValuePtr, ImageInfo->MotorolaOrder);
|
|
|
|
if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength) {
|
|
|
|
php_error(E_ERROR,"Illegal subdirectory link");
|
|
|
|
}
|
|
|
|
ProcessExifDir(ImageInfo, SubdirStart, OffsetBase, ExifLength, LastExifRefd);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2000-05-30 00:34:19 +08:00
|
|
|
Process an EXIF marker
|
2000-05-24 18:33:18 +08:00
|
|
|
Describes all the drivel that most digital cameras include...
|
|
|
|
*/
|
|
|
|
static void process_EXIF (ImageInfoType *ImageInfo, char *CharBuf, unsigned int length, char *LastExifRefd)
|
|
|
|
{
|
|
|
|
ImageInfo->FlashUsed = 0; /* If it s from a digicam, and it used flash, it says so. */
|
|
|
|
LastExifRefd = CharBuf;
|
|
|
|
|
|
|
|
ImageInfo->FocalplaneXRes = 0;
|
|
|
|
ImageInfo->FocalplaneUnits = 0;
|
|
|
|
ImageInfo->ExifImageWidth = 0;
|
|
|
|
|
|
|
|
{ /* Check the EXIF header component */
|
|
|
|
static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
|
|
|
|
if (memcmp(CharBuf+2, ExifHeader,6)) {
|
|
|
|
php_error(E_ERROR,"Incorrect Exif header");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (memcmp(CharBuf+8,"II",2) == 0) {
|
|
|
|
/* if (ShowTags) printf("Exif section in Intel order\n"); */
|
|
|
|
ImageInfo->MotorolaOrder = 0;
|
|
|
|
} else {
|
|
|
|
if (memcmp(CharBuf+8,"MM",2) == 0) {
|
|
|
|
/* if (ShowTags) printf("Exif section in Motorola order\n"); */
|
|
|
|
ImageInfo->MotorolaOrder = 1;
|
|
|
|
} else {
|
|
|
|
php_error(E_ERROR,"Invalid Exif alignment marker.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the next two values for correctness. */
|
|
|
|
if (Get16u(CharBuf+10,ImageInfo->MotorolaOrder) != 0x2a
|
|
|
|
|| Get32u(CharBuf+12,ImageInfo->MotorolaOrder) != 0x08) {
|
|
|
|
php_error(E_ERROR,"Invalid Exif start (1)");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First directory starts 16 bytes in. Offsets start at 8 bytes in. */
|
|
|
|
ProcessExifDir(ImageInfo, CharBuf+16, CharBuf+8, length-6, LastExifRefd);
|
|
|
|
|
|
|
|
/* Compute the CCD width, in milimeters. */
|
|
|
|
if (ImageInfo->FocalplaneXRes != 0) {
|
|
|
|
ImageInfo->CCDWidth = (float)(ImageInfo->ExifImageWidth * ImageInfo->FocalplaneUnits / ImageInfo->FocalplaneXRes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the marker stream until SOS or EOI is seen; */
|
|
|
|
static int scan_JPEG_header (ImageInfoType *ImageInfo, FILE *infile, Section_t *Sections, int *SectionsRead, int ReadAll, char *LastExifRefd)
|
|
|
|
{
|
|
|
|
int a;
|
|
|
|
int HaveCom = FALSE;
|
|
|
|
|
|
|
|
a = fgetc(infile);
|
|
|
|
if (a != 0xff || fgetc(infile) != M_SOI) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(*SectionsRead=0;*SectionsRead < 19;) {
|
|
|
|
int itemlen;
|
|
|
|
int marker = 0;
|
|
|
|
int ll,lh, got;
|
|
|
|
uchar *Data;
|
|
|
|
|
|
|
|
for (a=0;a<7;a++) {
|
|
|
|
marker = fgetc(infile);
|
|
|
|
if (marker != 0xff) break;
|
|
|
|
}
|
|
|
|
if (marker == 0xff) {
|
|
|
|
/* 0xff is legal padding, but if we get that many, something's wrong. */
|
|
|
|
php_error(E_ERROR,"too many padding bytes!");
|
|
|
|
}
|
|
|
|
|
|
|
|
Sections[*SectionsRead].Type = marker;
|
|
|
|
|
|
|
|
/* Read the length of the section. */
|
|
|
|
lh = fgetc(infile);
|
|
|
|
ll = fgetc(infile);
|
|
|
|
|
|
|
|
itemlen = (lh << 8) | ll;
|
|
|
|
|
|
|
|
if (itemlen < 2) {
|
|
|
|
php_error(E_ERROR,"invalid marker");
|
|
|
|
}
|
|
|
|
|
|
|
|
Sections[*SectionsRead].Size = itemlen;
|
|
|
|
|
|
|
|
Data = (uchar *)emalloc(itemlen+1); /* Add 1 to allow sticking a 0 at the end. */
|
|
|
|
Sections[*SectionsRead].Data = Data;
|
|
|
|
|
|
|
|
/* Store first two pre-read bytes. */
|
|
|
|
Data[0] = (uchar)lh;
|
|
|
|
Data[1] = (uchar)ll;
|
|
|
|
|
|
|
|
got = fread(Data+2, 1, itemlen-2, infile); /* Read the whole section. */
|
|
|
|
if (got != itemlen-2) {
|
|
|
|
php_error(E_ERROR,"reading from file");
|
|
|
|
}
|
|
|
|
*SectionsRead += 1;
|
|
|
|
|
|
|
|
/*printf("Marker '%x' size %d\n",marker, itemlen);*/
|
|
|
|
switch(marker) {
|
|
|
|
case M_SOS: /* stop before hitting compressed data */
|
|
|
|
/* If reading entire image is requested, read the rest of the data. */
|
|
|
|
if (ReadAll) {
|
|
|
|
int cp, ep, size;
|
|
|
|
/* Determine how much file is left. */
|
|
|
|
cp = ftell(infile);
|
|
|
|
fseek(infile, 0, SEEK_END);
|
|
|
|
ep = ftell(infile);
|
|
|
|
fseek(infile, cp, SEEK_SET);
|
|
|
|
|
|
|
|
size = ep-cp;
|
|
|
|
Data = (uchar *)malloc(size);
|
|
|
|
if (Data == NULL) {
|
|
|
|
php_error(E_ERROR,"could not allocate data for entire image");
|
|
|
|
}
|
|
|
|
|
|
|
|
got = fread(Data, 1, size, infile);
|
|
|
|
if (got != size) {
|
|
|
|
php_error(E_ERROR,"could not read the rest of the image");
|
|
|
|
}
|
|
|
|
|
|
|
|
Sections[*SectionsRead].Data = Data;
|
|
|
|
Sections[*SectionsRead].Size = size;
|
|
|
|
Sections[*SectionsRead].Type = PSEUDO_IMAGE_MARKER;
|
|
|
|
(*SectionsRead)++;
|
|
|
|
/*
|
|
|
|
*HaveAll = 1;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case M_EOI: /* in case it's a tables-only JPEG stream */
|
|
|
|
php_error(E_ERROR,"No image in jpeg!");
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case M_COM: /* Comment section */
|
|
|
|
if (HaveCom) {
|
|
|
|
(*SectionsRead) -= 1;
|
|
|
|
efree(Sections[*SectionsRead].Data);
|
|
|
|
} else {
|
|
|
|
process_COM(ImageInfo, Data, itemlen);
|
|
|
|
HaveCom = TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case M_EXIF:
|
|
|
|
if (*SectionsRead <= 2) {
|
|
|
|
/* Seen files from some 'U-lead' software with Vivitar scanner
|
|
|
|
that uses marker 31 later in the file (no clue what for!) */
|
|
|
|
process_EXIF(ImageInfo, (char *)Data, itemlen, LastExifRefd);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case M_SOF0:
|
|
|
|
case M_SOF1:
|
|
|
|
case M_SOF2:
|
|
|
|
case M_SOF3:
|
|
|
|
case M_SOF5:
|
|
|
|
case M_SOF6:
|
|
|
|
case M_SOF7:
|
|
|
|
case M_SOF9:
|
|
|
|
case M_SOF10:
|
|
|
|
case M_SOF11:
|
|
|
|
case M_SOF13:
|
|
|
|
case M_SOF14:
|
|
|
|
case M_SOF15:
|
|
|
|
process_SOFn(ImageInfo, Data, marker);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* skip any other marker silently. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Discard read data.
|
|
|
|
*/
|
|
|
|
void DiscardData(Section_t *Sections, int *SectionsRead)
|
|
|
|
{
|
|
|
|
int a;
|
|
|
|
for (a=0;a<*SectionsRead-1;a++) {
|
|
|
|
efree(Sections[a].Data);
|
|
|
|
}
|
|
|
|
*SectionsRead = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Read image data.
|
|
|
|
*/
|
|
|
|
int ReadJpegFile(ImageInfoType *ImageInfo, Section_t *Sections,
|
|
|
|
int *SectionsRead, char *FileName,
|
|
|
|
int ReadAll, char *LastExifRefd)
|
|
|
|
{
|
|
|
|
FILE *infile;
|
|
|
|
int ret;
|
|
|
|
char *tmp;
|
|
|
|
|
|
|
|
infile = V_FOPEN(FileName, "rb"); /* Unix ignores 'b', windows needs it. */
|
|
|
|
|
|
|
|
if (infile == NULL) {
|
|
|
|
php_error(E_ERROR, "Unable to open '%s'", FileName);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
/* CurrentFile = FileName; */
|
|
|
|
|
|
|
|
/* Start with an empty image information structure. */
|
|
|
|
memset(ImageInfo, 0, sizeof(*ImageInfo));
|
|
|
|
memset(Sections, 0, sizeof(*Sections));
|
|
|
|
|
|
|
|
tmp = php_basename(FileName,strlen(FileName));
|
|
|
|
strncpy(ImageInfo->FileName, tmp, 119);
|
|
|
|
efree(tmp);
|
|
|
|
ImageInfo->FocalLength = 0;
|
|
|
|
ImageInfo->ExposureTime = 0;
|
|
|
|
ImageInfo->ApertureFNumber = 0;
|
|
|
|
ImageInfo->Distance = 0;
|
|
|
|
ImageInfo->CCDWidth = 0;
|
|
|
|
ImageInfo->FlashUsed = -1;
|
|
|
|
|
|
|
|
{
|
|
|
|
/* Store file date/time. */
|
|
|
|
struct stat st;
|
|
|
|
if (stat(FileName, &st) >= 0) {
|
|
|
|
ImageInfo->FileDateTime = st.st_mtime;
|
|
|
|
ImageInfo->FileSize = st.st_size;
|
|
|
|
} else {
|
|
|
|
php_error(E_ERROR,"Can't get file statitics");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Scan the JPEG headers. */
|
|
|
|
ret = scan_JPEG_header(ImageInfo, infile, Sections, SectionsRead, ReadAll, LastExifRefd);
|
|
|
|
if (!ret) {
|
|
|
|
php_error(E_ERROR,"Invalid Jpeg file: '%s'\n",FileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(infile);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
PHPAPI int php_read_jpeg_exif(ImageInfoType *ImageInfo, char *FileName, int ReadAll) {
|
2000-05-24 18:33:18 +08:00
|
|
|
Section_t Sections[20];
|
|
|
|
int SectionsRead;
|
|
|
|
char *LastExifRefd=NULL;
|
2000-05-30 00:34:19 +08:00
|
|
|
int ret, i;
|
|
|
|
int thumbsize=0;
|
2000-05-24 18:33:18 +08:00
|
|
|
|
|
|
|
ImageInfo->MotorolaOrder = 0;
|
|
|
|
|
|
|
|
ret = ReadJpegFile(ImageInfo, Sections, &SectionsRead, FileName, ReadAll, LastExifRefd);
|
2000-05-30 00:34:19 +08:00
|
|
|
/*
|
|
|
|
* Thought this might pick out the embedded thumbnail, but it doesn't work.
|
|
|
|
for (i=0;i<SectionsRead-1;i++) {
|
|
|
|
if (Sections[i].Type == M_EXIF) {
|
|
|
|
thumbsize = Sections[i].Size;
|
|
|
|
if(thumbsize>0) {
|
|
|
|
ImageInfo->Thumbnail = emalloc(thumbsize+5);
|
|
|
|
ImageInfo->ThumbnailSize = thumbsize;
|
|
|
|
ImageInfo->Thumbnail[0] = 0xff;
|
|
|
|
ImageInfo->Thumbnail[1] = 0xd8;
|
|
|
|
ImageInfo->Thumbnail[2] = 0xff;
|
|
|
|
memcpy(ImageInfo->Thumbnail+4, Sections[i].Data, thumbsize+4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
if (ret != FALSE) {
|
|
|
|
DiscardData(Sections, &SectionsRead);
|
|
|
|
}
|
2000-05-24 18:33:18 +08:00
|
|
|
return(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* {{{ proto string read_exif_data(string filename)
|
|
|
|
Reads the EXIF header data from a JPEG file */
|
|
|
|
PHP_FUNCTION(read_exif_data) {
|
|
|
|
pval **p_name;
|
2000-06-06 03:47:54 +08:00
|
|
|
int ac = ZEND_NUM_ARGS(), ret;
|
2000-05-24 18:33:18 +08:00
|
|
|
ImageInfoType ImageInfo;
|
|
|
|
char tmp[64];
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
ImageInfo.Thumbnail = NULL;
|
|
|
|
ImageInfo.ThumbnailSize = 0;
|
|
|
|
|
2000-05-24 18:33:18 +08:00
|
|
|
if (ac != 1 || zend_get_parameters_ex(ac, &p_name) == FAILURE)
|
|
|
|
WRONG_PARAM_COUNT;
|
|
|
|
|
|
|
|
convert_to_string_ex(p_name);
|
|
|
|
ret = php_read_jpeg_exif(&ImageInfo, (*p_name)->value.str.val,1);
|
|
|
|
if (array_init(return_value) == FAILURE) {
|
|
|
|
RETURN_FALSE;
|
|
|
|
}
|
|
|
|
add_assoc_string(return_value,"FileName",ImageInfo.FileName,1);
|
|
|
|
add_assoc_long(return_value,"FileDateTime",ImageInfo.FileDateTime);
|
|
|
|
add_assoc_long(return_value,"FileSize",ImageInfo.FileSize);
|
|
|
|
if (ImageInfo.CameraMake[0]) {
|
|
|
|
add_assoc_string(return_value,"CameraMake",ImageInfo.CameraMake,1);
|
|
|
|
}
|
|
|
|
if (ImageInfo.CameraModel[0]) {
|
|
|
|
add_assoc_string(return_value,"CameraModel",ImageInfo.CameraModel,1);
|
|
|
|
}
|
|
|
|
if (ImageInfo.DateTime[0]) {
|
|
|
|
add_assoc_string(return_value,"DateTime",ImageInfo.DateTime,1);
|
|
|
|
}
|
|
|
|
add_assoc_long(return_value,"Height",ImageInfo.Height);
|
|
|
|
add_assoc_long(return_value,"Width",ImageInfo.Width);
|
|
|
|
add_assoc_long(return_value,"IsColor",ImageInfo.IsColor);
|
|
|
|
if(ImageInfo.FlashUsed >= 0) {
|
|
|
|
add_assoc_long(return_value,"FlashUsed",ImageInfo.FlashUsed);
|
|
|
|
}
|
|
|
|
if (ImageInfo.FocalLength) {
|
|
|
|
sprintf(tmp,"%4.1fmm",ImageInfo.FocalLength);
|
|
|
|
add_assoc_string(return_value,"FocalLength",tmp,1);
|
|
|
|
if(ImageInfo.CCDWidth) {
|
|
|
|
sprintf(tmp,"%dmm",(int)(ImageInfo.FocalLength/ImageInfo.CCDWidth*35+0.5));
|
|
|
|
add_assoc_string(return_value,"35mmFocalLength",tmp,1);
|
|
|
|
}
|
|
|
|
add_assoc_double(return_value,"RawFocalLength",ImageInfo.FocalLength);
|
|
|
|
}
|
|
|
|
if(ImageInfo.ExposureTime) {
|
|
|
|
if(ImageInfo.ExposureTime <= 0.5) {
|
|
|
|
sprintf(tmp,"%6.3f s (1/%d)",ImageInfo.ExposureTime,(int)(0.5 + 1/ImageInfo.ExposureTime));
|
|
|
|
} else {
|
|
|
|
sprintf(tmp,"%6.3f s",ImageInfo.ExposureTime);
|
|
|
|
}
|
|
|
|
add_assoc_string(return_value,"ExposureTime",tmp,1);
|
|
|
|
add_assoc_double(return_value,"RawExposureTime",ImageInfo.ExposureTime);
|
|
|
|
}
|
|
|
|
if(ImageInfo.ApertureFNumber) {
|
|
|
|
sprintf(tmp,"f/%4.1f",ImageInfo.ApertureFNumber);
|
|
|
|
add_assoc_string(return_value,"ApertureFNumber",tmp,1);
|
|
|
|
add_assoc_double(return_value,"RawApertureFNumber",ImageInfo.ApertureFNumber);
|
|
|
|
}
|
|
|
|
if(ImageInfo.Distance) {
|
|
|
|
if(ImageInfo.Distance<0) {
|
|
|
|
add_assoc_string(return_value,"FocusDistance","Infinite",1);
|
|
|
|
} else {
|
|
|
|
sprintf(tmp,"%5.2fm",ImageInfo.Distance);
|
|
|
|
add_assoc_string(return_value,"FocusDistance",tmp,1);
|
|
|
|
}
|
|
|
|
add_assoc_double(return_value,"RawFocusDistance",ImageInfo.Distance);
|
|
|
|
}
|
|
|
|
if(ImageInfo.CCDWidth) {
|
|
|
|
add_assoc_double(return_value,"CCDWidth",ImageInfo.CCDWidth);
|
|
|
|
}
|
2000-05-30 00:34:19 +08:00
|
|
|
if(ImageInfo.Orientation) {
|
|
|
|
add_assoc_long(return_value,"Orientation",ImageInfo.Orientation);
|
|
|
|
}
|
|
|
|
if (ImageInfo.GPSinfo[0]) {
|
|
|
|
add_assoc_string(return_value,"GPSinfo",ImageInfo.GPSinfo,1);
|
|
|
|
}
|
|
|
|
if(ImageInfo.ISOspeed) {
|
|
|
|
add_assoc_long(return_value,"ISOspeed",ImageInfo.ISOspeed);
|
|
|
|
}
|
|
|
|
if (ImageInfo.ExifVersion[0]) {
|
|
|
|
add_assoc_string(return_value,"ExifVersion",ImageInfo.ExifVersion,1);
|
|
|
|
}
|
|
|
|
if (ImageInfo.Copyright[0]) {
|
|
|
|
add_assoc_string(return_value,"Copyright",ImageInfo.Copyright,1);
|
|
|
|
}
|
|
|
|
if (ImageInfo.Software[0]) {
|
|
|
|
add_assoc_string(return_value,"Software",ImageInfo.Software,1);
|
|
|
|
}
|
2000-05-24 18:33:18 +08:00
|
|
|
if(ImageInfo.Comments[0]) {
|
|
|
|
add_assoc_string(return_value,"Comments",ImageInfo.Comments,1);
|
|
|
|
}
|
2000-05-30 00:34:19 +08:00
|
|
|
if(ImageInfo.ThumbnailSize) {
|
|
|
|
add_assoc_stringl(return_value,"Thumbnail",ImageInfo.Thumbnail,ImageInfo.ThumbnailSize,1);
|
|
|
|
add_assoc_long(return_value,"ThumbnailSize",ImageInfo.ThumbnailSize);
|
|
|
|
efree(ImageInfo.Thumbnail);
|
|
|
|
}
|
2000-05-24 18:33:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2000-05-30 00:34:19 +08:00
|
|
|
/*
|
|
|
|
* Local variables:
|
|
|
|
* tab-width: 4
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* End:
|
|
|
|
*/
|