Arquivos
hhvm/hphp/runtime/ext/ext_image.cpp
T
Sara Golemon 6ec64e8bf9 make #includes consistent
I was learning from @jdelong and he said that you should use
double quotes for local includes and angle brackets for library
includes. I asked why our code was the way it was, and he said he wanted
to clean it up. I beat him to it :)

Conflicts:

	hphp/runtime/base/server/admin_request_handler.cpp
	hphp/runtime/vm/named_entity.h
2013-05-15 13:05:06 -07:00

8028 linhas
245 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.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. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/ext_image.h"
#include "hphp/runtime/ext/ext_file.h"
#include "hphp/runtime/base/zend/zend_printf.h"
#include "hphp/runtime/base/zend/zend_string.h"
#include "hphp/runtime/base/util/request_local.h"
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/util/min_max_macros.h"
#include <gdfontt.h> /* 1 Tiny font */
#include <gdfonts.h> /* 2 Small font */
#include <gdfontmb.h> /* 3 Medium bold font */
#include <gdfontl.h> /* 4 Large font */
#include <gdfontg.h> /* 5 Giant font */
#include <zlib.h>
// #define IM_MEMORY_CHECK
namespace HPHP {
IMPLEMENT_DEFAULT_EXTENSION(exif);
IMPLEMENT_DEFAULT_EXTENSION(gd);
///////////////////////////////////////////////////////////////////////////////
StaticString Image::s_class_name("gd");
#define HAS_GDIMAGESETANTIALIASED
#if defined(HAS_GDIMAGEANTIALIAS)
#define SetAntiAliased(gd, flag) gdImageAntialias(gd, flag)
#define SetupAntiAliasedColor(gd, color) (color)
#elif defined(HAS_GDIMAGESETANTIALIASED)
#define SetAntiAliased(gd, flag) ((gd)->AA = (flag))
#define SetupAntiAliasedColor(gd, color) \
((gd)->AA ? \
gdImageSetAntiAliased(im, color), gdAntiAliased : \
color)
#else
#define SetAntiAliased(gd, flag)
#define SetupAntiAliasedColor(gd, color) (color)
#endif
///////////////////////////////////////////////////////////////////////////////
Image::~Image() {
if (m_gdImage) {
gdImageDestroy(m_gdImage);
m_gdImage = NULL;
}
}
class ImageMemoryAlloc: public RequestEventHandler {
public:
ImageMemoryAlloc() : m_mallocSize(0) {}
virtual void requestInit() {
#ifdef IM_MEMORY_CHECK
void *ptrs[1000];
int n = 1000;
if (m_mallocSize) imDump(ptrs, n);
#endif
assert(m_mallocSize == 0);
m_mallocSize = 0;
}
virtual void requestShutdown() {
#ifdef IM_MEMORY_CHECK
void *ptrs[1000];
int n = 1000;
if (m_mallocSize) imDump(ptrs, n);
assert(m_mallocSize == 0);
#endif
m_mallocSize = 0;
}
void *imMalloc(size_t size
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
assert(m_mallocSize < (size_t)RuntimeOption::ImageMemoryMaxBytes);
if (m_mallocSize + size < (size_t)RuntimeOption::ImageMemoryMaxBytes) {
#ifdef IM_MEMORY_CHECK
void *ptr = malloc(sizeof(ln) + sizeof(size) + size);
if (!ptr) return NULL;
memcpy(ptr, &ln, sizeof(ln));
memcpy((char*)ptr + sizeof(ln), &size, sizeof(size));
m_mallocSize += size;
m_alloced.insert(ptr);
return ((char *)ptr + sizeof(ln) + sizeof(size));
#else
void *ptr = malloc(sizeof(size) + size);
if (!ptr) return NULL;
memcpy(ptr, &size, sizeof(size));
m_mallocSize += size;
return ((char *)ptr + sizeof(size));
#endif
}
return NULL;
}
void *imCalloc(size_t nmemb, size_t size
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
assert(m_mallocSize < (size_t)RuntimeOption::ImageMemoryMaxBytes);
size_t bytes = nmemb * size;
if (m_mallocSize + bytes < (size_t)RuntimeOption::ImageMemoryMaxBytes) {
#ifdef IM_MEMORY_CHECK
void *ptr = malloc(sizeof(ln) + sizeof(size) + bytes);
if (!ptr) return NULL;
memset(ptr, 0, sizeof(ln) + sizeof(size) + bytes);
memcpy(ptr, &ln, sizeof(ln));
memcpy((char*)ptr + sizeof(ln), &bytes, sizeof(bytes));
m_mallocSize += bytes;
m_alloced.insert(ptr);
return ((char *)ptr + sizeof(ln) + sizeof(size));
#else
void *ptr = malloc(sizeof(size) + bytes);
if (!ptr) return NULL;
memcpy(ptr, &bytes, sizeof(bytes));
memset((char *)ptr + sizeof(size), 0, bytes);
m_mallocSize += bytes;
return ((char *)ptr + sizeof(size));
#endif
}
return NULL;
}
void imFree(void *ptr
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
size_t size;
void *sizePtr = (char *)ptr - sizeof(size);
memcpy(&size, sizePtr, sizeof(size));
m_mallocSize -= size;
#ifdef IM_MEMORY_CHECK
void *lnPtr = (char *)sizePtr - sizeof(ln);
int count = m_alloced.erase((char*)sizePtr - sizeof(ln));
assert(count == 1); // double free on failure
assert(m_mallocSize < (size_t)RuntimeOption::ImageMemoryMaxBytes);
free(lnPtr);
#else
assert(m_mallocSize < (size_t)RuntimeOption::ImageMemoryMaxBytes);
free(sizePtr);
#endif
}
// wrapper of realloc, the original buffer is freed on failure
void *imRealloc(void *ptr, size_t size
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
assert(m_mallocSize < (size_t)RuntimeOption::ImageMemoryMaxBytes);
#ifdef IM_MEMORY_CHECK
if (!ptr) return imMalloc(size, ln);
if (!size) {
imFree(ptr, ln);
return NULL;
}
#else
if (!ptr) return imMalloc(size);
if (!size) {
imFree(ptr);
return NULL;
}
#endif
void *sizePtr = (char *)ptr - sizeof(size);
size_t oldSize = 0;
if (ptr) memcpy(&oldSize, sizePtr, sizeof(oldSize));
int diff = size - oldSize;
void *tmp;
#ifdef IM_MEMORY_CHECK
void *lnPtr = (char *)sizePtr - sizeof(ln);
if (m_mallocSize + diff > (size_t)RuntimeOption::ImageMemoryMaxBytes ||
!(tmp = realloc(lnPtr, sizeof(ln) + sizeof(size) + size))) {
int count = m_alloced.erase(ptr);
assert(count == 1); // double free on failure
free(lnPtr);
return NULL;
}
memcpy(tmp, &ln, sizeof(ln));
memcpy((char*)tmp + sizeof(ln), &size, sizeof(size));
m_mallocSize += diff;
if (tmp != lnPtr) {
int count = m_alloced.erase(lnPtr);
assert(count == 1);
m_alloced.insert(tmp);
}
return ((char *)tmp + sizeof(ln) + sizeof(size));
#else
if (m_mallocSize + diff > (size_t)RuntimeOption::ImageMemoryMaxBytes ||
!(tmp = realloc(sizePtr, sizeof(size) + size))) {
free(sizePtr);
return NULL;
}
memcpy(tmp, &size, sizeof(size));
m_mallocSize += diff;
return ((char *)tmp + sizeof(size));
#endif
}
#ifdef IM_MEMORY_CHECK
void imDump(void *ptrs[], int &n) {
int i = 0;
for (std::set<void*>::iterator iter = m_alloced.begin();
iter != m_alloced.end(); ++i, ++iter) {
void *p = *iter;
assert(p);
if (i < n) ptrs[i] = p;
int ln;
size_t size;
memcpy(&ln, p, sizeof(ln));
memcpy(&size, (char*)p + sizeof(ln), sizeof(size));
printf("%d: (%p, %lu)\n", ln, p, size);
}
n = (i < n) ? i : n;
}
#endif
private:
size_t m_mallocSize;
#ifdef IM_MEMORY_CHECK
std::set<void *> m_alloced;
#endif
};
IMPLEMENT_STATIC_REQUEST_LOCAL(ImageMemoryAlloc, s_ima);
#ifdef IM_MEMORY_CHECK
#define IM_MALLOC(size) s_ima->imMalloc((size), __LINE__)
#define IM_CALLOC(nmemb, size) s_ima->imCalloc((nmemb), (size), __LINE__)
#define IM_FREE(ptr) s_ima->imFree((ptr), __LINE__)
#define IM_REALLOC(ptr, size) s_ima->imRealloc((ptr), (size), __LINE__)
#else
#define IM_MALLOC(size) s_ima->imMalloc((size))
#define IM_CALLOC(nmemb, size) s_ima->imCalloc((nmemb), (size))
#define IM_FREE(ptr) s_ima->imFree((ptr))
#define IM_REALLOC(ptr, size) s_ima->imRealloc((ptr), (size))
#endif
#define CHECK_BUFFER(begin, end, size) \
do { \
if (((char*)end) - ((char*)(begin)) < (size)) { \
raise_warning("%s/%d: Buffer overrun (%p, %p, %d)", \
__FUNCTION__, __LINE__, begin, end, size); \
return; \
} \
} while (0)
#define CHECK_BUFFER_R(begin, end, size, retcod) \
do { \
if (((char*)(end)) - ((char*)(begin)) < (size)) { \
raise_warning("%s/%d: Buffer overrun (%p, %p, %d, %d)", \
__FUNCTION__, __LINE__, begin, end, size, retcod); \
return retcod; \
} \
} while (0)
#define CHECK_ALLOC(ptr, size) \
do { \
if (!(ptr)) { \
raise_warning("%s/%d: failed to allocate %lu bytes", \
__FUNCTION__, __LINE__, ((size_t)(size))); \
return; \
} \
} while (0)
#define CHECK_ALLOC_R(ptr, size, retcod) \
do { \
if (!(ptr)) { \
raise_warning("%s/%d: failed to allocate %lu bytes", \
__FUNCTION__, __LINE__, ((size_t)(size))); \
return retcod; \
} \
} while (0)
// original Zend name is _estrndup
static char *php_strndup_impl(const char* s, uint length
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
char *p;
#ifdef IM_MEMORY_CHECK
p = (char *)s_ima->imMalloc((length+1), ln);
#else
p = (char *)s_ima->imMalloc((length+1));
#endif
CHECK_ALLOC_R(p, length+1, NULL);
memcpy(p, s, length);
p[length] = 0;
return p;
}
static char *php_strdup_impl(const char* s
#ifdef IM_MEMORY_CHECK
, int ln
#endif
) {
#ifdef IM_MEMORY_CHECK
return php_strndup_impl(s, strlen(s), ln);
#else
return php_strndup_impl(s, strlen(s));
#endif
}
#ifdef IM_MEMORY_CHECK
#define PHP_STRNDUP(var, s, length) \
do { \
if (var) s_ima->imFree((var), __LINE__); \
(var) = php_strndup_impl((s), (length), __LINE__); \
} while (0)
#define PHP_STRDUP(var, s) \
do { \
if (var) s_ima->imFree((var), __LINE__); \
(var) = php_strdup_impl(s, __LINE__); \
} while (0)
#else
#define PHP_STRNDUP(var, s, length) \
do { \
if (var) IM_FREE(var); \
(var) = php_strndup_impl((s), (length)); \
} while (0)
#define PHP_STRDUP(var, s) \
do { \
if (var) IM_FREE(var); \
(var) = php_strdup_impl(s); \
} while (0)
#endif
typedef enum {
IMAGE_FILETYPE_UNKNOWN=0,
IMAGE_FILETYPE_GIF=1,
IMAGE_FILETYPE_JPEG,
IMAGE_FILETYPE_PNG,
IMAGE_FILETYPE_SWF,
IMAGE_FILETYPE_PSD,
IMAGE_FILETYPE_BMP,
IMAGE_FILETYPE_TIFF_II, /* intel */
IMAGE_FILETYPE_TIFF_MM, /* motorola */
IMAGE_FILETYPE_JPC,
IMAGE_FILETYPE_JP2,
IMAGE_FILETYPE_JPX,
IMAGE_FILETYPE_JB2,
IMAGE_FILETYPE_SWC,
IMAGE_FILETYPE_IFF,
IMAGE_FILETYPE_WBMP,
/* IMAGE_FILETYPE_JPEG2000 is a userland alias for IMAGE_FILETYPE_JPC */
IMAGE_FILETYPE_XBM,
IMAGE_FILETYPE_ICO
} image_filetype;
// PHP extension STANDARD: image.c
/* file type markers */
static const char php_sig_gif[3] = {'G', 'I', 'F'};
static const char php_sig_psd[4] = {'8', 'B', 'P', 'S'};
static const char php_sig_bmp[2] = {'B', 'M'};
static const char php_sig_swf[3] = {'F', 'W', 'S'};
static const char php_sig_swc[3] = {'C', 'W', 'S'};
static const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff};
static const char php_sig_png[8] =
{(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
static const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00};
static const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A};
static const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff};
static const char php_sig_jp2[12] =
{(char)0x00, (char)0x00, (char)0x00, (char)0x0c,
(char)0x6a, (char)0x50, (char)0x20, (char)0x20,
(char)0x0d, (char)0x0a, (char)0x87, (char)0x0a};
static const char php_sig_iff[4] = {'F','O','R','M'};
static struct gfxinfo *php_handle_gif(CObjRef stream) {
struct gfxinfo *result = NULL;
String dim;
const unsigned char *s;
if (f_fseek(stream, 3, SEEK_CUR)) return NULL;
dim = f_fread(stream, 5);
if (dim.length() != 5) return NULL;
s = (unsigned char *)dim.c_str();
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, (sizeof(struct gfxinfo)), NULL);
result->width = (unsigned int)s[0] | (((unsigned int)s[1])<<8);
result->height = (unsigned int)s[2] | (((unsigned int)s[3])<<8);
result->bits = s[4]&0x80 ? ((((unsigned int)s[4])&0x07) + 1) : 0;
result->channels = 3; /* allways */
return result;
}
static struct gfxinfo *php_handle_psd (CObjRef stream) {
struct gfxinfo *result = NULL;
String dim;
const unsigned char *s;
if (f_fseek(stream, 11, SEEK_CUR)) return NULL;
dim = f_fread(stream, 8);
if (dim.length() != 8) return NULL;
s = (unsigned char *)dim.c_str();
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, (sizeof(struct gfxinfo)), NULL);
result->height = (((unsigned int)s[0]) << 24) +
(((unsigned int)s[1]) << 16) +
(((unsigned int)s[2]) << 8) +
((unsigned int)s[3]);
result->width = (((unsigned int)s[4]) << 24) +
(((unsigned int)s[5]) << 16) +
(((unsigned int)s[6]) << 8) +
((unsigned int)s[7]);
return result;
}
static struct gfxinfo *php_handle_bmp (CObjRef stream) {
struct gfxinfo *result = NULL;
String dim;
const unsigned char *s;
int size;
if (f_fseek(stream, 11, SEEK_CUR)) return NULL;
dim = f_fread(stream, 16);
if (dim.length() != 16) return NULL;
s = (unsigned char *)dim.c_str();
size = (((unsigned int)s[3]) << 24) +
(((unsigned int)s[2]) << 16) +
(((unsigned int)s[1]) << 8) +
((unsigned int)s[0]);
if (size == 12) {
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof(struct gfxinfo), NULL);
result->width = (((unsigned int)s[5]) << 8) + ((unsigned int)s[4]);
result->height = (((unsigned int)s[7]) << 8) + ((unsigned int)s[6]);
result->bits = ((unsigned int)s[11]);
} else if (size > 12 && (size <= 64 || size == 108)) {
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof(struct gfxinfo), NULL);
result->width = (((unsigned int)s[7]) << 24) +
(((unsigned int)s[6]) << 16) +
(((unsigned int)s[5]) << 8) +
((unsigned int)s[4]);
result->height = (((unsigned int)s[11]) << 24) +
(((unsigned int)s[10]) << 16) +
(((unsigned int)s[9]) << 8) +
((unsigned int)s[8]);
result->bits = (((unsigned int)s[15]) << 8) +
((unsigned int)s[14]);
} else {
return NULL;
}
return result;
}
static unsigned long int php_swf_get_bits(unsigned char* buffer,
unsigned int pos,
unsigned int count) {
unsigned int loop;
unsigned long int result = 0;
for (loop = pos; loop < pos + count; loop++)
{
result = result +
((((buffer[loop / 8]) >> (7 - (loop % 8))) & 0x01) <<
(count - (loop - pos) - 1));
}
return result;
}
#if HAVE_ZLIB && !defined(COMPILE_DL_ZLIB)
static struct gfxinfo *php_handle_swc(CObjRef stream) {
struct gfxinfo *result = NULL;
long bits;
String a;
unsigned long len=64, szlength;
int factor=1,maxfactor=16;
int slength, status=0;
unsigned char *b, *buf=NULL;
String bufz;
String tmp;
b = (unsigned char *)IM_CALLOC(1, len + 1);
CHECK_ALLOC_R(b, (len + 1), NULL);
if (f_fseek(stream, 5, SEEK_CUR)) {
IM_FREE(b);
return NULL;
}
a = toString(f_fread(stream, 64));
if (a.length() != 64) {
IM_FREE(b);
return NULL;
}
if (uncompress((Bytef*)b, &len, (const Bytef*)a.c_str(), 64) != Z_OK) {
/* failed to decompress the file, will try reading the rest of the file */
if (f_fseek(stream, 8, SEEK_SET)) {
IM_FREE(b);
return NULL;
}
while (!(tmp = f_fread(stream, 8192)).empty()) {
bufz += tmp;
}
slength = bufz.length();
/*
* zlib::uncompress() wants to know the output data length
* if none was given as a parameter
* we try from input length * 2 up to input length * 2^8
* doubling it whenever it wasn't big enough
* that should be eneugh for all real life cases
*/
do {
szlength=slength*(1<<factor++);
buf = (unsigned char *) IM_REALLOC(buf,szlength);
if (!buf) IM_FREE(b);
CHECK_ALLOC_R(buf, szlength, NULL);
status = uncompress((Bytef*)buf, &szlength,
(const Bytef*)bufz.c_str(), slength);
} while ((status==Z_BUF_ERROR)&&(factor<maxfactor));
if (status == Z_OK) {
memcpy(b, buf, len);
}
if (buf) {
IM_FREE(buf);
}
}
if (!status) {
result = (struct gfxinfo *)IM_CALLOC(1, sizeof (struct gfxinfo));
if (!result) IM_FREE(b);
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
bits = php_swf_get_bits (b, 0, 5);
result->width = (php_swf_get_bits (b, 5 + bits, bits) -
php_swf_get_bits (b, 5, bits)) / 20;
result->height = (php_swf_get_bits (b, 5 + (3 * bits), bits) -
php_swf_get_bits (b, 5 + (2 * bits), bits)) / 20;
} else {
result = NULL;
}
IM_FREE(b);
return result;
}
#endif
static struct gfxinfo *php_handle_swf(CObjRef stream) {
struct gfxinfo *result = NULL;
long bits;
unsigned char *a;
if (f_fseek(stream, 5, SEEK_CUR)) return NULL;
String str = toString(f_fread(stream, 32));
if (str.length() != 32) return NULL;
a = (unsigned char *)str.c_str();
result = (struct gfxinfo *)IM_CALLOC(1, sizeof (struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
bits = php_swf_get_bits (a, 0, 5);
result->width = (php_swf_get_bits (a, 5 + bits, bits) -
php_swf_get_bits (a, 5, bits)) / 20;
result->height = (php_swf_get_bits (a, 5 + (3 * bits), bits) -
php_swf_get_bits (a, 5 + (2 * bits), bits)) / 20;
result->bits = 0;
result->channels = 0;
return result;
}
static struct gfxinfo *php_handle_png(CObjRef stream) {
struct gfxinfo *result = NULL;
String dim;
const unsigned char *s;
/* Width: 4 bytes
* Height: 4 bytes
* Bit depth: 1 byte
* Color type: 1 byte
* Compression method: 1 byte
* Filter method: 1 byte
* Interlace method: 1 byte
*/
if (f_fseek(stream, 8, SEEK_CUR)) return NULL;
dim = f_fread(stream, 9);
if (dim.length() < 9) return NULL;
s = (unsigned char *)dim.c_str();
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
result->width = (((unsigned int)s[0]) << 24) +
(((unsigned int)s[1]) << 16) +
(((unsigned int)s[2]) << 8) +
((unsigned int)s[3]);
result->height = (((unsigned int)s[4]) << 24) +
(((unsigned int)s[5]) << 16) +
(((unsigned int)s[6]) << 8) +
((unsigned int)s[7]);
result->bits = (unsigned int)s[8];
return result;
}
/* routines to handle JPEG data */
/* some defines for the different JPEG block types */
#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
#define M_EOI 0xD9 /* End Of Image (end of datastream) */
#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */
#define M_APP0 0xe0
#define M_APP1 0xe1
#define M_APP2 0xe2
#define M_APP3 0xe3
#define M_APP4 0xe4
#define M_APP5 0xe5
#define M_APP6 0xe6
#define M_APP7 0xe7
#define M_APP8 0xe8
#define M_APP9 0xe9
#define M_APP10 0xea
#define M_APP11 0xeb
#define M_APP12 0xec
#define M_APP13 0xed
#define M_APP14 0xee
#define M_APP15 0xef
#define M_COM 0xFE /* COMment */
#define M_PSEUDO 0xFFD8 /* pseudo marker for start of image(byte 0) */
#define M_EXIF 0xE1 /* Exif Attribute Information */
static unsigned short php_read2(CObjRef stream) {
unsigned char *a;
String str = toString(f_fread(stream, 2));
/* just return 0 if we hit the end-of-file */
if (str.length() != 2) return 0;
a = (unsigned char *)str.c_str();
return (((unsigned short)a[0]) << 8) + ((unsigned short)a[1]);
}
static unsigned int php_next_marker(CObjRef stream, int last_marker,
int comment_correction, int ff_read) {
int a=0, marker;
// get marker byte, swallowing possible padding
if (last_marker==M_COM && comment_correction) {
// some software does not count the length bytes of COM section
// one company doing so is very much envolved in JPEG... so we accept too
// by the way: some of those companies changed their code now...
comment_correction = 2;
} else {
last_marker = 0;
comment_correction = 0;
}
if (ff_read) {
a = 1; /* already read 0xff in filetype detection */
}
do {
File *file = stream.getTyped<File>();
if ((marker = file->getc()) == EOF)
{
return M_EOI;/* we hit EOF */
}
if (last_marker==M_COM && comment_correction>0)
{
if (marker != 0xFF)
{
marker = 0xff;
comment_correction--;
} else {
last_marker = M_PSEUDO; /* stop skipping non 0xff for M_COM */
}
}
if (++a > 25)
{
/* who knows the maxim amount of 0xff? though 7 */
/* but found other implementations */
return M_EOI;
}
} while (marker == 0xff);
if (a < 2)
{
return M_EOI; /* at least one 0xff is needed before marker code */
}
if ( last_marker==M_COM && comment_correction)
{
return M_EOI; /* ah illegal: char after COM section not 0xFF */
}
return (unsigned int)marker;
}
static int php_skip_variable(CObjRef stream) {
off_t length = (unsigned int)php_read2(stream);
if (length < 2) {
return 0;
}
length = length - 2;
f_fseek(stream, (long)length, SEEK_CUR);
return 1;
}
static int php_read_APP(CObjRef stream, unsigned int marker, Variant &info) {
unsigned short length;
Variant buffer;
unsigned char markername[16];
length = php_read2(stream);
if (length < 2) {
return 0;
}
length -= 2; /* length includes itself */
buffer = f_fread(stream, (long)length);
if (is_empty_string(buffer)) {
return 0;
}
snprintf((char*)markername, sizeof(markername), "APP%d", marker - M_APP0);
if (!(info.is(KindOfArray) &&
info.toArray().exists(String((const char *)markername)))) {
/* XXX we onyl catch the 1st tag of it's kind! */
info.set(String((char*)markername, CopyString), buffer);
}
return 1;
}
static struct gfxinfo *php_handle_jpeg(CObjRef stream, Variant &info) {
struct gfxinfo *result = NULL;
unsigned int marker = M_PSEUDO;
unsigned short length, ff_read=1;
for (;;) {
marker = php_next_marker(stream, marker, 1, ff_read);
ff_read = 0;
switch (marker) {
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:
if (result == NULL) {
File *file = stream.getTyped<File>();
/* handle SOFn block */
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
length = php_read2(stream);
result->bits = file->getc();
result->height = php_read2(stream);
result->width = php_read2(stream);
result->channels = file->getc();
if (!info.isReferenced() || length < 8) {
/* if we don't want an extanded info -> return */
return result;
}
if (f_fseek(stream, length - 8, SEEK_CUR)) {
/* file error after info */
return result;
}
} else {
if (!php_skip_variable(stream)) {
return result;
}
}
break;
case M_APP0:
case M_APP1:
case M_APP2:
case M_APP3:
case M_APP4:
case M_APP5:
case M_APP6:
case M_APP7:
case M_APP8:
case M_APP9:
case M_APP10:
case M_APP11:
case M_APP12:
case M_APP13:
case M_APP14:
case M_APP15:
if (info.isReferenced()) {
if (!php_read_APP(stream, marker, info)) {
/* read all the app markes... */
return result;
}
} else {
if (!php_skip_variable(stream)) {
return result;
}
}
break;
case M_SOS:
case M_EOI:
/* we're about to hit image data, or are at EOF. stop processing. */
return result;
default:
if (!php_skip_variable(stream)) {
/* anything else isn't interesting */
return result;
}
break;
}
}
return result; /* perhaps image broken -> no info but size */
}
static unsigned short php_read4(CObjRef stream) {
unsigned char *a;
String str = toString(f_fread(stream, 4));
/* just return 0 if we hit the end-of-file */
if (str.length() != 4) return 0;
a = (unsigned char *)str.c_str();
return (((unsigned int)a[0]) << 24)
+ (((unsigned int)a[1]) << 16)
+ (((unsigned int)a[2]) << 8)
+ (((unsigned int)a[3]));
}
/* JPEG 2000 Marker Codes */
#define JPEG2000_MARKER_PREFIX 0xFF /* All marker codes start with this */
#define JPEG2000_MARKER_SOC 0x4F /* Start of Codestream */
#define JPEG2000_MARKER_SOT 0x90 /* Start of Tile part */
#define JPEG2000_MARKER_SOD 0x93 /* Start of Data */
#define JPEG2000_MARKER_EOC 0xD9 /* End of Codestream */
#define JPEG2000_MARKER_SIZ 0x51 /* Image and tile size */
#define JPEG2000_MARKER_COD 0x52 /* Coding style default */
#define JPEG2000_MARKER_COC 0x53 /* Coding style component */
#define JPEG2000_MARKER_RGN 0x5E /* Region of interest */
#define JPEG2000_MARKER_QCD 0x5C /* Quantization default */
#define JPEG2000_MARKER_QCC 0x5D /* Quantization component */
#define JPEG2000_MARKER_POC 0x5F /* Progression order change */
#define JPEG2000_MARKER_TLM 0x55 /* Tile-part lengths */
#define JPEG2000_MARKER_PLM 0x57 /* Packet length, main header */
#define JPEG2000_MARKER_PLT 0x58 /* Packet length, tile-part header */
#define JPEG2000_MARKER_PPM 0x60 /* Packed packet headers, main header */
#define JPEG2000_MARKER_PPT 0x61 /* Packed packet headers, tile part header */
#define JPEG2000_MARKER_SOP 0x91 /* Start of packet */
#define JPEG2000_MARKER_EPH 0x92 /* End of packet header */
#define JPEG2000_MARKER_CRG 0x63 /* Component registration */
#define JPEG2000_MARKER_COM 0x64 /* Comment */
/* Main loop to parse JPEG2000 raw codestream structure */
static struct gfxinfo *php_handle_jpc(CObjRef stream) {
struct gfxinfo *result = NULL;
int highest_bit_depth, bit_depth;
unsigned char first_marker_id;
unsigned int i;
/* JPEG 2000 components can be vastly different from one another.
Each component can be sampled at a different resolution, use
a different colour space, have a seperate colour depth, and
be compressed totally differently! This makes giving a single
"bit depth" answer somewhat problematic. For this implementation
we'll use the highest depth encountered. */
/* Get the single byte that remains after the file type indentification */
File *file = stream.getTyped<File>();
first_marker_id = file->getc();
/* Ensure that this marker is SIZ (as is mandated by the standard) */
if (first_marker_id != JPEG2000_MARKER_SIZ) {
raise_warning("JPEG2000 codestream corrupt(Expected SIZ marker "
"not found after SOC)");
return NULL;
}
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
php_read2(stream); /* Lsiz */
php_read2(stream); /* Rsiz */
result->width = php_read4(stream); /* Xsiz */
result->height = php_read4(stream); /* Ysiz */
#if MBO_0
php_read4(stream); /* XOsiz */
php_read4(stream); /* YOsiz */
php_read4(stream); /* XTsiz */
php_read4(stream); /* YTsiz */
php_read4(stream); /* XTOsiz */
php_read4(stream); /* YTOsiz */
#else
if (f_fseek(stream, 24, SEEK_CUR)) {
IM_FREE(result);
return NULL;
}
#endif
result->channels = php_read2(stream); /* Csiz */
if (result->channels > 256) {
IM_FREE(result);
return NULL;
}
/* Collect bit depth info */
highest_bit_depth = bit_depth = 0;
for (i = 0; i < result->channels; i++) {
bit_depth = file->getc(); /* Ssiz[i] */
bit_depth++;
if (bit_depth > highest_bit_depth) {
highest_bit_depth = bit_depth;
}
file->getc(); /* XRsiz[i] */
file->getc(); /* YRsiz[i] */
}
result->bits = highest_bit_depth;
return result;
}
/* main loop to parse JPEG 2000 JP2 wrapper format structure */
static struct gfxinfo *php_handle_jp2(CObjRef stream) {
struct gfxinfo *result = NULL;
unsigned int box_length;
unsigned int box_type;
char jp2c_box_id[] = {(char)0x6a, (char)0x70, (char)0x32, (char)0x63};
/* JP2 is a wrapper format for JPEG 2000. Data is contained within "boxes".
Boxes themselves can be contained within "super-boxes". Super-Boxes can
contain super-boxes which provides us with a hierarchical storage system.
It is valid for a JP2 file to contain multiple individual codestreams.
We'll just look for the first codestream at the root of the box structure
and handle that.
*/
for (;;)
{
box_length = php_read4(stream); /* LBox */
/* TBox */
String str = toString(f_fread(stream, sizeof(box_type)));
if (str.length() != sizeof(box_type)) {
/* Use this as a general "out of stream" error */
break;
}
memcpy(&box_type, str.c_str(), sizeof(box_type));
if (box_length == 1) {
/* We won't handle XLBoxes */
return NULL;
}
if (!memcmp(&box_type, jp2c_box_id, 4))
{
/* Skip the first 3 bytes to emulate the file type examination */
f_fseek(stream, 3, SEEK_CUR);
result = php_handle_jpc(stream);
break;
}
/* Stop if this was the last box */
if ((int)box_length <= 0) {
break;
}
/* Skip over LBox (Which includes both TBox and LBox itself */
if (f_fseek(stream, box_length - 8, SEEK_CUR)) {
break;
}
}
if (result == NULL) {
raise_warning("JP2 file has no codestreams at root level");
}
return result;
}
/* tiff constants */
static const int php_tiff_bytes_per_format[] =
{0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1};
static int get_php_tiff_bytes_per_format(int format) {
int size = sizeof(php_tiff_bytes_per_format)/sizeof(int);
if (format >= size) {
raise_warning("Invalid format %d", format);
format = 0;
}
return php_tiff_bytes_per_format[format];
}
/* uncompressed only */
#define TAG_IMAGEWIDTH 0x0100
#define TAG_IMAGEHEIGHT 0x0101
/* compressed images only */
#define TAG_COMP_IMAGEWIDTH 0xA002
#define TAG_COMP_IMAGEHEIGHT 0xA003
#define TAG_FMT_BYTE 1
#define TAG_FMT_STRING 2
#define TAG_FMT_USHORT 3
#define TAG_FMT_ULONG 4
#define TAG_FMT_URATIONAL 5
#define TAG_FMT_SBYTE 6
#define TAG_FMT_UNDEFINED 7
#define TAG_FMT_SSHORT 8
#define TAG_FMT_SLONG 9
#define TAG_FMT_SRATIONAL 10
#define TAG_FMT_SINGLE 11
#define TAG_FMT_DOUBLE 12
static int php_vspprintf(char **pbuf, size_t max_len,
const char *format, ...) {
va_list arglist;
char *buf;
va_start(arglist, format);
int len = vspprintf_ap(&buf, max_len, format, arglist);
if (buf) {
#ifdef IM_MEMORY_CHECK
*pbuf = php_strndup_impl(buf, len, __LINE__);
#else
*pbuf = php_strndup_impl(buf, len);
#endif
free(buf);
}
va_end(arglist);
return len;
}
static int php_vspprintf_ap(char **pbuf, size_t max_len,
const char *format, va_list ap) {
char *buf;
int len = vspprintf_ap(&buf, max_len, format, ap);
if (buf) {
#ifdef IM_MEMORY_CHECK
*pbuf = php_strndup_impl(buf, len, __LINE__);
#else
*pbuf = php_strndup_impl(buf, len);
#endif
free(buf);
}
return len;
}
/* Convert a 16 bit unsigned value from file's native byte order */
static int php_ifd_get16u(void *Short, int motorola_intel) {
if (motorola_intel) {
return (((unsigned char *)Short)[0] << 8) | ((unsigned char *)Short)[1];
} else {
return (((unsigned char *)Short)[1] << 8) | ((unsigned char *)Short)[0];
}
}
/* Convert a 16 bit signed value from file's native byte order */
static signed short php_ifd_get16s(void *Short, int motorola_intel) {
return (signed short)php_ifd_get16u(Short, motorola_intel);
}
/* Convert a 32 bit signed value from file's native byte order */
static int php_ifd_get32s(void *Long, int motorola_intel) {
if (motorola_intel) {
return ((( char *)Long)[0] << 24) |
(((unsigned char *)Long)[1] << 16) |
(((unsigned char *)Long)[2] << 8) |
(((unsigned char *)Long)[3] << 0);
} else {
return ((( char *)Long)[3] << 24) |
(((unsigned char *)Long)[2] << 16) |
(((unsigned char *)Long)[1] << 8) |
(((unsigned char *)Long)[0] << 0);
}
}
/* Convert a 32 bit unsigned value from file's native byte order */
static unsigned php_ifd_get32u(void *Long, int motorola_intel) {
return (unsigned)php_ifd_get32s(Long, motorola_intel) & 0xffffffff;
}
/* main loop to parse TIFF structure */
static struct gfxinfo *php_handle_tiff(CObjRef stream, int motorola_intel) {
struct gfxinfo *result = NULL;
int i, num_entries;
unsigned char *dir_entry;
size_t dir_size, entry_value, width=0, height=0, ifd_addr;
int entry_tag , entry_type;
String ifd_data;
String ifd_data2;
String ifd_ptr;
ifd_ptr = stream.getTyped<File>()->read(4);
if (ifd_ptr.length() != 4) return NULL;
ifd_addr = php_ifd_get32u((void*)ifd_ptr.c_str(), motorola_intel);
if (f_fseek(stream, ifd_addr-8, SEEK_CUR)) return NULL;
ifd_data = f_fread(stream, 2);
if (ifd_data.length() != 2) return NULL;
num_entries = php_ifd_get16u((void*)ifd_data.c_str(), motorola_intel);
dir_size = 2/*num dir entries*/ +12/*length of entry*/*
num_entries +
4/* offset to next ifd (points to thumbnail or NULL)*/;
ifd_data2 = f_fread(stream, dir_size-2);
if ((size_t)ifd_data2.length() != dir_size-2) return NULL;
ifd_data += ifd_data2;
/* now we have the directory we can look how long it should be */
for(i=0;i<num_entries;i++) {
dir_entry = (unsigned char*)ifd_data.c_str()+2+i*12;
entry_tag = php_ifd_get16u(dir_entry+0, motorola_intel);
entry_type = php_ifd_get16u(dir_entry+2, motorola_intel);
switch(entry_type) {
case TAG_FMT_BYTE:
case TAG_FMT_SBYTE:
entry_value = (size_t)(dir_entry[8]);
break;
case TAG_FMT_USHORT:
entry_value = php_ifd_get16u(dir_entry+8, motorola_intel);
break;
case TAG_FMT_SSHORT:
entry_value = php_ifd_get16s(dir_entry+8, motorola_intel);
break;
case TAG_FMT_ULONG:
entry_value = php_ifd_get32u(dir_entry+8, motorola_intel);
break;
case TAG_FMT_SLONG:
entry_value = php_ifd_get32s(dir_entry+8, motorola_intel);
break;
default:
continue;
}
switch(entry_tag) {
case TAG_IMAGEWIDTH:
case TAG_COMP_IMAGEWIDTH:
width = entry_value;
break;
case TAG_IMAGEHEIGHT:
case TAG_COMP_IMAGEHEIGHT:
height = entry_value;
break;
}
}
if ( width && height) {
/* not the same when in for-loop */
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
result->height = height;
result->width = width;
result->bits = 0;
result->channels = 0;
return result;
}
return NULL;
}
static struct gfxinfo *php_handle_iff(CObjRef stream) {
struct gfxinfo * result;
String str;
char *a;
int chunkId;
int size;
short width, height, bits;
str = f_fread(stream, 8);
if (str.length() != 8) return NULL;
a = (char *)str.c_str();
if (strncmp(a+4, "ILBM", 4) && strncmp(a+4, "PBM ", 4)) {
return NULL;
}
/* loop chunks to find BMHD chunk */
do {
str = f_fread(stream, 8);
if (str.length() != 8) return NULL;
a = (char *)str.c_str();
chunkId = php_ifd_get32s(a+0, 1);
size = php_ifd_get32s(a+4, 1);
if (size < 0) return NULL;
if ((size & 1) == 1) {
size++;
}
if (chunkId == 0x424d4844) { /* BMHD chunk */
if (size < 9) return NULL;
str = f_fread(stream, 9);
if (str.length() != 9) return NULL;
a = (char *)str.c_str();
width = php_ifd_get16s(a+0, 1);
height = php_ifd_get16s(a+2, 1);
bits = a[8] & 0xff;
if (width > 0 && height > 0 && bits > 0 && bits < 33) {
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, sizeof (struct gfxinfo), NULL);
result->width = width;
result->height = height;
result->bits = bits;
result->channels = 0;
return result;
}
} else {
if (f_fseek(stream, size, SEEK_CUR)) return NULL;
}
} while (1);
}
/*
* int WBMP file format type
* byte Header Type
* byte Extended Header
* byte Header Data (type 00 = multibyte)
* byte Header Data (type 11 = name/pairs)
* int Number of columns
* int Number of rows
*/
static int php_get_wbmp(CObjRef stream, struct gfxinfo **result, int check) {
int i, width = 0, height = 0;
if (!f_rewind(stream)) {
return 0;
}
File *file = stream.getTyped<File>();
/* get type */
if (file->getc() != 0) {
return 0;
}
/* skip header */
do {
i = file->getc();
if (i < 0) {
return 0;
}
} while (i & 0x80);
/* get width */
do {
i = file->getc();
if (i < 0) {
return 0;
}
width = (width << 7) | (i & 0x7f);
} while (i & 0x80);
/* get height */
do {
i = file->getc();
if (i < 0) {
return 0;
}
height = (height << 7) | (i & 0x7f);
} while (i & 0x80);
// maximum valid sizes for wbmp (although 127x127 may be a
// more accurate one)
if (!height || !width || height > 2048 || width > 2048) {
return 0;
}
if (!check) {
(*result)->width = width;
(*result)->height = height;
}
return IMAGE_FILETYPE_WBMP;
}
static struct gfxinfo *php_handle_wbmp(CObjRef stream) {
struct gfxinfo *result =
(struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(result, (sizeof(struct gfxinfo)), NULL);
if (!php_get_wbmp(stream, &result, 0)) {
IM_FREE(result);
return NULL;
}
return result;
}
static int php_get_xbm(CObjRef stream, struct gfxinfo **result) {
String fline;
char *iname;
char *type;
int value;
unsigned int width = 0, height = 0;
if (result) {
*result = NULL;
}
if (!f_rewind(stream)) {
return 0;
}
while (!(fline=f_fgets(stream, 0)).empty()) {
iname = (char *)IM_MALLOC(fline.size() + 1);
CHECK_ALLOC_R(iname, (fline.size() + 1), 0);
if (sscanf(fline.c_str(), "#define %s %d", iname, &value) == 2) {
if (!(type = strrchr(iname, '_'))) {
type = iname;
} else {
type++;
}
if (!strcmp("width", type)) {
width = (unsigned int)value;
if (height) {
IM_FREE(iname);
break;
}
}
if (!strcmp("height", type)) {
height = (unsigned int)value;
if (width) {
IM_FREE(iname);
break;
}
}
}
IM_FREE(iname);
}
if (width && height) {
if (result) {
*result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
CHECK_ALLOC_R(*result, sizeof(struct gfxinfo), 0);
(*result)->width = width;
(*result)->height = height;
}
return IMAGE_FILETYPE_XBM;
}
return 0;
}
static struct gfxinfo *php_handle_xbm(CObjRef stream) {
struct gfxinfo *result;
php_get_xbm(stream, &result);
return result;
}
/* Convert internal image_type to mime type */
static char *php_image_type_to_mime_type(int image_type) {
switch( image_type) {
case IMAGE_FILETYPE_GIF:
return "image/gif";
case IMAGE_FILETYPE_JPEG:
return "image/jpeg";
case IMAGE_FILETYPE_PNG:
return "image/png";
case IMAGE_FILETYPE_SWF:
case IMAGE_FILETYPE_SWC:
return "application/x-shockwave-flash";
case IMAGE_FILETYPE_PSD:
return "image/psd";
case IMAGE_FILETYPE_BMP:
return "image/bmp";
case IMAGE_FILETYPE_TIFF_II:
case IMAGE_FILETYPE_TIFF_MM:
return "image/tiff";
case IMAGE_FILETYPE_IFF:
return "image/iff";
case IMAGE_FILETYPE_WBMP:
return "image/vnd.wap.wbmp";
case IMAGE_FILETYPE_JPC:
return "application/octet-stream";
case IMAGE_FILETYPE_JP2:
return "image/jp2";
case IMAGE_FILETYPE_XBM:
return "image/xbm";
default:
case IMAGE_FILETYPE_UNKNOWN:
return "application/octet-stream"; /* suppose binary format */
}
}
/* detect filetype from first bytes */
static int php_getimagetype(CObjRef stream) {
File *file = stream.getTyped<File>();
String fileType;
String data;
fileType = file->read(3);
if (fileType.length() != 3) {
raise_notice("Read error!");
return IMAGE_FILETYPE_UNKNOWN;
}
/* BYTES READ: 3 */
if (!memcmp(fileType.c_str(), php_sig_gif, 3)) {
return IMAGE_FILETYPE_GIF;
} else if (!memcmp(fileType.c_str(), php_sig_jpg, 3)) {
return IMAGE_FILETYPE_JPEG;
} else if (!memcmp(fileType.c_str(), php_sig_png, 3)) {
data = file->read(5);
if (data.length() != 5) {
raise_notice("Read error!");
return IMAGE_FILETYPE_UNKNOWN;
}
if (!memcmp((fileType + data).c_str(), php_sig_png, 8)) {
return IMAGE_FILETYPE_PNG;
} else {
raise_warning("PNG file corrupted by ASCII conversion");
return IMAGE_FILETYPE_UNKNOWN;
}
} else if (!memcmp(fileType.c_str(), php_sig_swf, 3)) {
return IMAGE_FILETYPE_SWF;
} else if (!memcmp(fileType.c_str(), php_sig_swc, 3)) {
return IMAGE_FILETYPE_SWC;
} else if (!memcmp(fileType.c_str(), php_sig_psd, 3)) {
return IMAGE_FILETYPE_PSD;
} else if (!memcmp(fileType.c_str(), php_sig_bmp, 2)) {
return IMAGE_FILETYPE_BMP;
} else if (!memcmp(fileType.c_str(), php_sig_jpc, 3)) {
return IMAGE_FILETYPE_JPC;
}
data = file->read(1);
if (data.length() != 1) {
raise_notice("Read error!");
return IMAGE_FILETYPE_UNKNOWN;
}
/* BYTES READ: 4 */
fileType += data;
if (!memcmp(fileType.c_str(), php_sig_tif_ii, 4)) {
return IMAGE_FILETYPE_TIFF_II;
} else if (!memcmp(fileType.c_str(), php_sig_tif_mm, 4)) {
return IMAGE_FILETYPE_TIFF_MM;
}
if (!memcmp(fileType.c_str(), php_sig_iff, 4)) {
return IMAGE_FILETYPE_IFF;
}
data = file->read(8);
if (data.length() != 8) {
raise_notice("Read error!");
return IMAGE_FILETYPE_UNKNOWN;
}
/* BYTES READ: 12 */
fileType += data;
if (!memcmp(fileType.c_str(), php_sig_jp2, 12)) {
return IMAGE_FILETYPE_JP2;
}
/* AFTER ALL ABOVE FAILED */
if (php_get_wbmp(stream, NULL, 1)) {
return IMAGE_FILETYPE_WBMP;
}
if (php_get_xbm(stream, NULL)) {
return IMAGE_FILETYPE_XBM;
}
return IMAGE_FILETYPE_UNKNOWN;
}
String f_image_type_to_mime_type(int imagetype) {
switch( imagetype) {
case IMAGE_FILETYPE_GIF:
return "image/gif";
case IMAGE_FILETYPE_JPEG:
return "image/jpeg";
case IMAGE_FILETYPE_PNG:
return "image/png";
case IMAGE_FILETYPE_SWF:
case IMAGE_FILETYPE_SWC:
return "application/x-shockwave-flash";
case IMAGE_FILETYPE_PSD:
return "image/psd";
case IMAGE_FILETYPE_BMP:
return "image/x-ms-bmp";
case IMAGE_FILETYPE_TIFF_II:
case IMAGE_FILETYPE_TIFF_MM:
return "image/tiff";
case IMAGE_FILETYPE_IFF:
return "image/iff";
case IMAGE_FILETYPE_WBMP:
return "image/vnd.wap.wbmp";
case IMAGE_FILETYPE_JPC:
return "application/octet-stream";
case IMAGE_FILETYPE_JP2:
return "image/jp2";
case IMAGE_FILETYPE_XBM:
return "image/xbm";
case IMAGE_FILETYPE_ICO:
return "image/vnd.microsoft.icon";
default:
case IMAGE_FILETYPE_UNKNOWN:
return "application/octet-stream"; /* suppose binary format */
}
}
String f_image_type_to_extension(int imagetype,
bool include_dot /* = true */) {
String ret;
switch (imagetype) {
case IMAGE_FILETYPE_GIF:
return include_dot ? String(".gif") : String("gif");
case IMAGE_FILETYPE_JPEG:
return include_dot ? String(".jpeg") : String("jpeg");
case IMAGE_FILETYPE_PNG:
return include_dot ? String(".png") : String("png");
case IMAGE_FILETYPE_SWF:
case IMAGE_FILETYPE_SWC:
return include_dot ? String(".swf") : String("swf");
case IMAGE_FILETYPE_PSD:
return include_dot ? String(".psd") : String("psd");
case IMAGE_FILETYPE_BMP:
case IMAGE_FILETYPE_WBMP:
return include_dot ? String(".bmp") : String("bmp");
case IMAGE_FILETYPE_TIFF_II:
case IMAGE_FILETYPE_TIFF_MM:
return include_dot ? String(".tiff") : String("tiff");
case IMAGE_FILETYPE_IFF:
return include_dot ? String(".iff") : String("iff");
case IMAGE_FILETYPE_JPC:
return include_dot ? String(".jpc") : String("jpc");
case IMAGE_FILETYPE_JP2:
return include_dot ? String(".jp2") : String("jp2");
case IMAGE_FILETYPE_JPX:
return include_dot ? String(".jpx") : String("jpx");
case IMAGE_FILETYPE_JB2:
return include_dot ? String(".jb2") : String("jb2");
case IMAGE_FILETYPE_XBM:
return include_dot ? String(".xbm") : String("xbm");
default:
return ret;
}
}
static const StaticString s_bits("bits");
static const StaticString s_channels("channels");
static const StaticString s_mime("mime");
Variant f_getimagesize(CStrRef filename, VRefParam imageinfo /* = null */) {
int itype = 0;
struct gfxinfo *result = NULL;
if (imageinfo.isReferenced()) {
imageinfo = uninit_null();
}
Variant stream = f_fopen(filename, "rb");
if (same(stream, false)) {
raise_warning("failed to open stream: %s", filename.c_str());
return false;
}
itype = php_getimagetype(stream);
switch( itype) {
case IMAGE_FILETYPE_GIF:
result = php_handle_gif(stream);
break;
case IMAGE_FILETYPE_JPEG:
result = php_handle_jpeg(stream, imageinfo);
break;
case IMAGE_FILETYPE_PNG:
result = php_handle_png(stream);
break;
case IMAGE_FILETYPE_SWF:
result = php_handle_swf(stream);
break;
case IMAGE_FILETYPE_SWC:
#if HAVE_ZLIB && !defined(COMPILE_DL_ZLIB)
result = php_handle_swc(stream);
#else
raise_notice("The image is a compressed SWF file, but you do not "
"have a static version of the zlib extension enabled");
#endif
break;
case IMAGE_FILETYPE_PSD:
result = php_handle_psd(stream);
break;
case IMAGE_FILETYPE_BMP:
result = php_handle_bmp(stream);
break;
case IMAGE_FILETYPE_TIFF_II:
result = php_handle_tiff(stream, 0);
break;
case IMAGE_FILETYPE_TIFF_MM:
result = php_handle_tiff(stream, 1);
break;
case IMAGE_FILETYPE_JPC:
result = php_handle_jpc(stream);
break;
case IMAGE_FILETYPE_JP2:
result = php_handle_jp2(stream);
break;
case IMAGE_FILETYPE_IFF:
result = php_handle_iff(stream);
break;
case IMAGE_FILETYPE_WBMP:
result = php_handle_wbmp(stream);
break;
case IMAGE_FILETYPE_XBM:
result = php_handle_xbm(stream);
break;
default:
case IMAGE_FILETYPE_UNKNOWN:
break;
}
f_fclose(stream);
if (result) {
ArrayInit ret(7);
ret.set(0, (int64_t)result->width);
ret.set(1, (int64_t)result->height);
ret.set(2, itype);
char *temp;
php_vspprintf(&temp, 0, "width=\"%d\" height=\"%d\"",
result->width, result->height);
ret.set(3, String(temp, CopyString));
if (temp) IM_FREE(temp);
if (result->bits != 0) {
ret.set(s_bits, (int64_t)result->bits);
}
if (result->channels != 0) {
ret.set(s_channels, (int64_t)result->channels);
}
ret.set(s_mime, (char*)php_image_type_to_mime_type(itype));
IM_FREE(result);
return ret.create();
} else {
return false;
}
}
// PHP extension gd.c
#define HAVE_GDIMAGECREATEFROMPNG 1
#if HAVE_LIBTTF|HAVE_LIBFREETYPE
#define ENABLE_GD_TTF
#endif
#define PHP_GDIMG_TYPE_GIF 1
#define PHP_GDIMG_TYPE_PNG 2
#define PHP_GDIMG_TYPE_JPG 3
#define PHP_GDIMG_TYPE_WBM 4
#define PHP_GDIMG_TYPE_XBM 5
#define PHP_GDIMG_TYPE_XPM 6
#define PHP_GDIMG_CONVERT_WBM 7
#define PHP_GDIMG_TYPE_GD 8
#define PHP_GDIMG_TYPE_GD2 9
#define PHP_GDIMG_TYPE_GD2PART 10
#if HAVE_GD_BUNDLED
#define PHP_GD_VERSION_STRING "bundled (2.0.34 compatible)"
#elif HAVE_LIBGD20
#define PHP_GD_VERSION_STRING "2.0 or higher"
#elif HAVE_GDIMAGECOLORRESOLVE
#define PHP_GD_VERSION_STRING "1.6.2 or higher"
#elif HAVE_LIBGD13
#define PHP_GD_VERSION_STRING "between 1.3 and 1.6.1"
#else
#define PHP_GD_VERSION_STRING "1.2"
#endif
#if HAVE_LIBGD15
/* it's >= 1.5, i.e. has IOCtx */
#define USE_GD_IOCTX 1
#else
#undef USE_GD_IOCTX
#endif
#ifdef USE_GD_IOCTX
#define CTX_PUTC(c,ctx) ctx->putC(ctx, c)
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
static Variant php_open_plain_file(CStrRef filename, const char *mode,
FILE **fpp) {
Variant stream = f_fopen(filename, mode);
if (same(stream, false)) {
return false;
}
PlainFile *plain_file = Object(stream).getTyped<PlainFile>(false, true);
FILE *fp = NULL;
if (!plain_file || !(fp = plain_file->getStream())) {
f_fclose(stream);
return false;
}
if (fpp) *fpp = fp;
return stream;
}
static int php_write(void *buf, uint size) {
g_context->write((const char *)buf, size);
return size;
}
static void _php_image_output_putc(struct gdIOCtx *ctx, int c) {
/* without the following downcast, the write will fail
* (i.e., will write a zero byte) for all
* big endian architectures:
*/
unsigned char ch = (unsigned char) c;
php_write(&ch, 1);
}
static int _php_image_output_putbuf(struct gdIOCtx *ctx, const void* buf,
int len) {
return php_write((void *)buf, len);
}
static void _php_image_output_ctxfree(struct gdIOCtx *ctx) {
if(ctx) {
IM_FREE(ctx);
}
}
static bool _php_image_output_ctx(CObjRef image, CStrRef filename,
int quality, int basefilter,
int image_type, char *tn,
void (*func_p)()) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
Variant stream;
FILE *fp = NULL;
int q = quality, i;
int f = basefilter;
gdIOCtx *ctx;
/* The third (quality) parameter for Wbmp stands for the threshold
when called from image2wbmp(). The third (quality) parameter for
Wbmp and Xbm stands for the foreground color index when called
from imagey<type>().
*/
if (!filename.empty()) {
stream = php_open_plain_file(filename, "wb", &fp);
if (same(stream, false)) {
raise_warning("Unable to open '%s' for writing", filename.c_str());
return false;
}
ctx = gdNewFileCtx(fp);
} else {
ctx = (gdIOCtx *)IM_MALLOC(sizeof(gdIOCtx));
CHECK_ALLOC_R(ctx, sizeof(gdIOCtx), false);
ctx->putC = _php_image_output_putc;
ctx->putBuf = _php_image_output_putbuf;
#if HAVE_LIBGD204
ctx->gd_free = _php_image_output_ctxfree;
#else
ctx->free = _php_image_output_ctxfree;
#endif
}
switch(image_type) {
case PHP_GDIMG_CONVERT_WBM:
if(q<0||q>255) {
raise_warning("Invalid threshold value '%d'. "
"It must be between 0 and 255", q);
}
case PHP_GDIMG_TYPE_JPG:
((void(*)(gdImagePtr, gdIOCtx *, int))(func_p))(im, ctx, q);
break;
case PHP_GDIMG_TYPE_PNG:
((void(*)(gdImagePtr, gdIOCtx *, int, int))(func_p))(im, ctx, q, f);
break;
case PHP_GDIMG_TYPE_XBM:
case PHP_GDIMG_TYPE_WBM:
if (q == -1) { // argc < 3
for(i=0; i < gdImageColorsTotal(im); i++) {
if(!gdImageRed(im, i) && !gdImageGreen(im, i) && !gdImageBlue(im, i)) break;
}
q = i;
}
if (image_type == PHP_GDIMG_TYPE_XBM) {
((void(*)(gdImagePtr, char *, int, gdIOCtx *))(func_p))
(im, (char*)filename.c_str(), q, ctx);
} else {
((void(*)(gdImagePtr, int, gdIOCtx *))(func_p))(im, q, ctx);
}
break;
default:
((void(*)(gdImagePtr, gdIOCtx *))(func_p))(im, ctx);
break;
}
#if HAVE_LIBGD204
ctx->gd_free(ctx);
#else
ctx->free(ctx);
#endif
if(fp) {
fflush(fp);
f_fclose(stream);
}
return true;
}
#else
#define gdImageCreateFromGdCtx NULL
#define gdImageCreateFromGd2Ctx NULL
#define gdImageCreateFromGd2partCtx NULL
#define gdImageCreateFromGifCtx NULL
#define gdImageCreateFromJpegCtx NULL
#define gdImageCreateFromPngCtx NULL
#define gdImageCreateFromWBMPCtx NULL
typedef FILE gdIOCtx;
#define CTX_PUTC(c, fp) fputc(c, fp)
#endif
#ifdef HAVE_GD_WBMP
/* It converts a gd Image to bw using a threshold value */
static void _php_image_bw_convert(gdImagePtr im_org, gdIOCtx *out,
int threshold) {
gdImagePtr im_dest;
int white, black;
int color, color_org, median;
int dest_height = gdImageSY(im_org);
int dest_width = gdImageSX(im_org);
int x, y;
im_dest = gdImageCreate(dest_width, dest_height);
if (im_dest == NULL) {
raise_warning("Unable to allocate temporary buffer");
return;
}
white = gdImageColorAllocate(im_dest, 255, 255, 255);
if (white == -1) {
raise_warning("Unable to allocate the colors for "
"the destination buffer");
return;
}
black = gdImageColorAllocate(im_dest, 0, 0, 0);
if (black == -1) {
raise_warning("Unable to allocate the colors for "
"the destination buffer");
return;
}
#if HAVE_LIBGD20
if (im_org->trueColor) {
gdImageTrueColorToPalette(im_org, 1, 256);
}
#endif
for (y = 0; y < dest_height; y++) {
for (x = 0; x < dest_width; x++) {
color_org = gdImageGetPixel(im_org, x, y);
median = (im_org->red[color_org] +
im_org->green[color_org] +
im_org->blue[color_org]) / 3;
if (median < threshold) {
color = black;
} else {
color = white;
}
gdImageSetPixel (im_dest, x, y, color);
}
}
#ifdef USE_GD_IOCTX
gdImageWBMPCtx (im_dest, black, out);
#else
gdImageWBMP (im_dest, black, out);
#endif
}
/*
* converts jpeg/png images to wbmp and resizes them as needed
*/
static bool _php_image_convert(CStrRef f_org, CStrRef f_dest,
int dest_height, int dest_width,
int threshold, int image_type) {
gdImagePtr im_org, im_dest, im_tmp;
Variant org_stream, dest_stream;
FILE *org, *dest;
int org_height, org_width;
int white, black;
int color, color_org, median;
int x, y;
float x_ratio, y_ratio;
#ifdef HAVE_GD_JPG
// long ignore_warning;
#endif
/* Check threshold value */
if (threshold < 0 || threshold > 8) {
raise_warning("Invalid threshold value '%d'", threshold);
return false;
}
/* Open origin file */
org_stream = php_open_plain_file(f_org, "rb", &org);
if (same(org_stream, false)) {
raise_warning("Unable to open '%s' for reading", f_org.c_str());
return false;
}
/* Open destination file */
dest_stream = php_open_plain_file(f_dest, "wb", &dest);
if (same(dest_stream, false)) {
raise_warning("Unable to open '%s' for writing", f_dest.c_str());
return false;
}
switch (image_type) {
#ifdef HAVE_GD_GIF_READ
case PHP_GDIMG_TYPE_GIF:
im_org = gdImageCreateFromGif(org);
if (im_org == NULL) {
raise_warning("Unable to open '%s' Not a valid GIF file",
f_org.c_str());
return false;
}
break;
#endif /* HAVE_GD_GIF_READ */
#ifdef HAVE_GD_JPG
case PHP_GDIMG_TYPE_JPG:
im_org = gdImageCreateFromJpeg(org);
if (im_org == NULL) {
raise_warning("Unable to open '%s' Not a valid JPEG file",
f_org.c_str());
return false;
}
break;
#endif /* HAVE_GD_JPG */
#ifdef HAVE_GD_PNG
case PHP_GDIMG_TYPE_PNG:
im_org = gdImageCreateFromPng(org);
if (im_org == NULL) {
raise_warning("Unable to open '%s' Not a valid PNG file",
f_org.c_str());
return false;
}
break;
#endif /* HAVE_GD_PNG */
default:
raise_warning("Format not supported");
return false;
}
org_width = gdImageSX (im_org);
org_height = gdImageSY (im_org);
x_ratio = (float) org_width / (float) dest_width;
y_ratio = (float) org_height / (float) dest_height;
if (x_ratio > 1 && y_ratio > 1) {
if (y_ratio > x_ratio) {
x_ratio = y_ratio;
} else {
y_ratio = x_ratio;
}
dest_width = (int) (org_width / x_ratio);
dest_height = (int) (org_height / y_ratio);
} else {
x_ratio = (float) dest_width / (float) org_width;
y_ratio = (float) dest_height / (float) org_height;
if (y_ratio < x_ratio) {
x_ratio = y_ratio;
} else {
y_ratio = x_ratio;
}
dest_width = (int) (org_width * x_ratio);
dest_height = (int) (org_height * y_ratio);
}
im_tmp = gdImageCreate (dest_width, dest_height);
if (im_tmp == NULL ) {
raise_warning("Unable to allocate temporary buffer");
return false;
}
gdImageCopyResized (im_tmp, im_org, 0, 0, 0, 0,
dest_width, dest_height, org_width, org_height);
gdImageDestroy(im_org);
f_fclose(org_stream);
im_dest = gdImageCreate(dest_width, dest_height);
if (im_dest == NULL) {
raise_warning("Unable to allocate destination buffer");
return false;
}
white = gdImageColorAllocate(im_dest, 255, 255, 255);
if (white == -1) {
raise_warning("Unable to allocate the colors for "
"the destination buffer");
return false;
}
black = gdImageColorAllocate(im_dest, 0, 0, 0);
if (black == -1) {
raise_warning("Unable to allocate the colors for "
"the destination buffer");
return false;
}
threshold = threshold * 32;
for (y = 0; y < dest_height; y++) {
for (x = 0; x < dest_width; x++) {
color_org = gdImageGetPixel (im_tmp, x, y);
median = (im_tmp->red[color_org] +
im_tmp->green[color_org] +
im_tmp->blue[color_org]) / 3;
if (median < threshold) {
color = black;
} else {
color = white;
}
gdImageSetPixel (im_dest, x, y, color);
}
}
gdImageDestroy (im_tmp );
gdImageWBMP(im_dest, black , dest);
fflush(dest);
f_fclose(dest_stream);
gdImageDestroy(im_dest);
return true;
}
#endif /* HAVE_GD_WBMP */
// For quality and type, -1 means that the argument does not exist
static bool _php_image_output(CObjRef image, CStrRef filename, int quality,
int type, int image_type, char *tn,
void (*func_p)()) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
Variant stream;
FILE *fp;
int q = quality, i, t = type;
/* The quality parameter for Wbmp stands for the threshold when
called from image2wbmp() */
/* When called from imagewbmp() the quality parameter stands
for the foreground color. Default: black. */
/* The quality parameter for gd2 stands for chunk size */
if (!filename.empty()) {
stream = php_open_plain_file(filename, "wb", &fp);
if (same(stream, false)) {
raise_warning("Unable to open '%s' for writing", filename.c_str());
return false;
}
switch (image_type) {
#ifdef HAVE_GD_WBMP
case PHP_GDIMG_CONVERT_WBM:
if (q == -1) {
q = 0;
} else if (q < 0 || q > 255) {
raise_warning("Invalid threshold value '%d'. "
"It must be between 0 and 255", q);
q = 0;
}
gdImageWBMP(im, q, fp);
break;
#endif
case PHP_GDIMG_TYPE_JPG: {
// gdImageJpeg
((void(*)(gdImagePtr, FILE *, int))(func_p))(im, fp, q);
break;
}
case PHP_GDIMG_TYPE_WBM:
for (i = 0; i < gdImageColorsTotal(im); i++) {
if (gdImageRed(im, i) == 0) break;
}
// gdImageWBMP
((void(*)(gdImagePtr, int, FILE *))(func_p))(im, i, fp);
break;
#if HAVE_LIBGD20
case PHP_GDIMG_TYPE_GD:
if (im->trueColor){
gdImageTrueColorToPalette(im,1,256);
}
// gdImageGd
((void(*)(gdImagePtr, FILE *))(func_p))(im, fp);
break;
#endif
#ifdef HAVE_GD_GD2
case PHP_GDIMG_TYPE_GD2:
if (q == -1) {
q = 128;
}
// gdImageGd2
((void(*)(gdImagePtr, FILE *, int, int))(func_p))(im, fp, q, t);
break;
#endif
default:
if (q == -1) {
q = 128;
}
((void(*)(gdImagePtr, FILE *, int, int))(func_p))(im, fp, q, t);
break;
}
fflush(fp);
f_fclose(stream);
} else {
int b;
FILE *tmp;
char buf[4096];
char path[PATH_MAX];
// open a temporary file
snprintf(path, sizeof(path), "/tmp/XXXXXX");
int fd = mkstemp(path);
if (fd == -1 || (tmp = fdopen(fd, "r+b")) == NULL) {
if (fd != -1) close(fd);
raise_warning("Unable to open temporary file");
return false;
}
switch (image_type) {
#ifdef HAVE_GD_WBMP
case PHP_GDIMG_CONVERT_WBM:
if (q == -1) {
q = 0;
} else if (q < 0 || q > 255) {
raise_warning("Invalid threshold value '%d'. "
"It must be between 0 and 255", q);
q = 0;
}
gdImageWBMP(im, q, tmp);
break;
#endif
case PHP_GDIMG_TYPE_JPG:
((void(*)(gdImagePtr, FILE *, int))(func_p))(im, tmp, q);
break;
case PHP_GDIMG_TYPE_WBM:
for (i = 0; i < gdImageColorsTotal(im); i++) {
if (gdImageRed(im, i) == 0) {
break;
}
}
((void(*)(gdImagePtr, int, FILE *))(func_p))(im, q, tmp);
break;
#if HAVE_LIBGD20
case PHP_GDIMG_TYPE_GD:
if (im->trueColor) {
gdImageTrueColorToPalette(im,1,256);
}
((void(*)(gdImagePtr, FILE *))(func_p))(im, tmp);
break;
#endif
#ifdef HAVE_GD_GD2
case PHP_GDIMG_TYPE_GD2:
if (q == -1) {
q = 128;
}
((void(*)(gdImagePtr, FILE *, int, int))(func_p))(im, tmp, q, t);
break;
#endif
default:
((void(*)(gdImagePtr, FILE *))(func_p))(im, tmp);
break;
}
fseek(tmp, 0, SEEK_SET);
while ((b = fread(buf, 1, sizeof(buf), tmp)) > 0) {
g_context->write(buf, b);
}
fclose(tmp);
/* make sure that the temporary file is removed */
unlink((const char *)path);
}
return true;
}
static gdImagePtr _php_image_create_from(CStrRef filename,
int srcX, int srcY,
int width, int height,
int image_type, char *tn,
gdImagePtr(*func_p)(),
gdImagePtr(*ioctx_func_p)()) {
gdImagePtr im = NULL;
Variant stream;
#ifdef HAVE_GD_JPG
// long ignore_warning;
#endif
if (image_type == PHP_GDIMG_TYPE_GD2PART) {
if (width < 1 || height < 1) {
raise_warning("Zero width or height not allowed");
return NULL;
}
}
stream = f_fopen(filename, "rb");
if (same(stream, false)) {
raise_warning("failed to open stream: %s", filename.c_str());
return NULL;
}
#ifndef USE_GD_IOCTX
ioctx_func_p = NULL; /* don't allow sockets without IOCtx */
#endif
FILE *fp = NULL;
File *file = Object(stream).getTyped<File>();
PlainFile *plain_file = dynamic_cast<PlainFile*>(file);
if (plain_file) {
fp = plain_file->getStream();
} else if (ioctx_func_p) {
#ifdef USE_GD_IOCTX
/* we can create an io context */
gdIOCtx* io_ctx;
// copy all
String buff = file->read(8192);
String str;
do {
str = file->read(8192);
buff += str;
} while (!str.empty());
if (buff.empty()) {
raise_warning("Cannot read image data");
goto out_err;
}
io_ctx = gdNewDynamicCtxEx(buff.length(), (char *)buff.c_str(), 0);
if (!io_ctx) {
raise_warning("Cannot allocate GD IO context");
goto out_err;
}
if (image_type == PHP_GDIMG_TYPE_GD2PART) {
im =
((gdImagePtr(*)(gdIOCtx *, int, int, int, int))(ioctx_func_p))
(io_ctx, srcX, srcY, width, height);
} else {
im = ((gdImagePtr(*)(gdIOCtx *))(ioctx_func_p))(io_ctx);
}
#if HAVE_LIBGD204
io_ctx->gd_free(io_ctx);
#else
io_ctx->free(io_ctx);
#endif
#endif
}
else {
/* TODO: try and force the stream to be FILE* */
assert(false);
}
if (!im && fp) {
switch (image_type) {
case PHP_GDIMG_TYPE_GD2PART:
im = ((gdImagePtr(*)(FILE *, int, int, int, int))(func_p))
(fp, srcX, srcY, width, height);
break;
#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED)
case PHP_GDIMG_TYPE_XPM:
im = gdImageCreateFromXpm(filename);
break;
#endif
#ifdef HAVE_GD_JPG
case PHP_GDIMG_TYPE_JPG:
im = gdImageCreateFromJpeg(fp);
break;
#endif
default:
im = ((gdImagePtr(*)(FILE*))(func_p))(fp);
break;
}
fflush(fp);
}
if (im) {
f_fclose(stream);
return im;
}
raise_warning("'%s' is not a valid %s file", filename.c_str(), tn);
out_err:
f_fclose(stream);
return NULL;
}
static const char php_sig_gd2[3] = {'g', 'd', '2'};
/* getmbi
** ------
** Get a multibyte integer from a generic getin function
** 'getin' can be getc, with in = NULL
** you can find getin as a function just above the main function
** This way you gain a lot of flexibilty about how this package
** reads a wbmp file.
*/
static int getmbi(int (*getin) (void *in), void *in) {
int i, mbi = 0;
do {
i = getin (in);
if (i < 0)
return (-1);
mbi = (mbi << 7) | (i & 0x7f);
} while (i & 0x80);
return (mbi);
}
/* skipheader
** ----------
** Skips the ExtHeader. Not needed for the moment
**
*/
int skipheader (int (*getin) (void *in), void *in) {
int i;
do {
i = getin (in);
if (i < 0) return (-1);
}
while (i & 0x80);
return (0);
}
static int _php_image_type (char data[8]) {
#ifdef HAVE_LIBGD15
if (data == NULL) {
return -1;
}
if (!memcmp(data, php_sig_gd2, 3)) {
return PHP_GDIMG_TYPE_GD2;
} else if (!memcmp(data, php_sig_jpg, 3)) {
return PHP_GDIMG_TYPE_JPG;
} else if (!memcmp(data, php_sig_png, 3)) {
if (!memcmp(data, php_sig_png, 8)) {
return PHP_GDIMG_TYPE_PNG;
}
} else if (!memcmp(data, php_sig_gif, 3)) {
return PHP_GDIMG_TYPE_GIF;
}
#ifdef HAVE_GD_WBMP
else {
gdIOCtx *io_ctx;
io_ctx = gdNewDynamicCtxEx(8, data, 0);
if (io_ctx) {
if (getmbi((int(*)(void *)) gdGetC, io_ctx) == 0 &&
skipheader((int(*)(void *)) gdGetC, io_ctx) == 0 ) {
#if HAVE_LIBGD204
io_ctx->gd_free(io_ctx);
#else
io_ctx->free(io_ctx);
#endif
return PHP_GDIMG_TYPE_WBM;
} else {
#if HAVE_LIBGD204
io_ctx->gd_free(io_ctx);
#else
io_ctx->free(io_ctx);
#endif
}
}
}
#endif
return -1;
#endif
}
#ifdef HAVE_LIBGD15
gdImagePtr _php_image_create_from_string(CStrRef image, char *tn,
gdImagePtr (*ioctx_func_p)()) {
gdImagePtr im;
gdIOCtx *io_ctx;
io_ctx = gdNewDynamicCtxEx(image.length(), (char *)image.c_str(), 0);
if (!io_ctx) {
return NULL;
}
im = (*(gdImagePtr (*)(gdIOCtx *))ioctx_func_p)(io_ctx);
if (!im) {
raise_warning("Passed data is not in '%s' format", tn);
#if HAVE_LIBGD204
io_ctx->gd_free(io_ctx);
#else
io_ctx->free(io_ctx);
#endif
return NULL;
}
#if HAVE_LIBGD204
io_ctx->gd_free(io_ctx);
#else
io_ctx->free(io_ctx);
#endif
return im;
}
#endif
static gdFontPtr php_find_gd_font(int size) {
gdFontPtr font;
switch (size) {
case 1:
font = gdFontTiny;
break;
case 2:
font = gdFontSmall;
break;
case 3:
font = gdFontMediumBold;
break;
case 4:
font = gdFontLarge;
break;
case 5:
font = gdFontGiant;
break;
default:
raise_warning("Unsupported font: %d", size);
// font = zend_list_find(size - 5, &ind_type);
// if (!font || ind_type != le_gd_font) {
if (size < 1) {
font = gdFontTiny;
} else {
font = gdFontGiant;
}
break;
}
return font;
}
/* workaround for a bug in gd 1.2 */
static void php_gdimagecharup(gdImagePtr im, gdFontPtr f, int x, int y,
int c, int color) {
int cx, cy, px, py, fline;
cx = 0;
cy = 0;
if ((c < f->offset) || (c >= (f->offset + f->nchars))) {
return;
}
fline = (c - f->offset) * f->h * f->w;
for (py = y; (py > (y - f->w)); py--) {
for (px = x; (px < (x + f->h)); px++) {
if (f->data[fline + cy * f->w + cx]) {
gdImageSetPixel(im, px, py, color);
}
cy++;
}
cy = 0;
cx++;
}
}
/*
* arg = 0 ImageChar
* arg = 1 ImageCharUp
* arg = 2 ImageString
* arg = 3 ImageStringUp
*/
static bool php_imagechar(CObjRef image, int size, int x, int y,
CStrRef c, int color, int mode) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int ch = 0;
gdFontPtr font;
if (mode < 2) {
ch = (int)((unsigned char)(c.charAt(0)));
}
font = php_find_gd_font(size);
switch (mode) {
case 0:
gdImageChar(im, font, x, y, ch, color);
break;
case 1:
php_gdimagecharup(im, font, x, y, ch, color);
break;
case 2:
for (int i = 0; (i < c.length()); i++) {
gdImageChar(im, font, x, y, (int)((unsigned char)c.charAt(i)), color);
x += font->w;
}
break;
case 3:
for (int i = 0; (i < c.length()); i++) {
gdImageCharUp(im, font, x, y, (int)c.charAt(i), color);
y -= font->w;
}
break;
}
return true;
}
/* arg = 0 normal polygon
arg = 1 filled polygon */
static bool php_imagepolygon(CObjRef image, CArrRef points, int num_points,
int color, int filled) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdPointPtr pts;
int nelem, i;
nelem = points.size();
if (nelem < 6) {
raise_warning("You must have at least 3 points in your array");
return false;
}
if (nelem < num_points * 2) {
raise_warning("Trying to use %d points in array with only %d points",
num_points, nelem/2);
return false;
}
pts = (gdPointPtr)IM_MALLOC(num_points * sizeof(gdPoint));
CHECK_ALLOC_R(pts, (num_points * sizeof(gdPoint)), false);
for (i = 0; i < num_points; i++) {
if (points.exists(i * 2)) {
pts[i].x = points[i * 2];
}
if (points.exists(i * 2 + 1)) {
pts[i].y = points[i * 2 + 1];
}
}
if (filled) {
gdImageFilledPolygon(im, pts, num_points, color);
} else {
color = SetupAntiAliasedColor(im, color);
gdImagePolygon(im, pts, num_points, color);
}
IM_FREE(pts);
return true;
}
static bool php_image_filter_negate(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageNegate does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageNegate(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_grayscale(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageGrayScale does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageGrayScale(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_brightness(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageBrightness does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int brightness = arg1;
if (gdImageBrightness(im, brightness) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_contrast(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageContrast does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int contrast = arg1;
if (gdImageContrast(im, contrast) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_colorize(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageColor does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int r = arg1;
int g = arg2;
int b = arg3;
int a = arg1;
if (gdImageColor(im, r, g, b, a) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_edgedetect(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__,
"gdImageEdgeDetectQuick does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageEdgeDetectQuick(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_emboss(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageEmboss does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageEmboss(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_gaussian_blur(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageGaussianBlur does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageGaussianBlur(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_selective_blur(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageSelectiveBlur does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageSelectiveBlur(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_mean_removal(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageMeanRemoval does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (gdImageMeanRemoval(im) == 1) {
return true;
}
return false;
*/
}
static bool php_image_filter_smooth(CObjRef image,
int arg1 /* = 0 */,
int arg2 /* = 0 */,
int arg3 /* = 0 */,
int arg4 /* = 0 */) {
throw NotSupportedException(__func__, "gdImageSmooth does not exists");
/*
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int weight = arg1;
if (gdImageSmooth(im, weight) == 1) {
return true;
}
return false;
*/
}
/*
* arg = 0 ImageFontWidth
* arg = 1 ImageFontHeight
*/
static int php_imagefontsize(int size, int arg) {
gdFontPtr font = php_find_gd_font(size);
return (arg ? font->h : font->w);
}
#ifdef ENABLE_GD_TTF
#define TTFTEXT_DRAW 0
#define TTFTEXT_BBOX 1
#endif
#ifdef ENABLE_GD_TTF
static Variant php_imagettftext_common(int mode, int extended,
CVarRef arg1,
CVarRef arg2,
CVarRef arg3,
CVarRef arg4,
CVarRef arg5 = null_variant,
CVarRef arg6 = null_variant,
CVarRef arg7 = null_variant,
CVarRef arg8 = null_variant,
CVarRef arg9 = null_variant) {
gdImagePtr im=NULL;
long col = -1, x = -1, y = -1;
int brect[8];
double ptsize, angle;
String str;
String fontname;
Array extrainfo;
char *error = NULL;
#if HAVE_GD_STRINGFTEX
gdFTStringExtra strex = {0};
#endif
#if !HAVE_GD_STRINGFTEX
always_assert(!extended);
#endif
if (mode == TTFTEXT_BBOX) {
ptsize = arg1;
angle = arg2;
fontname = arg3;
str = arg4;
extrainfo = arg5;
} else {
Object image = arg1;
ptsize = arg2;
angle = arg3;
x = toInt64(arg4);
y = toInt64(arg5);
col = toInt64(arg6);
fontname = arg7;
str = arg8;
extrainfo = arg9;
im = image.getTyped<Image>()->get();
if (!im) return false;
}
/* convert angle to radians */
angle = angle * (M_PI/180);
#if HAVE_GD_STRINGFTEX
if (extended && !extrainfo.empty()) { /* parse extended info */
/* walk the assoc array */
for (ArrayIter iter(extrainfo); iter; ++iter) {
Variant key = iter.first();
if (!key.isString()) continue;
Variant item = iter.second();
if (key == "linespacing") {
strex.flags |= gdFTEX_LINESPACE;
strex.linespacing = toDouble(item);
}
}
}
#endif
FILE *fp = NULL;
if (!RuntimeOption::FontPath.empty()) {
fontname = String(RuntimeOption::FontPath.c_str()) + f_basename(fontname);
}
Variant stream = php_open_plain_file(fontname, "rb", &fp);
if (same(stream, false)) {
raise_warning("Invalid font filename %s", fontname.c_str());
return false;
}
f_fclose(stream);
#ifdef USE_GD_IMGSTRTTF
# if HAVE_GD_STRINGFTEX
if (extended) {
error = gdImageStringFTEx(im, brect, col, (char*)fontname.c_str(),
ptsize, angle, x, y, (char*)str.c_str(),
&strex);
}
else
# endif
# if HAVE_GD_STRINGFT
error = gdImageStringFT(im, brect, col, (char*)fontname.c_str(),
ptsize, angle, x, y, (char*)str.c_str());
# elif HAVE_GD_STRINGTTF
error = gdImageStringTTF(im, brect, col, fontname.c_str(),
ptsize, angle, x, y, str.c_str());
# endif
#else /* !USE_GD_IMGSTRTTF */
error = gdttf(im, brect, col, fontname.c_str(),
ptsize, angle, x, y, str.c_str());
#endif
if (error) {
raise_warning("%s", error);
return false;
}
/* return array with the text's bounding box */
Array ret;
for (int i = 0; i < 8; i++) {
ret.set(i, brect[i]);
}
return ret;
}
#endif /* ENABLE_GD_TTF */
static const StaticString s_GD_Version("GD Version");
static const StaticString s_FreeType_Support("FreeType Support");
static const StaticString s_FreeType_Linkage("FreeType Linkage");
static const StaticString s_with_freetype("with freetype");
static const StaticString s_with_TTF_library("with TTF library");
static const StaticString s_with_unknown_library("with unknown library");
static const StaticString s_T1Lib_Support("T1Lib_Support");
static const StaticString s_GIF_Read_Support("GIF Read Support");
static const StaticString s_GIF_Create_Support("GIF Create Support");
static const StaticString s_JPG_Support("JPG Support");
static const StaticString s_PNG_Support("PNG Support");
static const StaticString s_WBMP_Support("WBMP Support");
static const StaticString s_XPM_Support("XPM Support");
static const StaticString s_XBM_Support("XBM Support");
static const StaticString
s_JIS_mapped_Japanese_Font_Support("JIS-mapped Japanese Font Support");
Array f_gd_info() {
Array ret;
ret.set(s_GD_Version, PHP_GD_VERSION_STRING);
#ifdef ENABLE_GD_TTF
ret.set(s_FreeType_Support, true);
#if HAVE_LIBFREETYPE
ret.set(s_FreeType_Linkage, s_with_freetype);
#elif HAVE_LIBTTF
ret.set(s_FreeType_Linkage, s_with_TTF_library);
#else
ret.set(s_FreeType_Linkage, s_with_unknown_library);
#endif
#else
ret.set(s_FreeType_Support, false);
#endif
#ifdef HAVE_LIBT1
ret.set(s_T1Lib_Support, true);
#else
ret.set(s_T1Lib_Support, false);
#endif
#ifdef HAVE_GD_GIF_READ
ret.set(s_GIF_Read_Support, true);
#else
ret.set(s_GIF_Read_Support, false);
#endif
#ifdef HAVE_GD_GIF_CREATE
ret.set(s_GIF_Create_Support, true);
#else
ret.set(s_GIF_Create_Support, false);
#endif
#ifdef HAVE_GD_JPG
ret.set(s_JPG_Support, true);
#else
ret.set(s_JPG_Support, false);
#endif
#ifdef HAVE_GD_PNG
ret.set(s_PNG_Support, true);
#else
ret.set(s_PNG_Support, false);
#endif
#ifdef HAVE_GD_WBMP
ret.set(s_WBMP_Support, true);
#else
ret.set(s_WBMP_Support, false);
#endif
#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED)
ret.set(s_XPM_Support, true);
#else
ret.set(s_XPM_Support, false);
#endif
#ifdef HAVE_GD_XBM
ret.set(s_XBM_Support, true);
#else
ret.set(s_XBM_Support, false);
#endif
#if defined(USE_GD_JISX0208) && defined(HAVE_GD_BUNDLED)
ret.set(s_JIS_mapped_Japanese_Font_Support, true);
#else
ret.set(s_JIS_mapped_Japanese_Font_Support, false);
#endif
return ret;
}
#define FLIPWORD(a) (((a & 0xff000000) >> 24) | \
((a & 0x00ff0000) >> 8) | \
((a & 0x0000ff00) << 8) | \
((a & 0x000000ff) << 24))
Variant f_imageloadfont(CStrRef file) {
// TODO: ind = 5 + zend_list_insert(font, le_gd_font);
throw NotSupportedException(__func__, "NYI");
#ifdef NEVER
Variant stream;
zval **file;
int hdr_size = sizeof(gdFont) - sizeof(char *);
int ind, body_size, n = 0, b, i, body_size_check;
gdFontPtr font;
php_stream *stream;
stream = f_fopen(file, "rb");
if (same(stream, false)) {
raise_warning("failed to open file: %s", file.c_str());
return false;
}
/* Only supports a architecture-dependent binary dump format
* at the moment.
* The file format is like this on machines with 32-byte integers:
*
* byte 0-3: (int) number of characters in the font
* byte 4-7: (int) value of first character in the font (often 32, space)
* byte 8-11: (int) pixel width of each character
* byte 12-15: (int) pixel height of each character
* bytes 16-: (char) array with character data, one byte per pixel
* in each character, for a total of
* (nchars*width*height) bytes.
*/
font = (gdFontPtr) IM_MALLOC(sizeof(gdFont));
CHECK_ALLOC_R(font, sizeof(gdFont), false);
b = 0;
String hdr = f_fread(stream, hdr_size);
if (hdr.length() < hdr_size) {
IM_FREE(font);
if (f_feof(stream)) {
raise_warning("End of file while reading header");
} else {
raise_warning("Error while reading header");
}
f_fclose(stream);
return false;
}
memcpy((void*)font, hdr.c_str(), hdr.length());
i = toInt64(f_tell(stream));
f_fseek(stream, 0, SEEK_END);
body_size_check = toInt64(f_tell(stream)) - hdr_size;
f_fseek(stream, i, SEEK_SET);
body_size = font->w * font->h * font->nchars;
if (body_size != body_size_check) {
font->w = FLIPWORD(font->w);
font->h = FLIPWORD(font->h);
font->nchars = FLIPWORD(font->nchars);
body_size = font->w * font->h * font->nchars;
}
if (font->nchars <= 0 || font->h <= 0 || font->nchars >= INT_MAX || font->h >= INT_MAX) {
raise_warning("Error reading font, invalid font header");
IM_FREE(font);
f_fclose(stream);
return false;
}
if ((font->nchars * font->h) <= 0 || font->w <= 0 || (font->nchars * font->h) >= INT_MAX || font->w >= INT_MAX) {
raise_warning("Error reading font, invalid font header");
IM_FREE(font);
f_fclose(stream);
return false;
}
if (body_size != body_size_check) {
raise_warning("Error reading font");
IM_FREE(font);
f_fclose(stream);
return false;
}
String body = f_fread(stream, body_size);
if (body.length() < body_size) {
IM_FREE(font);
if (f_feof(stream)) {
raise_warning("End of file while reading body");
} else {
raise_warning("Error while reading body");
}
f_fclose(stream);
return false;
}
font->data = IM_MALLOC(body_size);
CHECK_ALLOC_R(font->data, body_size, false);
memcpy((void*)font->data, body.c_str(), body.length());
f_fclose(stream);
/* Adding 5 to the font index so we will never have font indices
* that overlap with the old fonts (with indices 1-5). The first
* list index given out is always 1.
*/
// ind = 5 + zend_list_insert(font, le_gd_font);
return ind;
#endif
}
bool f_imagesetstyle(CObjRef image, CArrRef style) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int *stylearr;
int index;
size_t malloc_size = sizeof(int) * style.size();
stylearr = (int *)IM_MALLOC(malloc_size);
CHECK_ALLOC_R(stylearr, malloc_size, false);
index = 0;
for (ArrayIter iter(style); iter; ++iter) {
stylearr[index++] = iter.secondRef();
}
gdImageSetStyle(im, stylearr, index);
IM_FREE(stylearr);
return true;
}
Variant f_imagecreatetruecolor(int width, int height) {
gdImagePtr im;
if (width <= 0 || height <= 0 || width >= INT_MAX || height >= INT_MAX) {
raise_warning("Invalid image dimensions");
return false;
}
im = gdImageCreateTrueColor(width, height);
if (!im) {
return false;
}
return Object(new Image(im));
}
bool f_imageistruecolor(CObjRef image) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return im->trueColor;
}
Variant f_imagetruecolortopalette(CObjRef image, bool dither, int ncolors) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (ncolors <= 0) {
raise_warning("Number of colors has to be greater than zero");
return false;
}
gdImageTrueColorToPalette(im, dither, ncolors);
return true;
}
Variant f_imagecolormatch(CObjRef image1, CObjRef image2) {
throw NotSupportedException(__func__, "gdImageColorMatch does not exist");
/*
gdImagePtr im1 = image1.getTyped<Image>()->get();
if (!im1) return false;
gdImagePtr im2 = image2.getTyped<Image>()->get();
if (!im2) return false;
int result;
result = gdImageColorMatch(im1, im2);
switch (result) {
case -1:
raise_warning("Image1 must be TrueColor" );
return false;
case -2:
raise_warning("Image2 must be Palette" );
return false;
case -3:
raise_warning("Image1 and Image2 must be the same size" );
return false;
case -4:
raise_warning("Image2 must have at least one color" );
return false;
}
return true;
*/
}
bool f_imagesetthickness(CObjRef image, int thickness) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageSetThickness(im, thickness);
return true;
}
bool f_imagefilledellipse(CObjRef image, int cx, int cy,
int width, int height, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageFilledEllipse(im, cx, cy, width, height, color);
return true;
}
bool f_imagefilledarc(CObjRef image, int cx, int cy, int width, int height,
int start, int end, int color, int style) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (end < 0) end %= 360;
if (start < 0) start %= 360;
gdImageFilledArc(im, cx, cy, width, height, start, end, color, style);
return true;
}
bool f_imagealphablending(CObjRef image, bool blendmode) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageAlphaBlending(im, blendmode);
return true;
}
bool f_imagesavealpha(CObjRef image, bool saveflag) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageSaveAlpha(im, saveflag);
return true;
}
bool f_imagelayereffect(CObjRef image, int effect) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageAlphaBlending(im, effect);
return true;
}
Variant f_imagecolorallocatealpha(CObjRef image, int red, int green, int blue,
int alpha) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int ct = gdImageColorAllocateAlpha(im, red, green, blue, alpha);
if (ct < 0) {
return false;
}
return ct;
}
Variant f_imagecolorresolvealpha(CObjRef image, int red, int green, int blue,
int alpha) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorResolveAlpha(im, red, green, blue, alpha);
}
Variant f_imagecolorclosestalpha(CObjRef image, int red, int green, int blue,
int alpha) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorClosestAlpha(im, red, green, blue, alpha);
}
Variant f_imagecolorexactalpha(CObjRef image, int red, int green, int blue,
int alpha) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorExactAlpha(im, red, green, blue, alpha);
}
bool f_imagecopyresampled(CObjRef dst_im, CObjRef src_im,
int dst_x, int dst_y, int src_x, int src_y,
int dst_w, int dst_h, int src_w, int src_h) {
gdImagePtr im_src = src_im.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = dst_im.getTyped<Image>()->get();
if (!im_dst) return false;
gdImageCopyResampled(im_dst, im_src, dst_x, dst_y, src_x, src_y,
dst_w, dst_h, src_w, src_h);
return true;
}
Variant f_imagegrabwindow(int window, int client_area /* = 0 */) {
#ifdef PHP_WIN32
#error config error: PHP_WIN32 defined!
#else
throw NotSupportedException(__func__, "PHP_WIN32 undefined");
#endif
}
Variant f_imagegrabscreen() {
#ifdef PHP_WIN32
#error config error: PHP_WIN32 defined!
#else
throw NotSupportedException(__func__, "PHP_WIN32 undefined");
#endif
}
Variant f_imagerotate(CObjRef source_image, double angle, int bgd_color,
int ignore_transparent /* = 0 */) {
#if defined(HPHP_OSS)
throw NotSupportedException(__func__, "gdImageRotate does not exist");
#else
gdImagePtr im_src = source_image.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = gdImageRotate(im_src, angle, bgd_color,
ignore_transparent);
if (!im_dst) return false;
return Object(new Image(im_dst));
#endif
}
bool f_imagesettile(CObjRef image, CObjRef tile) {
#if HAVE_GD_IMAGESETTILE
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImagePtr til = tile.getTyped<Image>()->get();
if (!til) return false;
gdImageSetTile(im, til);
return true;
#else
throw NotSupportedException(__func__, "HAVE_GD_IMAGESETTILE undefined");
#endif
}
bool f_imagesetbrush(CObjRef image, CObjRef brush) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImagePtr tile = brush.getTyped<Image>()->get();
if (!tile) return false;
gdImageSetBrush(im, tile);
return true;
}
Variant f_imagecreate(int width, int height) {
gdImagePtr im;
if (width <= 0 || height <= 0 || width >= INT_MAX || height >= INT_MAX) {
raise_warning("Invalid image dimensions");
return false;
}
im = gdImageCreate(width, height);
if (!im) {
return false;
}
return Object(new Image(im));
}
int64_t f_imagetypes() {
int ret=0;
#ifdef HAVE_GD_GIF_CREATE
ret = 1;
#endif
#ifdef HAVE_GD_JPG
ret |= 2;
#endif
#ifdef HAVE_GD_PNG
ret |= 4;
#endif
#ifdef HAVE_GD_WBMP
ret |= 8;
#endif
#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED)
ret |= 16;
#endif
return ret;
}
Variant f_imagecreatefromstring(CStrRef data) {
#ifdef HAVE_LIBGD15
gdImagePtr im;
int imtype;
char sig[8];
if (data.length() < 8) {
raise_warning("Empty string or invalid image");
return false;
}
memcpy(sig, data.c_str(), 8);
imtype = _php_image_type(sig);
switch (imtype) {
case PHP_GDIMG_TYPE_JPG:
#ifdef HAVE_GD_JPG
im = _php_image_create_from_string(data, "JPEG",
(gdImagePtr(*)())gdImageCreateFromJpegCtx);
#else
raise_warning("No JPEG support");
return false;
#endif
break;
case PHP_GDIMG_TYPE_PNG:
#ifdef HAVE_GD_PNG
im = _php_image_create_from_string(data, "PNG",
(gdImagePtr(*)())gdImageCreateFromPngCtx);
#else
raise_warning("No PNG support");
return false;
#endif
break;
case PHP_GDIMG_TYPE_GIF:
#ifdef HAVE_GD_GIF_READ
im = _php_image_create_from_string(data, "GIF",
(gdImagePtr(*)())gdImageCreateFromGifCtx);
#else
raise_warning("No GIF support");
return false;
#endif
break;
case PHP_GDIMG_TYPE_WBM:
#ifdef HAVE_GD_WBMP
im = _php_image_create_from_string(data, "WBMP",
(gdImagePtr(*)())gdImageCreateFromWBMPCtx);
#else
raise_warning("No WBMP support");
return false;
#endif
break;
case PHP_GDIMG_TYPE_GD2:
#ifdef HAVE_GD_GD2
im = _php_image_create_from_string(data, "GD2",
(gdImagePtr(*)())gdImageCreateFromGd2Ctx);
#else
raise_warning("No GD2 support");
return false;
#endif
break;
default:
raise_warning("Data is not in a recognized format");
return false;
}
if (!im) {
raise_warning("Couldn't create GD Image Stream out of Data");
return false;
}
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_LIBGD15 undefined");
#endif
}
Variant f_imagecreatefromgif(CStrRef filename) {
#ifdef HAVE_GD_GIF_READ
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_GIF, "GIF",
(gdImagePtr(*)())gdImageCreateFromGif,
(gdImagePtr(*)())gdImageCreateFromGifCtx);
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_GD_GIF_READ undefined");
#endif
}
Variant f_imagecreatefromjpeg(CStrRef filename) {
#ifdef HAVE_GD_JPG
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_JPG, "JPEG",
(gdImagePtr(*)())gdImageCreateFromJpeg,
(gdImagePtr(*)())gdImageCreateFromJpegCtx);
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_GD_JPG undefined");
#endif
}
Variant f_imagecreatefrompng(CStrRef filename) {
#ifdef HAVE_GD_PNG
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_PNG, "PNG",
(gdImagePtr(*)())gdImageCreateFromPng,
(gdImagePtr(*)())gdImageCreateFromPngCtx);
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_GD_PNG undefined");
#endif
}
Variant f_imagecreatefromxbm(CStrRef filename) {
#ifdef HAVE_GD_XBM
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_XBM, "XBM",
(gdImagePtr(*)())gdImageCreateFromXbm,
(gdImagePtr(*)())NULL);
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_GD_XBM undefined");
#endif
}
Variant f_imagecreatefromxpm(CStrRef filename) {
#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED)
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_XPM, "XPM",
(gdImagePtr(*)())gdImageCreateFromXpm,
(gdImagePtr(*)())NULL);
return Object(new Image(im));
#else
throw NotSupportedException(__func__,
"HAVE_GD_XPM or HAVE_GD_BUNDLED undefined");
#endif
}
Variant f_imagecreatefromwbmp(CStrRef filename) {
#ifdef HAVE_GD_WBMP
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_WBM, "WBMP",
(gdImagePtr(*)())gdImageCreateFromWBMP,
(gdImagePtr(*)())gdImageCreateFromWBMPCtx);
return Object(new Image(im));
#else
throw NotSupportedException(__func__, "HAVE_GD_WBMP undefined");
#endif
}
Variant f_imagecreatefromgd(CStrRef filename) {
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_GD, "GD",
(gdImagePtr(*)())gdImageCreateFromGd,
(gdImagePtr(*)())gdImageCreateFromGdCtx);
return Object(new Image(im));
}
Variant f_imagecreatefromgd2(CStrRef filename) {
gdImagePtr im =
_php_image_create_from(filename, -1, -1, -1, -1,
PHP_GDIMG_TYPE_GD2, "GD2",
(gdImagePtr(*)())gdImageCreateFromGd2,
(gdImagePtr(*)())gdImageCreateFromGd2Ctx);
return Object(new Image(im));
}
Variant f_imagecreatefromgd2part(CStrRef filename,
int srcx, int srcy, int width, int height) {
gdImagePtr im =
_php_image_create_from(filename, srcx, srcy, width, height,
PHP_GDIMG_TYPE_GD2PART, "GD2",
(gdImagePtr(*)())gdImageCreateFromGd2Part,
(gdImagePtr(*)())gdImageCreateFromGd2PartCtx);
return Object(new Image(im));
}
bool f_imagexbm(CObjRef image, CStrRef filename /* = null_string */,
int foreground /* = -1 */) {
throw NotSupportedException(__func__, "gdImageXbmCtx does not exist");
}
bool f_imagegif(CObjRef image, CStrRef filename /* = null_string */) {
#ifdef HAVE_GD_GIF_CTX
return _php_image_output_ctx(image, filename, -1, -1,
PHP_GDIMG_TYPE_GIF, "GIF",
(void (*)())gdImageGifCtx);
#else
return _php_image_output(image, filename, -1, -1,
PHP_GDIMG_TYPE_GIF, "GIF",
(void (*)())gdImageGif);
#endif
}
bool f_imagepng(CObjRef image, CStrRef filename /* = null_string */,
int quality /* = -1 */, int filters /* = -1 */) {
#ifdef HAVE_GD_PNG
#ifdef USE_GD_IOCTX
return _php_image_output_ctx(image, filename, quality, filters,
PHP_GDIMG_TYPE_PNG, "PNG",
(void (*)())gdImagePngCtxEx);
#else
return _php_image_output(image, filename, quality, filters,
PHP_GDIMG_TYPE_PNG, "PNG",
(void (*)())gdImagePng);
#endif
#else
throw NotSupportedException(__func__, "HAVE_GD_PNG undefined");
#endif
}
bool f_imagejpeg(CObjRef image, CStrRef filename /* = null_string */,
int quality /* = -1 */) {
#ifdef HAVE_GD_JPG
#ifdef USE_GD_IOCTX
return _php_image_output_ctx(image, filename, quality, -1,
PHP_GDIMG_TYPE_JPG, "JPEG",
(void (*)())gdImageJpegCtx);
#else
return _php_image_output(image, filename, quality, -1,
PHP_GDIMG_TYPE_JPG, "JPEG",
(void (*)())gdImageJpeg);
#endif
#else
throw NotSupportedException(__func__, "HAVE_GD_JPG undefined");
#endif
}
bool f_imagewbmp(CObjRef image, CStrRef filename /* = null_string */,
int foreground /* = -1 */) {
#ifdef USE_GD_IOCTX
return _php_image_output_ctx(image, filename, foreground, -1,
PHP_GDIMG_TYPE_WBM, "WBMP",
(void (*)())gdImageWBMPCtx);
#else
retgurn _php_image_output(image, filename, foreground, -1,
PHP_GDIMG_TYPE_WBM, "WBMP",
(void (*)())gdImageWBMP);
#endif
}
bool f_imagegd(CObjRef image, CStrRef filename /* = null_string */) {
return _php_image_output(image, filename, -1, -1, PHP_GDIMG_TYPE_GD, "GD",
(void (*)())gdImageGd);
}
bool f_imagegd2(CObjRef image, CStrRef filename /* = null_string */,
int chunk_size /* = 0 */, int type /* = 0 */) {
return _php_image_output(image, filename, chunk_size, type,
PHP_GDIMG_TYPE_GD2, "GD2",
(void (*)())gdImageGd2);
}
bool f_imagedestroy(CObjRef image) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageDestroy(im);
image.getTyped<Image>()->reset();
return true;
}
Variant f_imagecolorallocate(CObjRef image, int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
int ct = gdImageColorAllocate(im, red, green, blue);
if (ct < 0) {
return false;
}
return ct;
}
void f_imagepalettecopy(CObjRef destination, CObjRef source) {
/*
gdImagePtr dstim = destination.getTyped<Image>()->get();
if (!dstim) return false;
gdImagePtr srcim = source.getTyped<Image>()->get();
if (!srcim) return false;
gdImagePaletteCopy(dstim, srcim);
*/
}
Variant f_imagecolorat(CObjRef image, int x, int y) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
#if HAVE_LIBGD20
if (gdImageTrueColor(im)) {
if (im->tpixels && gdImageBoundsSafe(im, x, y)) {
return gdImageTrueColorPixel(im, x, y);
} else {
raise_notice("%d,%d is out of bounds", x, y);
return false;
}
} else {
#endif
if (im->pixels && gdImageBoundsSafe(im, x, y)) {
#if HAVE_LIBGD13
return (im->pixels[y][x]);
#else
return (im->pixels[x][y]);
#endif
} else {
raise_notice("%d,%d is out of bounds", x, y);
return false;
}
#if HAVE_LIBGD20
}
#endif
}
Variant f_imagecolorclosest(CObjRef image, int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorClosest(im, red, green, blue);
}
Variant f_imagecolorclosesthwb(CObjRef image, int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorClosestHWB(im, red, green, blue);
}
bool f_imagecolordeallocate(CObjRef image, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
#if HAVE_LIBGD20
/* We can return right away for a truecolor image as deallocating colours
is meaningless here */
if (gdImageTrueColor(im)) return true;
#endif
if (color >= 0 && color < gdImageColorsTotal(im)) {
gdImageColorDeallocate(im, color);
return true;
} else {
raise_warning("Color index %d out of range", color);
return false;
}
}
Variant f_imagecolorresolve(CObjRef image, int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorResolve(im, red, green, blue);
}
Variant f_imagecolorexact(CObjRef image, int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageColorExact(im, red, green, blue);
}
Variant f_imagecolorset(CObjRef image, int index,
int red, int green, int blue) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (index >= 0 && index < gdImageColorsTotal(im)) {
im->red[index] = red;
im->green[index] = green;
im->blue[index] = blue;
return true;
} else {
return false;
}
}
static const StaticString s_red("red");
static const StaticString s_green("green");
static const StaticString s_blue("blue");
static const StaticString s_alpha("alpha");
Variant f_imagecolorsforindex(CObjRef image, int index) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
#if HAVE_LIBGD20
if ((index >= 0 && gdImageTrueColor(im)) ||
(!gdImageTrueColor(im) && index >= 0 &&
index < gdImageColorsTotal(im))) {
ArrayInit ret(4);
ret.set(s_red, gdImageRed(im,index));
ret.set(s_green, gdImageGreen(im,index));
ret.set(s_blue, gdImageBlue(im,index));
ret.set(s_alpha, gdImageAlpha(im,index));
return ret.create();
}
#else
if (col >= 0 && col < gdImageColorsTotal(im)) {
ArrayInit ret(3);
ret.set(s_red, im->red[col]);
ret.set(s_green, im->green[col]);
ret.set(s_blue, im->blue[col]);
return ret.create();
}
#endif
raise_warning("Color index %d out of range", index);
return false;
}
bool f_imagegammacorrect(CObjRef image, double inputgamma,
double outputgamma) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
#if HAVE_LIBGD20
if (gdImageTrueColor(im)) {
int x, y, c;
for (y = 0; y < gdImageSY(im); y++) {
for (x = 0; x < gdImageSX(im); x++) {
c = gdImageGetPixel(im, x, y);
gdImageSetPixel(im, x, y,
gdTrueColor((int)((pow((pow((gdTrueColorGetRed(c)/255.0),
inputgamma)),1.0/outputgamma)*255) + .5),
(int)((pow((pow((gdTrueColorGetGreen(c)/255.0),
inputgamma)),1.0/outputgamma) * 255) + .5),
(int)((pow((pow((gdTrueColorGetBlue(c)/255.0),
inputgamma)),1.0/outputgamma) * 255) + .5)));
}
}
return true;
}
#endif
for (int i = 0; i < gdImageColorsTotal(im); i++) {
im->red[i] = (int)((pow((pow((im->red[i]/255.0), inputgamma)),
1.0/outputgamma)*255) + .5);
im->green[i] = (int)((pow((pow((im->green[i]/255.0), inputgamma)),
1.0/outputgamma)*255) + .5);
im->blue[i] = (int)((pow((pow((im->blue[i]/255.0), inputgamma)),
1.0/outputgamma)*255) + .5);
}
return true;
}
bool f_imagesetpixel(CObjRef image, int x, int y, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageSetPixel(im, x, y, color);
return true;
}
bool f_imageline(CObjRef image, int x1, int y1, int x2, int y2, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
color = SetupAntiAliasedColor(im, color);
gdImageLine(im, x1, y1, x2, y2, color);
return true;
}
bool f_imagedashedline(CObjRef image, int x1, int y1, int x2, int y2,
int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageDashedLine(im, x1, y1, x2, y2, color);
return true;
}
bool f_imagerectangle(CObjRef image, int x1, int y1,
int x2, int y2, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageRectangle(im, x1, y1, x2, y2, color);
return true;
}
bool f_imagefilledrectangle(CObjRef image, int x1, int y1,
int x2, int y2, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageFilledRectangle(im, x1, y1, x2, y2, color);
return true;
}
bool f_imagearc(CObjRef image, int cx, int cy, int width, int height,
int start, int end, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (end < 0) end %= 360;
if (start < 0) start %= 360;
color = SetupAntiAliasedColor(im, color);
gdImageArc(im, cx, cy, width, height, start, end, color);
return true;
}
bool f_imageellipse(CObjRef image, int cx, int cy, int width, int height,
int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
color = SetupAntiAliasedColor(im, color);
gdImageArc(im, cx, cy, width, height, 0, 360, color);
return true;
}
bool f_imagefilltoborder(CObjRef image, int x, int y,
int border, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageFillToBorder(im, x, y, border, color);
return true;
}
bool f_imagefill(CObjRef image, int x, int y, int color) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
gdImageFill(im, x, y, color);
return true;
}
Variant f_imagecolorstotal(CObjRef image) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return (gdImageColorsTotal(im));
}
Variant f_imagecolortransparent(CObjRef image, int color /* = -1 */) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (color != -1) {
// has color argument
gdImageColorTransparent(im, color);
}
return gdImageGetTransparent(im);
}
Variant f_imageinterlace(CObjRef image, int interlace /* = 0 */) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
if (interlace != 0) {
// has interlace argument
gdImageInterlace(im, interlace);
}
return gdImageGetInterlaced(im);
}
bool f_imagepolygon(CObjRef image, CArrRef points,
int num_points, int color) {
return php_imagepolygon(image, points, num_points, color, 0);
}
bool f_imagefilledpolygon(CObjRef image, CArrRef points, int num_points,
int color) {
return php_imagepolygon(image, points, num_points, color, 1);
}
int64_t f_imagefontwidth(int font) {
return php_imagefontsize(font, 0);
}
int64_t f_imagefontheight(int font) {
return php_imagefontsize(font, 1);
}
bool f_imagechar(CObjRef image, int font, int x, int y,
CStrRef c, int color) {
return php_imagechar(image, font, x, y, c, color, 0);
}
bool f_imagecharup(CObjRef image, int font, int x, int y,
CStrRef c, int color) {
return php_imagechar(image, font, x, y, c, color, 1);
}
bool f_imagestring(CObjRef image, int font, int x, int y,
CStrRef str, int color) {
return php_imagechar(image, font, x, y, str, color, 2);
}
bool f_imagestringup(CObjRef image, int font, int x, int y,
CStrRef str, int color) {
return php_imagechar(image, font, x, y, str, color, 3);
}
bool f_imagecopy(CObjRef dst_im, CObjRef src_im, int dst_x, int dst_y,
int src_x, int src_y, int src_w, int src_h) {
gdImagePtr im_src = src_im.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = dst_im.getTyped<Image>()->get();
if (!im_dst) return false;
gdImageCopy(im_dst, im_src, dst_x, dst_y, src_x, src_y, src_w, src_h);
return true;
}
bool f_imagecopymerge(CObjRef dst_im, CObjRef src_im,
int dst_x, int dst_y, int src_x, int src_y,
int src_w, int src_h, int pct) {
gdImagePtr im_src = src_im.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = dst_im.getTyped<Image>()->get();
if (!im_dst) return false;
#if HAVE_LIBGD15
gdImageCopyMerge(im_dst, im_src, dst_x, dst_y,
src_x, src_y, src_w, src_h, pct);
return true;
#else
throw NotSupportedException(__func__, "HAVE_LIBGD15 undefined");
#endif
}
bool f_imagecopymergegray(CObjRef dst_im, CObjRef src_im,
int dst_x, int dst_y,
int src_x, int src_y,
int src_w, int src_h, int pct) {
gdImagePtr im_src = src_im.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = dst_im.getTyped<Image>()->get();
if (!im_dst) return false;
gdImageCopyMergeGray(im_dst, im_src, dst_x, dst_y,
src_x, src_y, src_w, src_h, pct);
return true;
}
bool f_imagecopyresized(CObjRef dst_im, CObjRef src_im,
int dst_x, int dst_y, int src_x, int src_y,
int dst_w, int dst_h, int src_w, int src_h) {
gdImagePtr im_src = src_im.getTyped<Image>()->get();
if (!im_src) return false;
gdImagePtr im_dst = dst_im.getTyped<Image>()->get();
if (!im_dst) return false;
if (dst_w <= 0 || dst_h <= 0 || src_w <= 0 || src_h <= 0) {
raise_warning("Invalid image dimensions");
return false;
}
gdImageCopyResized(im_dst, im_src,
dst_x, dst_y, src_x, src_y,
dst_w, dst_h, src_w, src_h);
return true;
}
Variant f_imagesx(CObjRef image) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageSX(im);
}
Variant f_imagesy(CObjRef image) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
return gdImageSY(im);
}
Variant f_imageftbbox(double size, double angle, CStrRef font_file,
CStrRef text, CArrRef extrainfo /* = null */) {
#if defined(ENABLE_GD_TTF) && HAVE_LIBGD20 && \
HAVE_LIBFREETYPE && HAVE_GD_STRINGFTEX
return php_imagettftext_common(TTFTEXT_BBOX, 1,
size, angle, font_file, text, extrainfo);
#else
throw NotSupportedException(__func__, "ENABLE_GD_TTF undefined or "
"!(HAVE_LIBGD20 && HAVE_LIBFREETYPE && "
"HAVE_GD_STRINGFTEX)");
#endif
}
Variant f_imagefttext(CObjRef image, double size, double angle,
int x, int y, int col, CStrRef font_file,
CStrRef text, CArrRef extrainfo /* = null */) {
#if defined(ENABLE_GD_TTF) && HAVE_LIBGD20 && \
HAVE_LIBFREETYPE && HAVE_GD_STRINGFTEX
return php_imagettftext_common(TTFTEXT_DRAW, 1,
image, size, angle, x, y, col,
font_file, text, extrainfo);
#else
throw NotSupportedException(__func__, "ENABLE_GD_TTF undefined or "
"!(HAVE_LIBGD20 && HAVE_LIBFREETYPE && "
"HAVE_GD_STRINGFTEX)");
#endif
}
Variant f_imagettfbbox(double size, double angle,
CStrRef fontfile, CStrRef text) {
#ifdef ENABLE_GD_TTF
return php_imagettftext_common(TTFTEXT_BBOX, 0,
size, angle, fontfile, text);
#else
throw NotSupportedException(__func__, "ENABLE_GD_TTF undefined");
#endif
}
Variant f_imagettftext(CObjRef image, double size, double angle,
int x, int y, int color,
CStrRef fontfile, CStrRef text) {
#ifdef ENABLE_GD_TTF
return php_imagettftext_common(TTFTEXT_DRAW, 0,
image, size, angle, x, y, color,
fontfile, text);
#else
throw NotSupportedException(__func__, "ENABLE_GD_TTF undefined");
#endif
}
Object f_imagepsloadfont(CStrRef filename) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
bool f_imagepsfreefont(CObjRef fontindex) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
bool f_imagepsencodefont(CObjRef font_index, CStrRef encodingfile) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
bool f_imagepsextendfont(int font_index, double extend) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
bool f_imagepsslantfont(CObjRef font_index, double slant) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
Array f_imagepstext(CObjRef image, CStrRef text, CObjRef font, int size,
int foreground, int background, int x, int y,
int space /* = 0 */, int tightness /* = 0 */,
double angle /* = 0.0 */,
int antialias_steps /* = 0 */) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
Array f_imagepsbbox(CStrRef text, int font, int size, int space /* = 0 */,
int tightness /* = 0 */, double angle /* = 0.0 */) {
#ifdef HAVE_LIBT1
#error config error: HAVE_LIBT1 defined!
#else
throw NotSupportedException(__func__, "HAVE_LIBT1 undefined");
#endif
}
bool f_image2wbmp(CObjRef image, CStrRef filename /* = null_string */,
int threshold /* = -1 */) {
#ifdef HAVE_GD_WBMP
return _php_image_output(image, filename, threshold, -1,
PHP_GDIMG_CONVERT_WBM, "WBMP",
(void (*)())_php_image_bw_convert);
#else
throw NotSupportedException(__func__, "HAVE_GD_WBMP undefined");
#endif
}
bool f_jpeg2wbmp(CStrRef jpegname, CStrRef wbmpname, int dest_height,
int dest_width, int threshold) {
return _php_image_convert(jpegname, wbmpname, dest_height, dest_width,
threshold, PHP_GDIMG_TYPE_JPG);
}
bool f_png2wbmp(CStrRef pngname, CStrRef wbmpname, int dest_height,
int dest_width, int threshold) {
return _php_image_convert(pngname, wbmpname, dest_height, dest_width,
threshold, PHP_GDIMG_TYPE_PNG);
}
bool f_imagefilter(CObjRef image, int filtertype,
int arg1 /* = 0 */, int arg2 /* = 0 */,
int arg3 /* = 0 */, int arg4 /* = 0 */) {
typedef bool (*image_filter)(CObjRef, int, int, int, int);
image_filter filters[] = {
php_image_filter_negate ,
php_image_filter_grayscale,
php_image_filter_brightness,
php_image_filter_contrast,
php_image_filter_colorize,
php_image_filter_edgedetect,
php_image_filter_emboss,
php_image_filter_gaussian_blur,
php_image_filter_selective_blur,
php_image_filter_mean_removal,
php_image_filter_smooth
};
int num_filters = sizeof(filters)/sizeof(image_filter);
if (filtertype >= 0 && filtertype < num_filters) {
return filters[filtertype](image, arg1, arg2, arg3, arg4);
}
return false;
}
// gdImageConvolution does not exist in our libgd.a, copied from
// php's libgd/gd.c
/* Filters function added on 2003/12
* by Pierre-Alain Joye (pajoye@pearfr.org)
**/
/* Begin filters function */
#ifndef HAVE_GET_TRUE_COLOR
#define GET_PIXEL_FUNCTION(src) \
(src->trueColor?gdImageGetTrueColorPixel:gdImageGetPixel)
#endif
static int gdImageConvolution(gdImagePtr src, float filter[3][3],
float filter_div, float offset) {
int x, y, i, j, new_a;
float new_r, new_g, new_b;
int new_pxl, pxl=0;
gdImagePtr srcback;
typedef int (*FuncPtr)(gdImagePtr, int, int);
FuncPtr f;
if (src==NULL) {
return 0;
}
/* We need the orinal image with each safe neoghb. pixel */
srcback = gdImageCreateTrueColor (src->sx, src->sy);
gdImageCopy(srcback, src,0,0,0,0,src->sx,src->sy);
if (srcback==NULL) {
return 0;
}
f = GET_PIXEL_FUNCTION(src);
for ( y=0; y<src->sy; y++) {
for(x=0; x<src->sx; x++) {
new_r = new_g = new_b = 0;
new_a = gdImageAlpha(srcback, pxl);
for (j=0; j<3; j++) {
int yv = MIN(MAX(y - 1 + j, 0), src->sy - 1);
for (i=0; i<3; i++) {
pxl = f(srcback, MIN(MAX(x - 1 + i, 0), src->sx - 1), yv);
new_r += (float)gdImageRed(srcback, pxl) * filter[j][i];
new_g += (float)gdImageGreen(srcback, pxl) * filter[j][i];
new_b += (float)gdImageBlue(srcback, pxl) * filter[j][i];
}
}
new_r = (new_r/filter_div)+offset;
new_g = (new_g/filter_div)+offset;
new_b = (new_b/filter_div)+offset;
new_r = (new_r > 255.0f)? 255.0f : ((new_r < 0.0f)? 0.0f:new_r);
new_g = (new_g > 255.0f)? 255.0f : ((new_g < 0.0f)? 0.0f:new_g);
new_b = (new_b > 255.0f)? 255.0f : ((new_b < 0.0f)? 0.0f:new_b);
new_pxl = gdImageColorAllocateAlpha(src, (int)new_r, (int)new_g,
(int)new_b, new_a);
if (new_pxl == -1) {
new_pxl = gdImageColorClosestAlpha(src, (int)new_r, (int)new_g,
(int)new_b, new_a);
}
gdImageSetPixel (src, x, y, new_pxl);
}
}
gdImageDestroy(srcback);
return 1;
}
bool f_imageconvolution(CObjRef image, CArrRef matrix,
double div, double offset) {
gdImagePtr im_src = image.getTyped<Image>()->get();
if (!im_src) return false;
int nelem = matrix.size();
int i, j;
float mtx[3][3] = {{0,0,0}, {0,0,0}, {0,0,0}};
Variant v;
Array row;
if (nelem != 3) {
raise_warning("You must have 3x3 array");
return false;
}
for (i=0; i<3; i++) {
if (matrix.exists(i) && (v = matrix[i]).isArray()) {
if ((row = toArray(v)).size() != 3) {
raise_warning("You must have 3x3 array");
return false;
}
for (j=0; j<3; j++) {
if (row.exists(j)) {
mtx[i][j] = toDouble(row[j]);
} else {
raise_warning("You must have a 3x3 matrix");
return false;
}
}
}
}
if (gdImageConvolution(im_src, mtx, div, offset)) {
return true;
} else {
return false;
}
}
bool f_imageantialias(CObjRef image, bool on) {
gdImagePtr im = image.getTyped<Image>()->get();
if (!im) return false;
SetAntiAliased(im, on);
return true;
}
// PHP extension STANDARD: iptc.c
static int php_iptc_put1(File *file, int spool, unsigned char c,
unsigned char **spoolbuf) {
if (spool > 0) {
g_context->write((const char *)&c, 1);
}
if (spoolbuf) *(*spoolbuf)++ = c;
return c;
}
static int php_iptc_get1(File *file, int spool, unsigned char **spoolbuf) {
int c;
char cc;
c = file->getc();
if (c == EOF) return EOF;
if (spool > 0) {
cc = c;
g_context->write((const char *)&cc, 1);
}
if (spoolbuf) *(*spoolbuf)++ = c;
return c;
}
static int php_iptc_read_remaining(File *file, int spool,
unsigned char **spoolbuf) {
while (php_iptc_get1(file, spool, spoolbuf) != EOF) continue;
return M_EOI;
}
static int php_iptc_skip_variable(File *file, int spool,
unsigned char **spoolbuf) {
unsigned int length;
int c1, c2;
if ((c1 = php_iptc_get1(file, spool, spoolbuf)) == EOF) return M_EOI;
if ((c2 = php_iptc_get1(file, spool, spoolbuf)) == EOF) return M_EOI;
length = (((unsigned char) c1) << 8) + ((unsigned char) c2);
length -= 2;
while (length--) {
if (php_iptc_get1(file, spool, spoolbuf) == EOF) return M_EOI;
}
return 0;
}
static int php_iptc_next_marker(File *file, int spool,
unsigned char **spoolbuf) {
int c;
/* skip unimportant stuff */
c = php_iptc_get1(file, spool, spoolbuf);
if (c == EOF) return M_EOI;
while (c != 0xff) {
if ((c = php_iptc_get1(file, spool, spoolbuf)) == EOF) {
return M_EOI; /* we hit EOF */
}
}
/* get marker byte, swallowing possible padding */
do {
c = php_iptc_get1(file, 0, 0);
if (c == EOF)
return M_EOI; /* we hit EOF */
else if (c == 0xff)
php_iptc_put1(file, spool, (unsigned char)c, spoolbuf);
} while (c == 0xff);
return (unsigned int) c;
}
static const StaticString s_size("size");
Variant f_iptcembed(CStrRef iptcdata, CStrRef jpeg_file_name,
int spool /* = 0 */) {
char psheader[] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0";
unsigned int iptcdata_len = iptcdata.length();
unsigned int marker, inx;
unsigned char *spoolbuf = NULL, *poi = NULL;
bool done = false;
bool written = false;
Variant stream = f_fopen(jpeg_file_name, "rb");
if (same(stream, false)) {
raise_warning("failed to open file: %s", jpeg_file_name.c_str());
return false;
}
if (spool < 2) {
Array stat = f_fstat(stream);
int st_size = stat[s_size];
size_t malloc_size = iptcdata_len + sizeof(psheader) + st_size + 1024 + 1;
poi = spoolbuf = (unsigned char *)IM_MALLOC(malloc_size);
CHECK_ALLOC_R(poi, malloc_size, false);
memset(poi, 0, malloc_size);
}
File *file = Object(stream).getTyped<File>();
if (php_iptc_get1(file, spool, poi?&poi:0) != 0xFF) {
f_fclose(stream);
if (spoolbuf) {
IM_FREE(spoolbuf);
}
return false;
}
if (php_iptc_get1(file, spool, poi?&poi:0) != 0xD8) {
f_fclose(stream);
if (spoolbuf) {
IM_FREE(spoolbuf);
}
return false;
}
while (!done) {
marker = php_iptc_next_marker(file, spool, poi?&poi:0);
if (marker == M_EOI) { /* EOF */
break;
} else if (marker != M_APP13) {
php_iptc_put1(file, spool, (unsigned char)marker, poi?&poi:0);
}
switch (marker) {
case M_APP13:
/* we are going to write a new APP13 marker, so don't
output the old one */
php_iptc_skip_variable(file, 0, 0);
php_iptc_read_remaining(file, spool, poi?&poi:0);
done = true;
break;
case M_APP0:
/* APP0 is in each and every JPEG, so when we hit APP0
we insert our new APP13! */
case M_APP1:
if (written) {
/* don't try to write the data twice */
break;
}
written = true;
php_iptc_skip_variable(file, spool, poi?&poi:0);
if (iptcdata_len & 1) {
iptcdata_len++; /* make the length even */
}
psheader[2] = (iptcdata_len+28)>>8;
psheader[3] = (iptcdata_len+28)&0xff;
for (inx = 0; inx < 28; inx++) {
php_iptc_put1(file, spool, psheader[inx], poi?&poi:0);
}
php_iptc_put1(file, spool, (unsigned char)(iptcdata_len>>8),
poi?&poi:0);
php_iptc_put1(file, spool, (unsigned char)(iptcdata_len&0xff),
poi?&poi:0);
for (inx = 0; inx < iptcdata_len; inx++) {
php_iptc_put1(file, spool, iptcdata.c_str()[inx], poi?&poi:0);
}
break;
case M_SOS:
/* we hit data, no more marker-inserting can be done! */
php_iptc_read_remaining(file, spool, poi?&poi:0);
done = true;
break;
default:
php_iptc_skip_variable(file, spool, poi?&poi:0);
break;
}
}
f_fclose(stream);
if (spool < 2) {
return String((char *)spoolbuf, poi - spoolbuf, AttachString);
}
return true;
}
Variant f_iptcparse(CStrRef iptcblock) {
unsigned int inx = 0, len, tagsfound = 0;
unsigned char *buffer, recnum, dataset, key[16];
unsigned int str_len = iptcblock.length();
Array ret;
buffer = (unsigned char *)iptcblock.c_str();
while (inx < str_len) { /* find 1st tag */
if ((buffer[inx] == 0x1c) &&
((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){
break;
} else {
inx++;
}
}
while (inx < str_len) {
if (buffer[ inx++ ] != 0x1c) {
/* we ran against some data which does not conform to IPTC -
stop parsing! */
break;
}
if ((inx + 4) >= str_len)
break;
dataset = buffer[inx++];
recnum = buffer[inx++];
if (buffer[inx] & (unsigned char) 0x80) { /* long tag */
len = (((long)buffer[inx + 2]) << 24) +
(((long)buffer[inx + 3]) << 16) +
(((long)buffer[inx + 4]) << 8) +
(((long)buffer[inx + 5]));
inx += 6;
} else { /* short tag */
len = (((unsigned short)buffer[inx])<<8) |
(unsigned short)buffer[inx+1];
inx += 2;
}
snprintf((char *)key, sizeof(key), "%d#%03d",
(unsigned int)dataset, (unsigned int)recnum);
if ((len > str_len) || (inx + len) > str_len) {
break;
}
String skey((const char *)key, CopyString);
if (!ret.exists(skey)) {
ret.set(skey, Array());
}
ret.lvalAt(skey).append(String((const char *)(buffer+inx), len,
CopyString));
inx += len;
tagsfound++;
}
if (!tagsfound) {
return false;
}
return ret;
}
// PHP extension exif.c
#define NUM_FORMATS 13
#define TAG_FMT_BYTE 1
#define TAG_FMT_STRING 2
#define TAG_FMT_USHORT 3
#define TAG_FMT_ULONG 4
#define TAG_FMT_URATIONAL 5
#define TAG_FMT_SBYTE 6
#define TAG_FMT_UNDEFINED 7
#define TAG_FMT_SSHORT 8
#define TAG_FMT_SLONG 9
#define TAG_FMT_SRATIONAL 10
#define TAG_FMT_SINGLE 11
#define TAG_FMT_DOUBLE 12
#define TAG_FMT_IFD 13
/* Describes tag values */
#define TAG_GPS_VERSION_ID 0x0000
#define TAG_GPS_LATITUDE_REF 0x0001
#define TAG_GPS_LATITUDE 0x0002
#define TAG_GPS_LONGITUDE_REF 0x0003
#define TAG_GPS_LONGITUDE 0x0004
#define TAG_GPS_ALTITUDE_REF 0x0005
#define TAG_GPS_ALTITUDE 0x0006
#define TAG_GPS_TIME_STAMP 0x0007
#define TAG_GPS_SATELLITES 0x0008
#define TAG_GPS_STATUS 0x0009
#define TAG_GPS_MEASURE_MODE 0x000A
#define TAG_GPS_DOP 0x000B
#define TAG_GPS_SPEED_REF 0x000C
#define TAG_GPS_SPEED 0x000D
#define TAG_GPS_TRACK_REF 0x000E
#define TAG_GPS_TRACK 0x000F
#define TAG_GPS_IMG_DIRECTION_REF 0x0010
#define TAG_GPS_IMG_DIRECTION 0x0011
#define TAG_GPS_MAP_DATUM 0x0012
#define TAG_GPS_DEST_LATITUDE_REF 0x0013
#define TAG_GPS_DEST_LATITUDE 0x0014
#define TAG_GPS_DEST_LONGITUDE_REF 0x0015
#define TAG_GPS_DEST_LONGITUDE 0x0016
#define TAG_GPS_DEST_BEARING_REF 0x0017
#define TAG_GPS_DEST_BEARING 0x0018
#define TAG_GPS_DEST_DISTANCE_REF 0x0019
#define TAG_GPS_DEST_DISTANCE 0x001A
#define TAG_GPS_PROCESSING_METHOD 0x001B
#define TAG_GPS_AREA_INFORMATION 0x001C
#define TAG_GPS_DATE_STAMP 0x001D
#define TAG_GPS_DIFFERENTIAL 0x001E
#define TAG_TIFF_COMMENT 0x00FE /* SHOUDLNT HAPPEN */
#define TAG_NEW_SUBFILE 0x00FE /* New version of subfile tag */
#define TAG_SUBFILE_TYPE 0x00FF /* Old version of subfile tag */
#define TAG_IMAGEWIDTH 0x0100
#define TAG_IMAGEHEIGHT 0x0101
#define TAG_BITS_PER_SAMPLE 0x0102
#define TAG_COMPRESSION 0x0103
#define TAG_PHOTOMETRIC_INTERPRETATION 0x0106
#define TAG_TRESHHOLDING 0x0107
#define TAG_CELL_WIDTH 0x0108
#define TAG_CELL_HEIGHT 0x0109
#define TAG_FILL_ORDER 0x010A
#define TAG_DOCUMENT_NAME 0x010D
#define TAG_IMAGE_DESCRIPTION 0x010E
#define TAG_MAKE 0x010F
#define TAG_MODEL 0x0110
#define TAG_STRIP_OFFSETS 0x0111
#define TAG_ORIENTATION 0x0112
#define TAG_SAMPLES_PER_PIXEL 0x0115
#define TAG_ROWS_PER_STRIP 0x0116
#define TAG_STRIP_BYTE_COUNTS 0x0117
#define TAG_MIN_SAMPPLE_VALUE 0x0118
#define TAG_MAX_SAMPLE_VALUE 0x0119
#define TAG_X_RESOLUTION 0x011A
#define TAG_Y_RESOLUTION 0x011B
#define TAG_PLANAR_CONFIGURATION 0x011C
#define TAG_PAGE_NAME 0x011D
#define TAG_X_POSITION 0x011E
#define TAG_Y_POSITION 0x011F
#define TAG_FREE_OFFSETS 0x0120
#define TAG_FREE_BYTE_COUNTS 0x0121
#define TAG_GRAY_RESPONSE_UNIT 0x0122
#define TAG_GRAY_RESPONSE_CURVE 0x0123
#define TAG_RESOLUTION_UNIT 0x0128
#define TAG_PAGE_NUMBER 0x0129
#define TAG_TRANSFER_FUNCTION 0x012D
#define TAG_SOFTWARE 0x0131
#define TAG_DATETIME 0x0132
#define TAG_ARTIST 0x013B
#define TAG_HOST_COMPUTER 0x013C
#define TAG_PREDICTOR 0x013D
#define TAG_WHITE_POINT 0x013E
#define TAG_PRIMARY_CHROMATICITIES 0x013F
#define TAG_COLOR_MAP 0x0140
#define TAG_HALFTONE_HINTS 0x0141
#define TAG_TILE_WIDTH 0x0142
#define TAG_TILE_LENGTH 0x0143
#define TAG_TILE_OFFSETS 0x0144
#define TAG_TILE_BYTE_COUNTS 0x0145
#define TAG_SUB_IFD 0x014A
#define TAG_INK_SETMPUTER 0x014C
#define TAG_INK_NAMES 0x014D
#define TAG_NUMBER_OF_INKS 0x014E
#define TAG_DOT_RANGE 0x0150
#define TAG_TARGET_PRINTER 0x0151
#define TAG_EXTRA_SAMPLE 0x0152
#define TAG_SAMPLE_FORMAT 0x0153
#define TAG_S_MIN_SAMPLE_VALUE 0x0154
#define TAG_S_MAX_SAMPLE_VALUE 0x0155
#define TAG_TRANSFER_RANGE 0x0156
#define TAG_JPEG_TABLES 0x015B
#define TAG_JPEG_PROC 0x0200
#define TAG_JPEG_INTERCHANGE_FORMAT 0x0201
#define TAG_JPEG_INTERCHANGE_FORMAT_LEN 0x0202
#define TAG_JPEG_RESTART_INTERVAL 0x0203
#define TAG_JPEG_LOSSLESS_PREDICTOR 0x0205
#define TAG_JPEG_POINT_TRANSFORMS 0x0206
#define TAG_JPEG_Q_TABLES 0x0207
#define TAG_JPEG_DC_TABLES 0x0208
#define TAG_JPEG_AC_TABLES 0x0209
#define TAG_YCC_COEFFICIENTS 0x0211
#define TAG_YCC_SUB_SAMPLING 0x0212
#define TAG_YCC_POSITIONING 0x0213
#define TAG_REFERENCE_BLACK_WHITE 0x0214
/* 0x0301 - 0x0302 */
/* 0x0320 */
/* 0x0343 */
/* 0x5001 - 0x501B */
/* 0x5021 - 0x503B */
/* 0x5090 - 0x5091 */
/* 0x5100 - 0x5101 */
/* 0x5110 - 0x5113 */
/* 0x80E3 - 0x80E6 */
/* 0x828d - 0x828F */
#define TAG_COPYRIGHT 0x8298
#define TAG_EXPOSURETIME 0x829A
#define TAG_FNUMBER 0x829D
#define TAG_EXIF_IFD_POINTER 0x8769
#define TAG_ICC_PROFILE 0x8773
#define TAG_EXPOSURE_PROGRAM 0x8822
#define TAG_SPECTRAL_SENSITY 0x8824
#define TAG_GPS_IFD_POINTER 0x8825
#define TAG_ISOSPEED 0x8827
#define TAG_OPTOELECTRIC_CONVERSION_F 0x8828
/* 0x8829 - 0x882b */
#define TAG_EXIFVERSION 0x9000
#define TAG_DATE_TIME_ORIGINAL 0x9003
#define TAG_DATE_TIME_DIGITIZED 0x9004
#define TAG_COMPONENT_CONFIG 0x9101
#define TAG_COMPRESSED_BITS_PER_PIXEL 0x9102
#define TAG_SHUTTERSPEED 0x9201
#define TAG_APERTURE 0x9202
#define TAG_BRIGHTNESS_VALUE 0x9203
#define TAG_EXPOSURE_BIAS_VALUE 0x9204
#define TAG_MAX_APERTURE 0x9205
#define TAG_SUBJECT_DISTANCE 0x9206
#define TAG_METRIC_MODULE 0x9207
#define TAG_LIGHT_SOURCE 0x9208
#define TAG_FLASH 0x9209
#define TAG_FOCAL_LENGTH 0x920A
/* 0x920B - 0x920D */
/* 0x9211 - 0x9216 */
#define TAG_SUBJECT_AREA 0x9214
#define TAG_MAKER_NOTE 0x927C
#define TAG_USERCOMMENT 0x9286
#define TAG_SUB_SEC_TIME 0x9290
#define TAG_SUB_SEC_TIME_ORIGINAL 0x9291
#define TAG_SUB_SEC_TIME_DIGITIZED 0x9292
/* 0x923F */
/* 0x935C */
#define TAG_XP_TITLE 0x9C9B
#define TAG_XP_COMMENTS 0x9C9C
#define TAG_XP_AUTHOR 0x9C9D
#define TAG_XP_KEYWORDS 0x9C9E
#define TAG_XP_SUBJECT 0x9C9F
#define TAG_FLASH_PIX_VERSION 0xA000
#define TAG_COLOR_SPACE 0xA001
#define TAG_COMP_IMAGE_WIDTH 0xA002 /* compressed images only */
#define TAG_COMP_IMAGE_HEIGHT 0xA003
#define TAG_RELATED_SOUND_FILE 0xA004
#define TAG_INTEROP_IFD_POINTER 0xA005 /* IFD pointer */
#define TAG_FLASH_ENERGY 0xA20B
#define TAG_SPATIAL_FREQUENCY_RESPONSE 0xA20C
#define TAG_FOCALPLANE_X_RES 0xA20E
#define TAG_FOCALPLANE_Y_RES 0xA20F
#define TAG_FOCALPLANE_RESOLUTION_UNIT 0xA210
#define TAG_SUBJECT_LOCATION 0xA214
#define TAG_EXPOSURE_INDEX 0xA215
#define TAG_SENSING_METHOD 0xA217
#define TAG_FILE_SOURCE 0xA300
#define TAG_SCENE_TYPE 0xA301
#define TAG_CFA_PATTERN 0xA302
#define TAG_CUSTOM_RENDERED 0xA401
#define TAG_EXPOSURE_MODE 0xA402
#define TAG_WHITE_BALANCE 0xA403
#define TAG_DIGITAL_ZOOM_RATIO 0xA404
#define TAG_FOCAL_LENGTH_IN_35_MM_FILM 0xA405
#define TAG_SCENE_CAPTURE_TYPE 0xA406
#define TAG_GAIN_CONTROL 0xA407
#define TAG_CONTRAST 0xA408
#define TAG_SATURATION 0xA409
#define TAG_SHARPNESS 0xA40A
#define TAG_DEVICE_SETTING_DESCRIPTION 0xA40B
#define TAG_SUBJECT_DISTANCE_RANGE 0xA40C
#define TAG_IMAGE_UNIQUE_ID 0xA420
/* Olympus specific tags */
#define TAG_OLYMPUS_SPECIALMODE 0x0200
#define TAG_OLYMPUS_JPEGQUAL 0x0201
#define TAG_OLYMPUS_MACRO 0x0202
#define TAG_OLYMPUS_DIGIZOOM 0x0204
#define TAG_OLYMPUS_SOFTWARERELEASE 0x0207
#define TAG_OLYMPUS_PICTINFO 0x0208
#define TAG_OLYMPUS_CAMERAID 0x0209
/* end Olympus specific tags */
/* Internal */
#define TAG_NONE -1 /* note that -1 <> 0xFFFF */
#define TAG_COMPUTED_VALUE -2
#define TAG_END_OF_LIST 0xFFFD
/* Values for TAG_PHOTOMETRIC_INTERPRETATION */
#define PMI_BLACK_IS_ZERO 0
#define PMI_WHITE_IS_ZERO 1
#define PMI_RGB 2
#define PMI_PALETTE_COLOR 3
#define PMI_TRANSPARENCY_MASK 4
#define PMI_SEPARATED 5
#define PMI_YCBCR 6
#define PMI_CIELAB 8
typedef const struct {
unsigned short Tag;
char *Desc;
} tag_info_type;
typedef tag_info_type tag_info_array[];
typedef tag_info_type *tag_table_type;
#define TAG_TABLE_END \
{((unsigned short)TAG_NONE), "No tag value"},\
{((unsigned short)TAG_COMPUTED_VALUE), "Computed value"},\
{TAG_END_OF_LIST, ""} /* Important for exif_get_tagname()
IF value != "" function result is != false */
static const tag_info_array tag_table_IFD = {
{ 0x000B, "ACDComment"},
{ 0x00FE, "NewSubFile"}, /* better name it 'ImageType' ? */
{ 0x00FF, "SubFile"},
{ 0x0100, "ImageWidth"},
{ 0x0101, "ImageLength"},
{ 0x0102, "BitsPerSample"},
{ 0x0103, "Compression"},
{ 0x0106, "PhotometricInterpretation"},
{ 0x010A, "FillOrder"},
{ 0x010D, "DocumentName"},
{ 0x010E, "ImageDescription"},
{ 0x010F, "Make"},
{ 0x0110, "Model"},
{ 0x0111, "StripOffsets"},
{ 0x0112, "Orientation"},
{ 0x0115, "SamplesPerPixel"},
{ 0x0116, "RowsPerStrip"},
{ 0x0117, "StripByteCounts"},
{ 0x0118, "MinSampleValue"},
{ 0x0119, "MaxSampleValue"},
{ 0x011A, "XResolution"},
{ 0x011B, "YResolution"},
{ 0x011C, "PlanarConfiguration"},
{ 0x011D, "PageName"},
{ 0x011E, "XPosition"},
{ 0x011F, "YPosition"},
{ 0x0120, "FreeOffsets"},
{ 0x0121, "FreeByteCounts"},
{ 0x0122, "GrayResponseUnit"},
{ 0x0123, "GrayResponseCurve"},
{ 0x0124, "T4Options"},
{ 0x0125, "T6Options"},
{ 0x0128, "ResolutionUnit"},
{ 0x0129, "PageNumber"},
{ 0x012D, "TransferFunction"},
{ 0x0131, "Software"},
{ 0x0132, "DateTime"},
{ 0x013B, "Artist"},
{ 0x013C, "HostComputer"},
{ 0x013D, "Predictor"},
{ 0x013E, "WhitePoint"},
{ 0x013F, "PrimaryChromaticities"},
{ 0x0140, "ColorMap"},
{ 0x0141, "HalfToneHints"},
{ 0x0142, "TileWidth"},
{ 0x0143, "TileLength"},
{ 0x0144, "TileOffsets"},
{ 0x0145, "TileByteCounts"},
{ 0x014A, "SubIFD"},
{ 0x014C, "InkSet"},
{ 0x014D, "InkNames"},
{ 0x014E, "NumberOfInks"},
{ 0x0150, "DotRange"},
{ 0x0151, "TargetPrinter"},
{ 0x0152, "ExtraSample"},
{ 0x0153, "SampleFormat"},
{ 0x0154, "SMinSampleValue"},
{ 0x0155, "SMaxSampleValue"},
{ 0x0156, "TransferRange"},
{ 0x0157, "ClipPath"},
{ 0x0158, "XClipPathUnits"},
{ 0x0159, "YClipPathUnits"},
{ 0x015A, "Indexed"},
{ 0x015B, "JPEGTables"},
{ 0x015F, "OPIProxy"},
{ 0x0200, "JPEGProc"},
{ 0x0201, "JPEGInterchangeFormat"},
{ 0x0202, "JPEGInterchangeFormatLength"},
{ 0x0203, "JPEGRestartInterval"},
{ 0x0205, "JPEGLosslessPredictors"},
{ 0x0206, "JPEGPointTransforms"},
{ 0x0207, "JPEGQTables"},
{ 0x0208, "JPEGDCTables"},
{ 0x0209, "JPEGACTables"},
{ 0x0211, "YCbCrCoefficients"},
{ 0x0212, "YCbCrSubSampling"},
{ 0x0213, "YCbCrPositioning"},
{ 0x0214, "ReferenceBlackWhite"},
{ 0x02BC, "ExtensibleMetadataPlatform"},
/* XAP: Extensible Authoring Publishing, obsoleted by XMP:
Extensible Metadata Platform */
{ 0x0301, "Gamma"},
{ 0x0302, "ICCProfileDescriptor"},
{ 0x0303, "SRGBRenderingIntent"},
{ 0x0320, "ImageTitle"},
{ 0x5001, "ResolutionXUnit"},
{ 0x5002, "ResolutionYUnit"},
{ 0x5003, "ResolutionXLengthUnit"},
{ 0x5004, "ResolutionYLengthUnit"},
{ 0x5005, "PrintFlags"},
{ 0x5006, "PrintFlagsVersion"},
{ 0x5007, "PrintFlagsCrop"},
{ 0x5008, "PrintFlagsBleedWidth"},
{ 0x5009, "PrintFlagsBleedWidthScale"},
{ 0x500A, "HalftoneLPI"},
{ 0x500B, "HalftoneLPIUnit"},
{ 0x500C, "HalftoneDegree"},
{ 0x500D, "HalftoneShape"},
{ 0x500E, "HalftoneMisc"},
{ 0x500F, "HalftoneScreen"},
{ 0x5010, "JPEGQuality"},
{ 0x5011, "GridSize"},
{ 0x5012, "ThumbnailFormat"},
{ 0x5013, "ThumbnailWidth"},
{ 0x5014, "ThumbnailHeight"},
{ 0x5015, "ThumbnailColorDepth"},
{ 0x5016, "ThumbnailPlanes"},
{ 0x5017, "ThumbnailRawBytes"},
{ 0x5018, "ThumbnailSize"},
{ 0x5019, "ThumbnailCompressedSize"},
{ 0x501A, "ColorTransferFunction"},
{ 0x501B, "ThumbnailData"},
{ 0x5020, "ThumbnailImageWidth"},
{ 0x5021, "ThumbnailImageHeight"},
{ 0x5022, "ThumbnailBitsPerSample"},
{ 0x5023, "ThumbnailCompression"},
{ 0x5024, "ThumbnailPhotometricInterp"},
{ 0x5025, "ThumbnailImageDescription"},
{ 0x5026, "ThumbnailEquipMake"},
{ 0x5027, "ThumbnailEquipModel"},
{ 0x5028, "ThumbnailStripOffsets"},
{ 0x5029, "ThumbnailOrientation"},
{ 0x502A, "ThumbnailSamplesPerPixel"},
{ 0x502B, "ThumbnailRowsPerStrip"},
{ 0x502C, "ThumbnailStripBytesCount"},
{ 0x502D, "ThumbnailResolutionX"},
{ 0x502E, "ThumbnailResolutionY"},
{ 0x502F, "ThumbnailPlanarConfig"},
{ 0x5030, "ThumbnailResolutionUnit"},
{ 0x5031, "ThumbnailTransferFunction"},
{ 0x5032, "ThumbnailSoftwareUsed"},
{ 0x5033, "ThumbnailDateTime"},
{ 0x5034, "ThumbnailArtist"},
{ 0x5035, "ThumbnailWhitePoint"},
{ 0x5036, "ThumbnailPrimaryChromaticities"},
{ 0x5037, "ThumbnailYCbCrCoefficients"},
{ 0x5038, "ThumbnailYCbCrSubsampling"},
{ 0x5039, "ThumbnailYCbCrPositioning"},
{ 0x503A, "ThumbnailRefBlackWhite"},
{ 0x503B, "ThumbnailCopyRight"},
{ 0x5090, "LuminanceTable"},
{ 0x5091, "ChrominanceTable"},
{ 0x5100, "FrameDelay"},
{ 0x5101, "LoopCount"},
{ 0x5110, "PixelUnit"},
{ 0x5111, "PixelPerUnitX"},
{ 0x5112, "PixelPerUnitY"},
{ 0x5113, "PaletteHistogram"},
{ 0x1000, "RelatedImageFileFormat"},
{ 0x800D, "ImageID"},
{ 0x80E3, "Matteing"}, /* obsoleted by ExtraSamples */
{ 0x80E4, "DataType"}, /* obsoleted by SampleFormat */
{ 0x80E5, "ImageDepth"},
{ 0x80E6, "TileDepth"},
{ 0x828D, "CFARepeatPatternDim"},
{ 0x828E, "CFAPattern"},
{ 0x828F, "BatteryLevel"},
{ 0x8298, "Copyright"},
{ 0x829A, "ExposureTime"},
{ 0x829D, "FNumber"},
{ 0x83BB, "IPTC/NAA"},
{ 0x84E3, "IT8RasterPadding"},
{ 0x84E5, "IT8ColorTable"},
{ 0x8649, "ImageResourceInformation"}, /* PhotoShop */
{ 0x8769, "Exif_IFD_Pointer"},
{ 0x8773, "ICC_Profile"},
{ 0x8822, "ExposureProgram"},
{ 0x8824, "SpectralSensity"},
{ 0x8828, "OECF"},
{ 0x8825, "GPS_IFD_Pointer"},
{ 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"},
{ 0x920B, "FlashEnergy"}, /* 0xA20B in JPEG */
{ 0x920C, "SpatialFrequencyResponse"}, /* 0xA20C - - */
{ 0x920D, "Noise"},
{ 0x920E, "FocalPlaneXResolution"}, /* 0xA20E - - */
{ 0x920F, "FocalPlaneYResolution"}, /* 0xA20F - - */
{ 0x9210, "FocalPlaneResolutionUnit"}, /* 0xA210 - - */
{ 0x9211, "ImageNumber"},
{ 0x9212, "SecurityClassification"},
{ 0x9213, "ImageHistory"},
{ 0x9214, "SubjectLocation"}, /* 0xA214 - - */
{ 0x9215, "ExposureIndex"}, /* 0xA215 - - */
{ 0x9216, "TIFF/EPStandardID"},
{ 0x9217, "SensingMethod"}, /* 0xA217 - - */
{ 0x923F, "StoNits"},
{ 0x927C, "MakerNote"},
{ 0x9286, "UserComment"},
{ 0x9290, "SubSecTime"},
{ 0x9291, "SubSecTimeOriginal"},
{ 0x9292, "SubSecTimeDigitized"},
{ 0x935C, "ImageSourceData"},
/* "Adobe Photoshop Document Data Block": 8BIM... */
{ 0x9c9b, "Title" }, /* Win XP specific, Unicode */
{ 0x9c9c, "Comments" }, /* Win XP specific, Unicode */
{ 0x9c9d, "Author" }, /* Win XP specific, Unicode */
{ 0x9c9e, "Keywords" }, /* Win XP specific, Unicode */
{ 0x9c9f, "Subject" }, /* Win XP specific, Unicode,
not to be confused with SubjectDistance
and SubjectLocation */
{ 0xA000, "FlashPixVersion"},
{ 0xA001, "ColorSpace"},
{ 0xA002, "ExifImageWidth"},
{ 0xA003, "ExifImageLength"},
{ 0xA004, "RelatedSoundFile"},
{ 0xA005, "InteroperabilityOffset"},
{ 0xA20B, "FlashEnergy"}, /* 0x920B in TIFF/EP */
{ 0xA20C, "SpatialFrequencyResponse"}, /* 0x920C - - */
{ 0xA20D, "Noise"},
{ 0xA20E, "FocalPlaneXResolution"}, /* 0x920E - - */
{ 0xA20F, "FocalPlaneYResolution"}, /* 0x920F - - */
{ 0xA210, "FocalPlaneResolutionUnit"}, /* 0x9210 - - */
{ 0xA211, "ImageNumber"},
{ 0xA212, "SecurityClassification"},
{ 0xA213, "ImageHistory"},
{ 0xA214, "SubjectLocation"}, /* 0x9214 - - */
{ 0xA215, "ExposureIndex"}, /* 0x9215 - - */
{ 0xA216, "TIFF/EPStandardID"},
{ 0xA217, "SensingMethod"}, /* 0x9217 - - */
{ 0xA300, "FileSource"},
{ 0xA301, "SceneType"},
{ 0xA302, "CFAPattern"},
{ 0xA401, "CustomRendered"},
{ 0xA402, "ExposureMode"},
{ 0xA403, "WhiteBalance"},
{ 0xA404, "DigitalZoomRatio"},
{ 0xA405, "FocalLengthIn35mmFilm"},
{ 0xA406, "SceneCaptureType"},
{ 0xA407, "GainControl"},
{ 0xA408, "Contrast"},
{ 0xA409, "Saturation"},
{ 0xA40A, "Sharpness"},
{ 0xA40B, "DeviceSettingDescription"},
{ 0xA40C, "SubjectDistanceRange"},
{ 0xA420, "ImageUniqueID"},
TAG_TABLE_END
};
static const tag_info_array tag_table_GPS = {
{ 0x0000, "GPSVersion"},
{ 0x0001, "GPSLatitudeRef"},
{ 0x0002, "GPSLatitude"},
{ 0x0003, "GPSLongitudeRef"},
{ 0x0004, "GPSLongitude"},
{ 0x0005, "GPSAltitudeRef"},
{ 0x0006, "GPSAltitude"},
{ 0x0007, "GPSTimeStamp"},
{ 0x0008, "GPSSatellites"},
{ 0x0009, "GPSStatus"},
{ 0x000A, "GPSMeasureMode"},
{ 0x000B, "GPSDOP"},
{ 0x000C, "GPSSpeedRef"},
{ 0x000D, "GPSSpeed"},
{ 0x000E, "GPSTrackRef"},
{ 0x000F, "GPSTrack"},
{ 0x0010, "GPSImgDirectionRef"},
{ 0x0011, "GPSImgDirection"},
{ 0x0012, "GPSMapDatum"},
{ 0x0013, "GPSDestLatitudeRef"},
{ 0x0014, "GPSDestLatitude"},
{ 0x0015, "GPSDestLongitudeRef"},
{ 0x0016, "GPSDestLongitude"},
{ 0x0017, "GPSDestBearingRef"},
{ 0x0018, "GPSDestBearing"},
{ 0x0019, "GPSDestDistanceRef"},
{ 0x001A, "GPSDestDistance"},
{ 0x001B, "GPSProcessingMode"},
{ 0x001C, "GPSAreaInformation"},
{ 0x001D, "GPSDateStamp"},
{ 0x001E, "GPSDifferential"},
TAG_TABLE_END
};
static const tag_info_array tag_table_IOP = {
{ 0x0001, "InterOperabilityIndex"}, /* should be 'R98' or 'THM' */
{ 0x0002, "InterOperabilityVersion"},
{ 0x1000, "RelatedFileFormat"},
{ 0x1001, "RelatedImageWidth"},
{ 0x1002, "RelatedImageHeight"},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_CANON = {
{ 0x0001, "ModeArray"}, /* guess */
{ 0x0004, "ImageInfo"}, /* guess */
{ 0x0006, "ImageType"},
{ 0x0007, "FirmwareVersion"},
{ 0x0008, "ImageNumber"},
{ 0x0009, "OwnerName"},
{ 0x000C, "Camera"},
{ 0x000F, "CustomFunctions"},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_CASIO = {
{ 0x0001, "RecordingMode"},
{ 0x0002, "Quality"},
{ 0x0003, "FocusingMode"},
{ 0x0004, "FlashMode"},
{ 0x0005, "FlashIntensity"},
{ 0x0006, "ObjectDistance"},
{ 0x0007, "WhiteBalance"},
{ 0x000A, "DigitalZoom"},
{ 0x000B, "Sharpness"},
{ 0x000C, "Contrast"},
{ 0x000D, "Saturation"},
{ 0x0014, "CCDSensitivity"},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_FUJI = {
{ 0x0000, "Version"},
{ 0x1000, "Quality"},
{ 0x1001, "Sharpness"},
{ 0x1002, "WhiteBalance"},
{ 0x1003, "Color"},
{ 0x1004, "Tone"},
{ 0x1010, "FlashMode"},
{ 0x1011, "FlashStrength"},
{ 0x1020, "Macro"},
{ 0x1021, "FocusMode"},
{ 0x1030, "SlowSync"},
{ 0x1031, "PictureMode"},
{ 0x1100, "ContTake"},
{ 0x1300, "BlurWarning"},
{ 0x1301, "FocusWarning"},
{ 0x1302, "AEWarning "},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_NIKON = {
{ 0x0003, "Quality"},
{ 0x0004, "ColorMode"},
{ 0x0005, "ImageAdjustment"},
{ 0x0006, "CCDSensitivity"},
{ 0x0007, "WhiteBalance"},
{ 0x0008, "Focus"},
{ 0x000a, "DigitalZoom"},
{ 0x000b, "Converter"},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_NIKON_990 = {
{ 0x0001, "Version"},
{ 0x0002, "ISOSetting"},
{ 0x0003, "ColorMode"},
{ 0x0004, "Quality"},
{ 0x0005, "WhiteBalance"},
{ 0x0006, "ImageSharpening"},
{ 0x0007, "FocusMode"},
{ 0x0008, "FlashSetting"},
{ 0x000F, "ISOSelection"},
{ 0x0080, "ImageAdjustment"},
{ 0x0082, "AuxiliaryLens"},
{ 0x0085, "ManualFocusDistance"},
{ 0x0086, "DigitalZoom"},
{ 0x0088, "AFFocusPosition"},
{ 0x0010, "DataDump"},
TAG_TABLE_END
};
static const tag_info_array tag_table_VND_OLYMPUS = {
{ 0x0200, "SpecialMode"},
{ 0x0201, "JPEGQuality"},
{ 0x0202, "Macro"},
{ 0x0204, "DigitalZoom"},
{ 0x0207, "SoftwareRelease"},
{ 0x0208, "PictureInfo"},
{ 0x0209, "CameraId"},
{ 0x0F00, "DataDump"},
TAG_TABLE_END
};
typedef enum mn_byte_order_t {
MN_ORDER_INTEL = 0,
MN_ORDER_MOTOROLA = 1,
MN_ORDER_NORMAL
} mn_byte_order_t;
typedef enum mn_offset_mode_t {
MN_OFFSET_NORMAL,
MN_OFFSET_MAKER,
MN_OFFSET_GUESS
} mn_offset_mode_t;
typedef struct {
tag_table_type tag_table;
char *make;
char *model;
char *id_string;
int id_string_len;
int offset;
mn_byte_order_t byte_order;
mn_offset_mode_t offset_mode;
} maker_note_type;
static const maker_note_type maker_note_array[] = {
{ tag_table_VND_CANON, "Canon", NULL, NULL,
0, 0, MN_ORDER_INTEL, MN_OFFSET_GUESS},
/* { tag_table_VND_CANON, "Canon", NULL, NULL,
0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL},*/
{ tag_table_VND_CASIO, "CASIO", NULL, NULL,
0, 0, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL},
{ tag_table_VND_FUJI, "FUJIFILM", NULL, "FUJIFILM\x0C\x00\x00\x00",
12, 12, MN_ORDER_INTEL, MN_OFFSET_MAKER},
{ tag_table_VND_NIKON, "NIKON", NULL, "Nikon\x00\x01\x00",
8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL},
{ tag_table_VND_NIKON_990, "NIKON", NULL, NULL,
0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL},
{ tag_table_VND_OLYMPUS, "OLYMPUS OPTICAL CO.,LTD",
NULL, "OLYMP\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL},
};
/* Get headername for tag_num or NULL if not defined */
static char * exif_get_tagname(int tag_num, char *ret, int len,
tag_table_type tag_table) {
int i, t;
char tmp[32];
for (i = 0; (t = tag_table[i].Tag) != TAG_END_OF_LIST; i++) {
if (t == tag_num) {
if (ret && len) {
string_copy(ret, tag_table[i].Desc, abs(len));
if (len < 0) {
memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1);
ret[-len - 1] = '\0';
}
return ret;
}
return tag_table[i].Desc;
}
}
if (ret && len) {
snprintf(tmp, sizeof(tmp), "UndefinedTag:0x%04X", tag_num);
string_copy(ret, tmp, abs(len));
if (len < 0) {
memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1);
ret[-len - 1] = '\0';
}
return ret;
}
return "";
}
#define MAX_IFD_NESTING_LEVEL 100
#ifndef WORD
#define WORD unsigned short
#endif
#ifndef DWORD
#define DWORD unsigned int
#endif
typedef struct {
int num;
int den;
} signed_rational;
typedef struct {
unsigned int num;
unsigned int den;
} unsigned_rational;
typedef union _image_info_value {
char *s;
unsigned u;
int i;
float f;
double d;
signed_rational sr;
unsigned_rational ur;
union _image_info_value *list;
} image_info_value;
typedef struct {
WORD tag;
WORD format;
DWORD length;
DWORD dummy; /* value ptr of tiff directory entry */
char *name;
image_info_value value;
} image_info_data;
typedef struct {
int count;
image_info_data *list;
} image_info_list;
#define SECTION_FILE 0
#define SECTION_COMPUTED 1
#define SECTION_ANY_TAG 2
#define SECTION_IFD0 3
#define SECTION_THUMBNAIL 4
#define SECTION_COMMENT 5
#define SECTION_APP0 6
#define SECTION_EXIF 7
#define SECTION_FPIX 8
#define SECTION_GPS 9
#define SECTION_INTEROP 10
#define SECTION_APP12 11
#define SECTION_WINXP 12
#define SECTION_MAKERNOTE 13
#define SECTION_COUNT 14
#define FOUND_FILE (1<<SECTION_FILE)
#define FOUND_COMPUTED (1<<SECTION_COMPUTED)
#define FOUND_ANY_TAG (1<<SECTION_ANY_TAG)
#define FOUND_IFD0 (1<<SECTION_IFD0)
#define FOUND_THUMBNAIL (1<<SECTION_THUMBNAIL)
#define FOUND_COMMENT (1<<SECTION_COMMENT)
#define FOUND_APP0 (1<<SECTION_APP0)
#define FOUND_EXIF (1<<SECTION_EXIF)
#define FOUND_FPIX (1<<SECTION_FPIX)
#define FOUND_GPS (1<<SECTION_GPS)
#define FOUND_INTEROP (1<<SECTION_INTEROP)
#define FOUND_APP12 (1<<SECTION_APP12)
#define FOUND_WINXP (1<<SECTION_WINXP)
#define FOUND_MAKERNOTE (1<<SECTION_MAKERNOTE)
static const StaticString s_FILE("FILE");
static const StaticString s_COMPUTED("COMPUTED");
static const StaticString s_ANY_TAG("ANY_TAG");
static const StaticString s_IFD0("IFD0");
static const StaticString s_THUMBNAIL("THUMBNAIL");
static const StaticString s_COMMENT("COMMENT");
static const StaticString s_APP0("APP0");
static const StaticString s_EXIF("EXIF");
static const StaticString s_FPIX("FPIX");
static const StaticString s_GPS("GPS");
static const StaticString s_INTEROP("INTEROP");
static const StaticString s_APP12("APP12");
static const StaticString s_WINXP("WINXP");
static const StaticString s_MAKERNOTE("MAKERNOTE");
static String exif_get_sectionname(int section) {
switch(section) {
case SECTION_FILE: return s_FILE;
case SECTION_COMPUTED: return s_COMPUTED;
case SECTION_ANY_TAG: return s_ANY_TAG;
case SECTION_IFD0: return s_IFD0;
case SECTION_THUMBNAIL: return s_THUMBNAIL;
case SECTION_COMMENT: return s_COMMENT;
case SECTION_APP0: return s_APP0;
case SECTION_EXIF: return s_EXIF;
case SECTION_FPIX: return s_FPIX;
case SECTION_GPS: return s_GPS;
case SECTION_INTEROP: return s_INTEROP;
case SECTION_APP12: return s_APP12;
case SECTION_WINXP: return s_WINXP;
case SECTION_MAKERNOTE: return s_MAKERNOTE;
}
return empty_string;
}
static tag_table_type exif_get_tag_table(int section) {
switch(section) {
case SECTION_FILE: return &tag_table_IFD[0];
case SECTION_COMPUTED: return &tag_table_IFD[0];
case SECTION_ANY_TAG: return &tag_table_IFD[0];
case SECTION_IFD0: return &tag_table_IFD[0];
case SECTION_THUMBNAIL: return &tag_table_IFD[0];
case SECTION_COMMENT: return &tag_table_IFD[0];
case SECTION_APP0: return &tag_table_IFD[0];
case SECTION_EXIF: return &tag_table_IFD[0];
case SECTION_FPIX: return &tag_table_IFD[0];
case SECTION_GPS: return &tag_table_GPS[0];
case SECTION_INTEROP: return &tag_table_IOP[0];
case SECTION_APP12: return &tag_table_IFD[0];
case SECTION_WINXP: return &tag_table_IFD[0];
}
return &tag_table_IFD[0];
}
/* Return list of sectionnames specified by sectionlist.
Return value must be freed */
static char *exif_get_sectionlist(int sectionlist) {
int i, len, ml = 0;
char *sections;
for(i=0; i<SECTION_COUNT; i++) {
ml += exif_get_sectionname(i).size() + 2;
}
sections = (char *)IM_MALLOC(ml + 1);
CHECK_ALLOC_R(sections, ml + 1, NULL);
sections[0] = '\0';
len = 0;
for(i=0; i<SECTION_COUNT; i++) {
if (sectionlist&(1<<i)) {
snprintf(sections+len, ml-len, "%s, ", exif_get_sectionname(i).c_str());
len = strlen(sections);
}
}
if (len>2) {
sections[len-2] = '\0';
}
return sections;
}
/*
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 {
int type;
size_t size;
uchar *data;
} file_section;
typedef struct {
int count;
file_section *list;
} file_section_list;
typedef struct {
image_filetype filetype;
size_t width, height;
size_t size;
size_t offset;
char *data;
} thumbnail_data;
typedef struct {
char *value;
size_t size;
int tag;
} xp_field_type;
typedef struct {
int count;
xp_field_type *list;
} xp_field_list;
/* This structure is used to store a section of a Jpeg file. */
typedef struct {
File *infile;
String FileName;
time_t FileDateTime;
size_t FileSize;
image_filetype FileType;
int Height, Width;
int IsColor;
char *make;
char *model;
float ApertureFNumber;
float ExposureTime;
double FocalplaneUnits;
float CCDWidth;
double FocalplaneXRes;
size_t ExifImageWidth;
float FocalLength;
float Distance;
int motorola_intel; /* 1 Motorola; 0 Intel */
char *UserComment;
int UserCommentLength;
char *UserCommentEncoding;
char *encode_unicode;
char *decode_unicode_be;
char *decode_unicode_le;
char *encode_jis;
char *decode_jis_be;
char *decode_jis_le;
/* EXIF standard defines Copyright as
"<Photographer> [ '\0' <Editor> ] ['\0']" */
char *Copyright;
char *CopyrightPhotographer;
char *CopyrightEditor;
xp_field_list xp_fields;
thumbnail_data Thumbnail;
/* other */
int sections_found; /* FOUND_<marker> */
image_info_list info_list[SECTION_COUNT];
/* for parsing */
bool read_thumbnail;
bool read_all;
int ifd_nesting_level;
/* internal */
file_section_list file;
} image_info_type;
typedef struct {
int bits_per_sample;
size_t width;
size_t height;
int num_components;
} jpeg_sof_info;
/* forward declarations */
static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo,
char *dir_start, char *offset_base,
char *end,
size_t IFDlength, size_t displacement,
int section_index);
static int exif_process_IFD_TAG(image_info_type *ImageInfo, char *dir_entry,
char *offset_base, char *end, size_t IFDlength,
size_t displacement, int section_index,
int ReadNextIFD, tag_table_type tag_table);
/*
Add a file_section to image_info
returns the used block or -1. if size>0 and data == NULL buffer of
size is allocated
*/
static int exif_file_sections_add(image_info_type *ImageInfo, int type,
size_t size, uchar *data) {
file_section *tmp;
int count = ImageInfo->file.count;
size_t realloc_size = (count+1) * sizeof(file_section);
tmp = (file_section *)IM_REALLOC(ImageInfo->file.list, realloc_size);
CHECK_ALLOC_R(tmp, realloc_size, -1);
ImageInfo->file.list = tmp;
ImageInfo->file.list[count].type = 0xFFFF;
ImageInfo->file.list[count].data = NULL;
ImageInfo->file.list[count].size = 0;
ImageInfo->file.count = count+1;
if (!size) {
data = NULL;
} else if (data == NULL) {
data = (uchar *)IM_MALLOC(size);
if (data == NULL) IM_FREE(tmp);
CHECK_ALLOC_R(data, size, -1);
}
ImageInfo->file.list[count].type = type;
ImageInfo->file.list[count].data = data;
ImageInfo->file.list[count].size = size;
return count;
}
/* get length of string if buffer if less than buffer size or buffer size */
static size_t php_strnlen(char* str, size_t maxlen) {
size_t len = 0;
if (str && maxlen && *str) {
do {
len++;
} while (--maxlen && *(++str));
}
return len;
}
/* Add a value to image_info */
static void exif_iif_add_value(image_info_type *image_info, int section_index,
char *name, int tag, int format, int length,
void* value, int motorola_intel) {
size_t idex;
void *vptr;
image_info_value *info_value;
image_info_data *info_data;
image_info_data *list;
if (length < 0) {
return;
}
size_t realloc_size = (image_info->info_list[section_index].count+1) *
sizeof(image_info_data);
list = (image_info_data*)
IM_REALLOC(image_info->info_list[section_index].list, realloc_size);
CHECK_ALLOC(list, realloc_size);
image_info->info_list[section_index].list = list;
info_data = &image_info->info_list[section_index].
list[image_info->info_list[section_index].count];
memset(info_data, 0, sizeof(image_info_data));
info_data->tag = tag;
info_data->format = format;
info_data->length = length;
PHP_STRDUP(info_data->name, name);
info_value = &info_data->value;
switch (format) {
case TAG_FMT_STRING:
if (value) {
length = php_strnlen((char*)value, length);
// TODO
// if (PG(magic_quotes_runtime)) {
// info_value->s = php_addslashes(value, length, &length, 0 TSRMLS_CC);
// } else {
PHP_STRNDUP(info_value->s, (const char *)value, length);
// }
info_data->length = (info_value->s ? length : 0);
} else {
info_data->length = 0;
PHP_STRDUP(info_value->s, "");
}
break;
default:
/* Standard says more types possible but skip them...
* but allow users to handle data if they know how to
* So not return but use type UNDEFINED
* return;
*/
info_data->tag = TAG_FMT_UNDEFINED;/* otherwise not freed from memory */
case TAG_FMT_SBYTE:
case TAG_FMT_BYTE:
/* in contrast to strings bytes do not need to allocate buffer for
NULL if length==0 */
if (!length)
break;
case TAG_FMT_UNDEFINED:
if (value) {
/* do not recompute length here */
// TODO
// if (PG(magic_quotes_runtime)) {
// info_value->s = php_addslashes(value, length, &length, 0 TSRMLS_CC);
// } else {
PHP_STRNDUP(info_value->s, (const char *)value, length);
// }
info_data->length = (info_value->s ? length : 0);
} else {
info_data->length = 0;
PHP_STRDUP(info_value->s, "");
}
break;
case TAG_FMT_USHORT:
case TAG_FMT_ULONG:
case TAG_FMT_URATIONAL:
case TAG_FMT_SSHORT:
case TAG_FMT_SLONG:
case TAG_FMT_SRATIONAL:
case TAG_FMT_SINGLE:
case TAG_FMT_DOUBLE:
if (length==0) {
break;
} else if (length>1) {
info_value->list =
(image_info_value*)IM_CALLOC(length, sizeof(image_info_value));
CHECK_ALLOC(info_value->list, sizeof(image_info_value));
} else {
info_value = &info_data->value;
}
for (idex=0,vptr=value; idex<(size_t)length;
idex++,vptr=(char *) vptr + get_php_tiff_bytes_per_format(format)) {
if (length>1) {
info_value = &info_data->value.list[idex];
}
switch (format) {
case TAG_FMT_USHORT:
info_value->u = php_ifd_get16u(vptr, motorola_intel);
break;
case TAG_FMT_ULONG:
info_value->u = php_ifd_get32u(vptr, motorola_intel);
break;
case TAG_FMT_URATIONAL:
info_value->ur.num = php_ifd_get32u(vptr, motorola_intel);
info_value->ur.den = php_ifd_get32u(4+(char *)vptr, motorola_intel);
break;
case TAG_FMT_SSHORT:
info_value->i = php_ifd_get16s(vptr, motorola_intel);
break;
case TAG_FMT_SLONG:
info_value->i = php_ifd_get32s(vptr, motorola_intel);
break;
case TAG_FMT_SRATIONAL:
info_value->sr.num = php_ifd_get32u(vptr, motorola_intel);
info_value->sr.den = php_ifd_get32u(4+(char *)vptr, motorola_intel);
break;
case TAG_FMT_SINGLE:
info_value->f = *(float *)value;
case TAG_FMT_DOUBLE:
info_value->d = *(double *)value;
break;
}
}
}
image_info->sections_found |= 1<<section_index;
image_info->info_list[section_index].count++;
}
/* Add a tag from IFD to image_info */
static void exif_iif_add_tag(image_info_type *image_info, int section_index,
char *name, int tag, int format,
size_t length, void* value) {
exif_iif_add_value(image_info, section_index, name, tag, format,
(int)length, value, image_info->motorola_intel);
}
/* Evaluate number, be it int, rational, or float from directory. */
static double exif_convert_any_format(void *value, int format,
int motorola_intel) {
int s_den;
unsigned u_den;
switch(format) {
case TAG_FMT_SBYTE:
return *(signed char *)value;
case TAG_FMT_BYTE:
return *(uchar *)value;
case TAG_FMT_USHORT:
return php_ifd_get16u(value, motorola_intel);
case TAG_FMT_ULONG:
return php_ifd_get32u(value, motorola_intel);
case TAG_FMT_URATIONAL:
u_den = php_ifd_get32u(4+(char *)value, motorola_intel);
if (u_den == 0) {
return 0;
} else {
return (double)php_ifd_get32u(value, motorola_intel) / u_den;
}
case TAG_FMT_SRATIONAL:
s_den = php_ifd_get32s(4+(char *)value, motorola_intel);
if (s_den == 0) {
return 0;
} else {
return (double)php_ifd_get32s(value, motorola_intel) / s_den;
}
case TAG_FMT_SSHORT:
return (signed short)php_ifd_get16u(value, motorola_intel);
case TAG_FMT_SLONG:
return php_ifd_get32s(value, motorola_intel);
/* Not sure if this is correct (never seen float used in Exif format) */
case TAG_FMT_SINGLE:
return (double)*(float *)value;
case TAG_FMT_DOUBLE:
return *(double *)value;
}
return 0;
}
/* Evaluate number, be it int, rational, or float from directory. */
static size_t exif_convert_any_to_int(void *value, int format,
int motorola_intel) {
int s_den;
unsigned u_den;
switch(format) {
case TAG_FMT_SBYTE:
return *(signed char *)value;
case TAG_FMT_BYTE:
return *(uchar *)value;
case TAG_FMT_USHORT:
return php_ifd_get16u(value, motorola_intel);
case TAG_FMT_ULONG:
return php_ifd_get32u(value, motorola_intel);
case TAG_FMT_URATIONAL:
u_den = php_ifd_get32u(4+(char *)value, motorola_intel);
if (u_den == 0) {
return 0;
} else {
return php_ifd_get32u(value, motorola_intel) / u_den;
}
case TAG_FMT_SRATIONAL:
s_den = php_ifd_get32s(4+(char *)value, motorola_intel);
if (s_den == 0) {
return 0;
} else {
return php_ifd_get32s(value, motorola_intel) / s_den;
}
case TAG_FMT_SSHORT:
return php_ifd_get16u(value, motorola_intel);
case TAG_FMT_SLONG:
return php_ifd_get32s(value, motorola_intel);
/* Not sure if this is correct (never seen float used in Exif format) */
case TAG_FMT_SINGLE:
return (size_t)*(float *)value;
case TAG_FMT_DOUBLE:
return (size_t)*(double *)value;
}
return 0;
}
/* Get 16 bits motorola order (always) for jpeg header stuff. */
static int php_jpg_get16(void *value) {
return (((uchar *)value)[0] << 8) | ((uchar *)value)[1];
}
/* Write 16 bit unsigned value to data */
static void php_ifd_set16u(char *data, unsigned int value, int motorola_intel) {
if (motorola_intel) {
data[0] = (value & 0xFF00) >> 8;
data[1] = (value & 0x00FF);
} else {
data[1] = (value & 0xFF00) >> 8;
data[0] = (value & 0x00FF);
}
}
/* Convert a 32 bit unsigned value from file's native byte order */
static void php_ifd_set32u(char *data, size_t value, int motorola_intel) {
if (motorola_intel) {
data[0] = (value & 0xFF000000) >> 24;
data[1] = (value & 0x00FF0000) >> 16;
data[2] = (value & 0x0000FF00) >> 8;
data[3] = (value & 0x000000FF);
} else {
data[3] = (value & 0xFF000000) >> 24;
data[2] = (value & 0x00FF0000) >> 16;
data[1] = (value & 0x0000FF00) >> 8;
data[0] = (value & 0x000000FF);
}
}
/* Create a value for an ifd from an info_data pointer */
static void* exif_ifd_make_value(image_info_data *info_data,
int motorola_intel) {
size_t byte_count;
char *value_ptr, *data_ptr;
size_t i;
image_info_value *info_value;
byte_count =
get_php_tiff_bytes_per_format(info_data->format) * info_data->length;
size_t malloc_size = byte_count > 4 ? byte_count : 4;
value_ptr = (char *)IM_MALLOC(malloc_size);
CHECK_ALLOC_R(value_ptr, malloc_size, NULL);
memset(value_ptr, 0, 4);
if (!info_data->length) {
return value_ptr;
}
if (info_data->format == TAG_FMT_UNDEFINED ||
info_data->format == TAG_FMT_STRING ||
(byte_count>1 && (info_data->format == TAG_FMT_BYTE ||
info_data->format == TAG_FMT_SBYTE))) {
memmove(value_ptr, info_data->value.s, byte_count);
return value_ptr;
} else if (info_data->format == TAG_FMT_BYTE) {
*value_ptr = info_data->value.u;
return value_ptr;
} else if (info_data->format == TAG_FMT_SBYTE) {
*value_ptr = info_data->value.i;
return value_ptr;
} else {
data_ptr = value_ptr;
for(i=0; i<info_data->length; i++) {
if (info_data->length==1) {
info_value = &info_data->value;
} else {
info_value = &info_data->value.list[i];
}
switch(info_data->format) {
case TAG_FMT_USHORT:
php_ifd_set16u(data_ptr, info_value->u, motorola_intel);
data_ptr += 2;
break;
case TAG_FMT_ULONG:
php_ifd_set32u(data_ptr, info_value->u, motorola_intel);
data_ptr += 4;
break;
case TAG_FMT_SSHORT:
php_ifd_set16u(data_ptr, info_value->i, motorola_intel);
data_ptr += 2;
break;
case TAG_FMT_SLONG:
php_ifd_set32u(data_ptr, info_value->i, motorola_intel);
data_ptr += 4;
break;
case TAG_FMT_URATIONAL:
php_ifd_set32u(data_ptr, info_value->sr.num, motorola_intel);
php_ifd_set32u(data_ptr+4, info_value->sr.den, motorola_intel);
data_ptr += 8;
break;
case TAG_FMT_SRATIONAL:
php_ifd_set32u(data_ptr, info_value->ur.num, motorola_intel);
php_ifd_set32u(data_ptr+4, info_value->ur.den, motorola_intel);
data_ptr += 8;
break;
case TAG_FMT_SINGLE:
memmove(data_ptr, &info_data->value.f, byte_count);
data_ptr += 4;
break;
case TAG_FMT_DOUBLE:
memmove(data_ptr, &info_data->value.d, byte_count);
data_ptr += 8;
break;
}
}
}
return value_ptr;
}
/*
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 exif_process_COM(image_info_type *image_info, char *value,
size_t length) {
exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment",
TAG_COMPUTED_VALUE, TAG_FMT_STRING,
length-2, value+2);
}
/* Check and build thumbnail */
static void exif_thumbnail_build(image_info_type *ImageInfo) {
size_t new_size, new_move, new_value;
char *new_data;
void *value_ptr;
int i, byte_count;
image_info_list *info_list;
image_info_data *info_data;
if (!ImageInfo->read_thumbnail || !ImageInfo->Thumbnail.offset ||
!ImageInfo->Thumbnail.size) {
return; /* ignore this call */
}
switch(ImageInfo->Thumbnail.filetype) {
default:
case IMAGE_FILETYPE_JPEG:
/* done */
break;
case IMAGE_FILETYPE_TIFF_II:
case IMAGE_FILETYPE_TIFF_MM:
info_list = &ImageInfo->info_list[SECTION_THUMBNAIL];
new_size = 8 + 2 + info_list->count * 12 + 4;
new_value= new_size; /* offset for ifd values outside ifd directory */
for (i=0; i<info_list->count; i++) {
info_data = &info_list->list[i];
byte_count =
get_php_tiff_bytes_per_format(info_data->format) * info_data->length;
if (byte_count > 4) {
new_size += byte_count;
}
}
new_move = new_size;
new_data = (char *)IM_REALLOC(ImageInfo->Thumbnail.data,
ImageInfo->Thumbnail.size + new_size);
CHECK_ALLOC(new_data, ImageInfo->Thumbnail.size + new_size);
ImageInfo->Thumbnail.data = new_data;
memmove(ImageInfo->Thumbnail.data + new_move,
ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
ImageInfo->Thumbnail.size += new_size;
/* fill in data */
if (ImageInfo->motorola_intel) {
memmove(new_data, "MM\x00\x2a\x00\x00\x00\x08", 8);
} else {
memmove(new_data, "II\x2a\x00\x08\x00\x00\x00", 8);
}
new_data += 8;
php_ifd_set16u(new_data, info_list->count, ImageInfo->motorola_intel);
new_data += 2;
for (i=0; i<info_list->count; i++) {
info_data = &info_list->list[i];
byte_count =
get_php_tiff_bytes_per_format(info_data->format) * info_data->length;
if (info_data->tag==TAG_STRIP_OFFSETS ||
info_data->tag==TAG_JPEG_INTERCHANGE_FORMAT) {
php_ifd_set16u(new_data + 0, info_data->tag,
ImageInfo->motorola_intel);
php_ifd_set16u(new_data + 2, TAG_FMT_ULONG,
ImageInfo->motorola_intel);
php_ifd_set32u(new_data + 4, 1, ImageInfo->motorola_intel);
php_ifd_set32u(new_data + 8, new_move, ImageInfo->motorola_intel);
} else {
php_ifd_set16u(new_data + 0, info_data->tag,
ImageInfo->motorola_intel);
php_ifd_set16u(new_data + 2, info_data->format,
ImageInfo->motorola_intel);
php_ifd_set32u(new_data + 4, info_data->length,
ImageInfo->motorola_intel);
value_ptr = exif_ifd_make_value(info_data, ImageInfo->motorola_intel);
if (byte_count <= 4) {
memmove(new_data+8, value_ptr, 4);
} else {
php_ifd_set32u(new_data+8, new_value, ImageInfo->motorola_intel);
memmove(ImageInfo->Thumbnail.data+new_value, value_ptr, byte_count);
new_value += byte_count;
}
if (value_ptr) IM_FREE(value_ptr);
}
new_data += 12;
}
memset(new_data, 0, 4); /* next ifd pointer */
break;
}
}
/* Grab the thumbnail, corrected */
static void exif_thumbnail_extract(image_info_type *ImageInfo,
char *offset, size_t length) {
if (ImageInfo->Thumbnail.data) {
raise_warning("Multiple possible thumbnails");
return; /* Should not happen */
}
if (!ImageInfo->read_thumbnail) {
return; /* ignore this call */
}
/* according to exif2.1, the thumbnail is not supposed to be greater
than 64K */
if (ImageInfo->Thumbnail.size >= 65536 ||
ImageInfo->Thumbnail.size <= 0 ||
ImageInfo->Thumbnail.offset <= 0) {
raise_warning("Illegal thumbnail size/offset");
return;
}
/* Check to make sure we are not going to go past the ExifLength */
if ((ImageInfo->Thumbnail.offset + ImageInfo->Thumbnail.size) > length) {
raise_warning("Thumbnail goes IFD boundary or end of file reached");
return;
}
PHP_STRNDUP(ImageInfo->Thumbnail.data, offset + ImageInfo->Thumbnail.offset,
ImageInfo->Thumbnail.size);
exif_thumbnail_build(ImageInfo);
}
/* Copy a string/buffer in Exif header to a character string and return
length of allocated buffer if any. */
static int exif_process_undefined(char **result, char *value,
size_t byte_count) {
/* we cannot use strlcpy - here the problem is that we have to copy NUL
* chars up to byte_count, we also have to add a single NUL character to
* force end of string.
*/
if (byte_count) {
PHP_STRNDUP((*result), value, byte_count); /* NULL @ byte_count!!! */
if (*result) return byte_count+1;
}
return 0;
}
/* Copy a string in Exif header to a character string returns length of
allocated buffer if any. */
#if !EXIF_USE_MBSTRING
static int exif_process_string_raw(char **result, char *value,
size_t byte_count) {
/* we cannot use strlcpy - here the problem is that we have to copy NUL
* chars up to byte_count, we also have to add a single NUL character to
* force end of string.
*/
*result = 0;
if (byte_count) {
(*result) = (char*)IM_MALLOC(byte_count + 1);
CHECK_ALLOC_R((*result), byte_count + 1, 0);
memcpy(*result, value, byte_count);
(*result)[byte_count] = '\0';
return byte_count+1;
}
return 0;
}
#endif
/*
* Copy a string in Exif header to a character string and return length of
allocated buffer if any. In contrast to exif_process_string this function
does allways return a string buffer */
static int exif_process_string(char **result, char *value,
size_t byte_count) {
/* 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=byte_count+1
* because then we might get into an EXCEPTION if we exceed an allocated
* memory page...so we use php_strnlen in conjunction with memcpy and add
* the NUL char.
*/
if ((byte_count=php_strnlen(value, byte_count)) > 0) {
return exif_process_undefined(result, value, byte_count);
}
PHP_STRNDUP((*result), "", 1); /* force empty string */
if (*result) return byte_count+1;
return 0;
}
/* Process UserComment in IFD. */
static int exif_process_user_comment(image_info_type *ImageInfo,
char **pszInfoPtr, char **pszEncoding,
char *szValuePtr, int ByteCount) {
int a;
#if EXIF_USE_MBSTRING
char *decode;
size_t len;
#endif
*pszEncoding = NULL;
/* Copy the comment */
if (ByteCount>=8) {
if (!memcmp(szValuePtr, "UNICODE\0", 8)) {
PHP_STRDUP(*pszEncoding, (const char*)szValuePtr);
szValuePtr = szValuePtr+8;
ByteCount -= 8;
#if EXIF_USE_MBSTRING
/* First try to detect BOM: ZERO WIDTH NOBREAK SPACE (FEFF 16)
* since we have no encoding support for the BOM yet we skip that.
*/
if (!memcmp(szValuePtr, "\xFE\xFF", 2)) {
decode = "UCS-2BE";
szValuePtr = szValuePtr+2;
ByteCount -= 2;
} else if (!memcmp(szValuePtr, "\xFF\xFE", 2)) {
decode = "UCS-2LE";
szValuePtr = szValuePtr+2;
ByteCount -= 2;
} else if (ImageInfo->motorola_intel) {
decode = ImageInfo->decode_unicode_be;
} else {
decode = ImageInfo->decode_unicode_le;
}
*pszInfoPtr = php_mb_convert_encoding(szValuePtr, ByteCount,
ImageInfo->encode_unicode,
decode, &len);
return len;
#else
return exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount);
#endif
} else
if (!memcmp(szValuePtr, "ASCII\0\0\0", 8)) {
PHP_STRDUP(*pszEncoding, (const char*)szValuePtr);
szValuePtr = szValuePtr+8;
ByteCount -= 8;
} else
if (!memcmp(szValuePtr, "JIS\0\0\0\0\0", 8)) {
/* JIS should be tanslated to MB or we leave it to the user - leave it to the user */
PHP_STRDUP(*pszEncoding, (const char*)szValuePtr);
szValuePtr = szValuePtr+8;
ByteCount -= 8;
#if EXIF_USE_MBSTRING
if (ImageInfo->motorola_intel) {
*pszInfoPtr = php_mb_convert_encoding(szValuePtr, ByteCount, ImageInfo->encode_jis, ImageInfo->decode_jis_be, &len);
} else {
*pszInfoPtr = php_mb_convert_encoding(szValuePtr, ByteCount, ImageInfo->encode_jis, ImageInfo->decode_jis_le, &len);
}
return len;
#else
return exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount);
#endif
} else
if (!memcmp(szValuePtr, "\0\0\0\0\0\0\0\0", 8)) {
/* 8 NULL means undefined and should be ASCII... */
PHP_STRDUP(*pszEncoding, "UNDEFINED");
szValuePtr = szValuePtr+8;
ByteCount -= 8;
}
}
/* Olympus has this padded with trailing spaces. Remove these first. */
if (ByteCount>0) {
for (a=ByteCount-1;a && szValuePtr[a]==' ';a--) {
(szValuePtr)[a] = '\0';
}
}
/* normal text without encoding */
exif_process_string(pszInfoPtr, szValuePtr, ByteCount);
return strlen(*pszInfoPtr);
}
/* Process unicode field in IFD. */
static int exif_process_unicode(image_info_type *ImageInfo,
xp_field_type *xp_field, int tag,
char *szValuePtr, int ByteCount) {
xp_field->tag = tag;
/* Copy the comment */
#if EXIF_USE_MBSTRING
xp_field->value =
php_mb_convert_encoding(szValuePtr, ByteCount, ImageInfo->encode_unicode,
ImageInfo->decode_unicode_le, &xp_field->size);
return xp_field->size;
#else
xp_field->size =
exif_process_string_raw(&xp_field->value, szValuePtr, ByteCount);
return xp_field->size;
#endif
}
/* Process nested IFDs directories in Maker Note. */
static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo,
char * value_ptr, int value_len,
char *offset_base, size_t IFDlength,
size_t displacement) {
int de, section_index = SECTION_MAKERNOTE;
int NumDirEntries, old_motorola_intel, offset_diff;
const maker_note_type *maker_note;
char *dir_start;
char *value_end = value_ptr + value_len;
for (unsigned int i=0;
i<=sizeof(maker_note_array)/sizeof(maker_note_type); i++) {
if (i==sizeof(maker_note_array)/sizeof(maker_note_type))
return 0;
maker_note = maker_note_array+i;
if (maker_note->make &&
(!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make))) {
continue;
}
if (maker_note->model &&
(!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) {
continue;
}
if (maker_note->id_string &&
strncmp(maker_note->id_string, value_ptr,
(maker_note->id_string_len < value_len ?
maker_note->id_string_len : value_len))) {
continue;
}
break;
}
if (maker_note->offset >= value_len) return 0;
dir_start = value_ptr + maker_note->offset;
ImageInfo->sections_found |= FOUND_MAKERNOTE;
old_motorola_intel = ImageInfo->motorola_intel;
switch (maker_note->byte_order) {
case MN_ORDER_INTEL:
ImageInfo->motorola_intel = 0;
break;
case MN_ORDER_MOTOROLA:
ImageInfo->motorola_intel = 1;
break;
default:
case MN_ORDER_NORMAL:
break;
}
if (value_end - dir_start < 2) return 0;
NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel);
switch (maker_note->offset_mode) {
case MN_OFFSET_MAKER:
offset_base = value_ptr;
break;
case MN_OFFSET_GUESS:
if (value_end - (dir_start+10) < 4) return 0;
offset_diff = 2 + NumDirEntries*12 + 4 -
php_ifd_get32u(dir_start+10, ImageInfo->motorola_intel);
offset_base = value_ptr + offset_diff;
break;
default:
case MN_OFFSET_NORMAL:
break;
}
if ((2+NumDirEntries*12) > value_len) {
raise_warning("Illegal IFD size: 2 + x%04X*12 = x%04X > x%04X",
NumDirEntries, 2+NumDirEntries*12, value_len);
return 0;
}
for (de=0;de<NumDirEntries;de++) {
if (!exif_process_IFD_TAG(ImageInfo, dir_start + 2 + 12 * de,
offset_base, value_end, IFDlength, displacement,
section_index, 0, maker_note->tag_table)) {
return 0;
}
}
ImageInfo->motorola_intel = old_motorola_intel;
return 0;
}
/* Process one of the nested IFDs directories. */
static int exif_process_IFD_TAG(image_info_type *ImageInfo, char *dir_entry,
char *offset_base, char *end, size_t IFDlength,
size_t displacement, int section_index,
int ReadNextIFD, tag_table_type tag_table) {
size_t length;
int tag, format, components;
char *value_ptr, tagname[64], cbuf[32], *outside=NULL;
size_t byte_count, offset_val, fpos, fgot;
xp_field_type *tmp_xp;
/* Protect against corrupt headers */
if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) {
raise_warning("corrupt EXIF header: maximum directory "
"nesting level reached");
return 0;
}
ImageInfo->ifd_nesting_level++;
CHECK_BUFFER_R(dir_entry+4, end, 4, 0);
tag = php_ifd_get16u(dir_entry, ImageInfo->motorola_intel);
format = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel);
components = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel);
if (!format || format > NUM_FORMATS) {
/* (-1) catches illegal zero case as unsigned underflows to
positive large. */
raise_warning("Process tag(x%04X=%s): Illegal format code 0x%04X, "
"suppose BYTE", tag,
exif_get_tagname(tag, tagname, -12, tag_table), format);
format = TAG_FMT_BYTE;
/*return TRUE;*/
}
byte_count = components * get_php_tiff_bytes_per_format(format);
if ((ssize_t)byte_count < 0) {
raise_warning("Process tag(x%04X=%s): Illegal byte_count(%ld)",
tag, exif_get_tagname(tag, tagname, -12, tag_table),
byte_count);
return 0;
}
if (byte_count > 4) {
CHECK_BUFFER_R(dir_entry+8, end, 4, 0);
offset_val = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel);
/* If its bigger than 4 bytes, the dir entry contains an offset. */
value_ptr = offset_base+offset_val;
if (offset_val+byte_count > IFDlength || value_ptr < dir_entry) {
/*
// It is important to check for IMAGE_FILETYPE_TIFF
// JPEG does not use absolute pointers instead its pointers are relative to the start
// of the TIFF header in APP1 section.
*/
if (offset_val+byte_count>ImageInfo->FileSize ||
(ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II &&
ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM &&
ImageInfo->FileType!=IMAGE_FILETYPE_JPEG)) {
if (value_ptr < dir_entry) {
/* we can read this if offset_val > 0 */
/* some files have their values in other parts of the file */
raise_warning("Process tag(x%04X=%s): Illegal pointer offset"
"(x%04lX < %04lX)", tag,
exif_get_tagname(tag, tagname, -12, tag_table),
offset_val, dir_entry-offset_base);
} else {
/* this is for sure not allowed */
/* exception are IFD pointers */
raise_warning("Process tag(x%04X=%s): Illegal pointer offset"
"(x%04lX + x%04lX = x%04lX > x%04lX)", tag,
exif_get_tagname(tag, tagname, -12, tag_table),
offset_val, byte_count, offset_val+byte_count,
IFDlength);
}
return 0;
}
if (byte_count>sizeof(cbuf)) {
/* mark as outside range and get buffer */
value_ptr = (char *)IM_MALLOC(byte_count);
CHECK_ALLOC_R(value_ptr, byte_count, 0);
outside = value_ptr;
} else {
/*
// in most cases we only access a small range so
// it is faster to use a static buffer there
// BUT it offers also the possibility to have
// pointers read without the need to free them
// explicitley before returning.
*/
memset(&cbuf, 0, sizeof(cbuf));
value_ptr = cbuf;
}
fpos = ImageInfo->infile->tell();
ImageInfo->infile->seek(offset_val, SEEK_SET);
fgot = ImageInfo->infile->tell();
if (fgot!=offset_val) {
if (outside) IM_FREE(outside);
raise_warning("Wrong file pointer: 0x%08lX != 0x%08lX",
fgot, offset_val);
return 0;
}
String str = ImageInfo->infile->read(byte_count);
fgot = str.length();
memcpy(value_ptr, str.c_str(), fgot);
ImageInfo->infile->seek(fpos, SEEK_SET);
if (fgot<byte_count) {
if (outside) IM_FREE(outside);
raise_warning("Unexpected end of file reached");
return 0;
}
}
} else {
/* 4 bytes or less and value is in the dir entry itself */
value_ptr = dir_entry+8;
offset_val= value_ptr-offset_base;
}
ImageInfo->sections_found |= FOUND_ANY_TAG;
if (section_index==SECTION_THUMBNAIL) {
if (!ImageInfo->Thumbnail.data) {
switch(tag) {
case TAG_IMAGEWIDTH:
case TAG_COMP_IMAGE_WIDTH:
ImageInfo->Thumbnail.width =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_IMAGEHEIGHT:
case TAG_COMP_IMAGE_HEIGHT:
ImageInfo->Thumbnail.height =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_STRIP_OFFSETS:
case TAG_JPEG_INTERCHANGE_FORMAT:
/* accept both formats */
ImageInfo->Thumbnail.offset =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_STRIP_BYTE_COUNTS:
if (ImageInfo->FileType == IMAGE_FILETYPE_TIFF_II ||
ImageInfo->FileType == IMAGE_FILETYPE_TIFF_MM) {
ImageInfo->Thumbnail.filetype = ImageInfo->FileType;
} else {
/* motorola is easier to read */
ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_TIFF_MM;
}
ImageInfo->Thumbnail.size =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_JPEG_INTERCHANGE_FORMAT_LEN:
if (ImageInfo->Thumbnail.filetype == IMAGE_FILETYPE_UNKNOWN) {
ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_JPEG;
ImageInfo->Thumbnail.size =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
}
break;
}
}
} else {
if (section_index==SECTION_IFD0 || section_index==SECTION_EXIF)
switch(tag) {
case TAG_COPYRIGHT:
/* check for "<photographer> NUL <editor> NUL" */
if (byte_count>1 && (length=php_strnlen(value_ptr, byte_count)) > 0) {
if (length<byte_count-1) {
/* When there are any characters after the first NUL */
PHP_STRDUP(ImageInfo->CopyrightPhotographer, value_ptr);
PHP_STRDUP(ImageInfo->CopyrightEditor, value_ptr+length+1);
if (ImageInfo->Copyright) IM_FREE(ImageInfo->Copyright);
php_vspprintf(&ImageInfo->Copyright, 0, "%s, %s",
value_ptr, value_ptr+length+1);
/* format = TAG_FMT_UNDEFINED; this musn't be ASCII */
/* but we are not supposed to change this */
/* keep in mind that image_info does not store editor value */
} else {
PHP_STRDUP(ImageInfo->Copyright, value_ptr);
}
}
break;
case TAG_USERCOMMENT:
ImageInfo->UserCommentLength =
exif_process_user_comment(ImageInfo, &(ImageInfo->UserComment),
&(ImageInfo->UserCommentEncoding),
value_ptr, byte_count);
break;
case TAG_XP_TITLE:
case TAG_XP_COMMENTS:
case TAG_XP_AUTHOR:
case TAG_XP_KEYWORDS:
case TAG_XP_SUBJECT: {
size_t realloc_size =
(ImageInfo->xp_fields.count+1) * sizeof(xp_field_type);
tmp_xp = (xp_field_type*)
IM_REALLOC(ImageInfo->xp_fields.list, realloc_size);
if (!tmp_xp) {
if (outside) IM_FREE(outside);
}
CHECK_ALLOC_R(tmp_xp, realloc_size, 0);
ImageInfo->sections_found |= FOUND_WINXP;
ImageInfo->xp_fields.list = tmp_xp;
ImageInfo->xp_fields.count++;
exif_process_unicode(ImageInfo,
&(ImageInfo->xp_fields.list[ImageInfo->xp_fields.count-1]),
tag, value_ptr, byte_count);
break;
}
case TAG_FNUMBER:
/* Simplest way of expressing aperture, so I trust it the most.
(overwrite previously computed value if there is one) */
ImageInfo->ApertureFNumber =
(float)exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_APERTURE:
case TAG_MAX_APERTURE:
/* 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(exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel)*log(2)*0.5);
}
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.
SHUTTERSPEED comes after EXPOSURE TIME
*/
if (ImageInfo->ExposureTime == 0) {
ImageInfo->ExposureTime
= (float)(1/exp(exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel)*log(2)));
}
break;
case TAG_EXPOSURETIME:
ImageInfo->ExposureTime = -1;
break;
case TAG_COMP_IMAGE_WIDTH:
ImageInfo->ExifImageWidth =
exif_convert_any_to_int(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_FOCALPLANE_X_RES:
ImageInfo->FocalplaneXRes =
exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel);
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)exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel);
break;
case TAG_FOCALPLANE_RESOLUTION_UNIT:
switch((int)exif_convert_any_format(value_ptr, format,
ImageInfo->motorola_intel)) {
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_SUB_IFD:
if (format==TAG_FMT_IFD) {
/* If this is called we are either in a TIFFs thumbnail or
a JPEG where we cannot handle it */
/* TIFF thumbnail: our data structure cannot store a thumbnail
of a thumbnail */
/* JPEG do we have the data area and what to do with it */
raise_notice("Skip SUB IFD");
}
break;
case TAG_MAKE:
PHP_STRDUP(ImageInfo->make, value_ptr);
break;
case TAG_MODEL:
PHP_STRDUP(ImageInfo->model, value_ptr);
break;
case TAG_MAKER_NOTE:
exif_process_IFD_in_MAKERNOTE(ImageInfo, value_ptr, byte_count,
offset_base, IFDlength, displacement);
break;
case TAG_EXIF_IFD_POINTER:
case TAG_GPS_IFD_POINTER:
case TAG_INTEROP_IFD_POINTER:
if (ReadNextIFD) {
char *Subdir_start;
int sub_section_index = 0;
switch(tag) {
case TAG_EXIF_IFD_POINTER:
ImageInfo->sections_found |= FOUND_EXIF;
sub_section_index = SECTION_EXIF;
break;
case TAG_GPS_IFD_POINTER:
ImageInfo->sections_found |= FOUND_GPS;
sub_section_index = SECTION_GPS;
break;
case TAG_INTEROP_IFD_POINTER:
ImageInfo->sections_found |= FOUND_INTEROP;
sub_section_index = SECTION_INTEROP;
break;
}
CHECK_BUFFER_R(value_ptr, end, 4, 0);
Subdir_start = offset_base +
php_ifd_get32u(value_ptr, ImageInfo->motorola_intel);
if (Subdir_start < offset_base ||
Subdir_start > offset_base+IFDlength) {
raise_warning("Illegal IFD Pointer");
return 0;
}
if (!exif_process_IFD_in_JPEG(ImageInfo, Subdir_start,
offset_base, end, IFDlength,
displacement, sub_section_index)) {
return 0;
}
}
}
}
exif_iif_add_tag(ImageInfo, section_index,
exif_get_tagname(tag, tagname, sizeof(tagname), tag_table),
tag, format, components, value_ptr);
if (outside) IM_FREE(outside);
return 1;
}
/* Process one of the nested IFDs directories. */
static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo,
char *dir_start, char *offset_base,
char *end,
size_t IFDlength, size_t displacement,
int section_index) {
int de;
int NumDirEntries;
int NextDirOffset;
ImageInfo->sections_found |= FOUND_IFD0;
CHECK_BUFFER_R(dir_start, end, 2, 0);
NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel);
if ((dir_start+2+NumDirEntries*12) > (offset_base+IFDlength)) {
raise_warning("Illegal IFD size: x%04X + 2 + x%04X*12 = x%04X > x%04lX",
(int)((size_t)dir_start+2-(size_t)offset_base),
NumDirEntries,
(int)((size_t)dir_start+2+
NumDirEntries*12-(size_t)offset_base), IFDlength);
return 0;
}
for (de=0;de<NumDirEntries;de++) {
if (!exif_process_IFD_TAG(ImageInfo, dir_start + 2 + 12 * de,
offset_base, end, IFDlength, displacement,
section_index, 1,
exif_get_tag_table(section_index))) {
return 0;
}
}
/*
* Ignore IFD2 if it purportedly exists
*/
if (section_index == SECTION_THUMBNAIL) {
return true;
}
/*
* Hack to make it process IDF1 I hope
* There are 2 IDFs, the second one holds the keys (0x0201 and 0x0202)
* to the thumbnail
*/
CHECK_BUFFER_R(dir_start+2+12*de, end, 4, 0);
NextDirOffset =
php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel);
if (NextDirOffset) {
/* the next line seems false but here IFDlength means
length of all IFDs */
if (offset_base + NextDirOffset < offset_base ||
offset_base + NextDirOffset > offset_base+IFDlength) {
raise_warning("Illegal IFD offset");
return 0;
}
/* That is the IFD for the first thumbnail */
if (exif_process_IFD_in_JPEG(ImageInfo, offset_base + NextDirOffset,
offset_base, end, IFDlength, displacement,
SECTION_THUMBNAIL)) {
if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN &&
ImageInfo->Thumbnail.size &&
ImageInfo->Thumbnail.offset &&
ImageInfo->read_thumbnail) {
exif_thumbnail_extract(ImageInfo, offset_base, IFDlength);
}
return 1;
} else {
return 0;
}
}
return 1;
}
/* Process a TIFF header in a JPEG file */
static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo,
char *CharBuf, size_t length,
size_t displacement) {
char *end = CharBuf + length;
unsigned exif_value_2a, offset_of_ifd;
/* set the thumbnail stuff to nothing so we can test to see if
they get set up */
CHECK_BUFFER(CharBuf, end, 2);
if (memcmp(CharBuf, "II", 2) == 0) {
ImageInfo->motorola_intel = 0;
} else if (memcmp(CharBuf, "MM", 2) == 0) {
ImageInfo->motorola_intel = 1;
} else {
raise_warning("Invalid TIFF a lignment marker");
return;
}
/* Check the next two values for correctness. */
CHECK_BUFFER(CharBuf+4, end, 4);
exif_value_2a = php_ifd_get16u(CharBuf+2, ImageInfo->motorola_intel);
offset_of_ifd = php_ifd_get32u(CharBuf+4, ImageInfo->motorola_intel);
if ( exif_value_2a != 0x2a || offset_of_ifd < 0x08) {
raise_warning("Invalid TIFF start (1)");
return;
}
if (offset_of_ifd > length) {
raise_warning("Invalid IFD start");
return;
}
ImageInfo->sections_found |= FOUND_IFD0;
/* First directory starts at offset 8. Offsets starts at 0. */
exif_process_IFD_in_JPEG(ImageInfo, CharBuf+offset_of_ifd,
CharBuf, end, length/* -14*/, displacement,
SECTION_IFD0);
/* Compute the CCD width, in milimeters. */
if (ImageInfo->FocalplaneXRes != 0) {
ImageInfo->CCDWidth = (float)(ImageInfo->ExifImageWidth *
ImageInfo->FocalplaneUnits / ImageInfo->FocalplaneXRes);
}
}
/* Process an JPEG APP1 block marker
Describes all the drivel that most digital cameras include...
*/
static void exif_process_APP1(image_info_type *ImageInfo, char *CharBuf,
size_t length, size_t displacement) {
/* Check the APP1 for Exif Identifier Code */
char *end = CharBuf + length;
static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
CHECK_BUFFER(CharBuf+2, end, 6);
if (length <= 8 || memcmp(CharBuf+2, ExifHeader, 6)) {
raise_warning("Incorrect APP1 Exif Identifier Code");
return;
}
exif_process_TIFF_in_JPEG(ImageInfo, CharBuf + 8, length - 8,
displacement+8);
}
/* Process an JPEG APP12 block marker used by OLYMPUS */
static void exif_process_APP12(image_info_type *ImageInfo,
char *buffer, size_t length) {
size_t l1, l2=0;
if ((l1 = php_strnlen(buffer+2, length-2)) > 0) {
exif_iif_add_tag(ImageInfo, SECTION_APP12, "Company",
TAG_NONE, TAG_FMT_STRING, l1, buffer+2);
if (length > 2+l1+1) {
l2 = php_strnlen(buffer+2+l1+1, length-2-l1+1);
exif_iif_add_tag(ImageInfo, SECTION_APP12, "Info",
TAG_NONE, TAG_FMT_STRING, l2, buffer+2+l1+1);
}
}
}
/* Process a SOFn marker. This is useful for the image dimensions */
static void exif_process_SOFn (uchar *Data, int marker,
jpeg_sof_info *result) {
result->bits_per_sample = Data[2];
result->height = php_jpg_get16(Data+3);
result->width = php_jpg_get16(Data+5);
result->num_components = Data[7];
}
/* Parse the marker stream until SOS or EOI is seen; */
static int exif_scan_JPEG_header(image_info_type *ImageInfo) {
int section, sn;
int marker = 0, last_marker = M_PSEUDO, comment_correction=1;
int ll, lh;
uchar *Data;
size_t fpos, size, got, itemlen;
jpeg_sof_info sof_info;
for(section=0;;section++) {
// get marker byte, swallowing possible padding
// some software does not count the length bytes of COM section
// one company doing so is very much envolved in JPEG...
// so we accept too
if (last_marker==M_COM && comment_correction) {
comment_correction = 2;
}
do {
if ((marker = ImageInfo->infile->getc()) == EOF) {
raise_warning("File structure corrupted");
return 0;
}
if (last_marker==M_COM && comment_correction>0) {
if (marker!=0xFF) {
marker = 0xff;
comment_correction--;
} else {
last_marker = M_PSEUDO; /* stop skipping 0 for M_COM */
}
}
} while (marker == 0xff);
if (last_marker==M_COM && !comment_correction) {
raise_notice("Image has corrupt COM section: some software set "
"wrong length information");
}
if (last_marker==M_COM && comment_correction)
return M_EOI; /* ah illegal: char after COM section not 0xFF */
fpos = ImageInfo->infile->tell();
if (marker == 0xff) {
// 0xff is legal padding, but if we get that many, something's wrong.
raise_warning("To many padding bytes");
return 0;
}
/* Read the length of the section. */
if ((lh = ImageInfo->infile->getc()) == EOF) {
raise_warning("File structure corrupted");
return 0;
}
if ((ll = ImageInfo->infile->getc()) == EOF) {
raise_warning("File structure corrupted");
return 0;
}
itemlen = (lh << 8) | ll;
if (itemlen < 2) {
raise_warning("File structure corrupted");
return 0;
}
sn = exif_file_sections_add(ImageInfo, marker, itemlen+1, NULL);
if (sn == -1) return 0;
Data = ImageInfo->file.list[sn].data;
/* Store first two pre-read bytes. */
Data[0] = (uchar)lh;
Data[1] = (uchar)ll;
String str = ImageInfo->infile->read(itemlen-2);
got = str.length();
if (got != itemlen-2) {
raise_warning("Error reading from file: "
"got=x%04lX(=%lu) != itemlen-2=x%04lX(=%lu)",
got, got, itemlen-2, itemlen-2);
return 0;
}
memcpy(Data+2, str.c_str(), got);
switch(marker) {
case M_SOS: /* stop before hitting compressed data */
// If reading entire image is requested, read the rest of the data.
if (ImageInfo->read_all) {
/* Determine how much file is left. */
fpos = ImageInfo->infile->tell();
size = ImageInfo->FileSize - fpos;
sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL);
if (sn == -1) return 0;
Data = ImageInfo->file.list[sn].data;
str = ImageInfo->infile->read(size);
got = str.length();
if (got != size) {
raise_warning("Unexpected end of file reached");
return 0;
}
memcpy(Data, str.c_str(), got);
}
return 1;
case M_EOI: /* in case it's a tables-only JPEG stream */
raise_warning("No image in jpeg!");
return (ImageInfo->sections_found&(~FOUND_COMPUTED)) ? 1 : 0;
case M_COM: /* Comment section */
exif_process_COM(ImageInfo, (char *)Data, itemlen);
break;
case M_EXIF:
if (!(ImageInfo->sections_found&FOUND_IFD0)) {
/*ImageInfo->sections_found |= FOUND_EXIF;*/
/* Seen files from some 'U-lead' software with Vivitar scanner
that uses marker 31 later in the file (no clue what for!) */
exif_process_APP1(ImageInfo, (char *)Data, itemlen, fpos);
}
break;
case M_APP12:
exif_process_APP12(ImageInfo, (char *)Data, itemlen);
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:
exif_process_SOFn(Data, marker, &sof_info);
ImageInfo->Width = sof_info.width;
ImageInfo->Height = sof_info.height;
if (sof_info.num_components == 3) {
ImageInfo->IsColor = 1;
} else {
ImageInfo->IsColor = 0;
}
break;
default:
/* skip any other marker silently. */
break;
}
/* keep track of last marker */
last_marker = marker;
}
return 1;
}
/* Reallocate a file section returns 0 on success and -1 on failure */
static int exif_file_sections_realloc(image_info_type *ImageInfo,
int section_index, size_t size) {
void *tmp;
/* This is not a malloc/realloc check. It is a plausibility check for the
* function parameters (requirements engineering).
*/
if (section_index >= ImageInfo->file.count) {
raise_warning("Illegal reallocating of undefined file section");
return -1;
}
tmp = IM_REALLOC(ImageInfo->file.list[section_index].data, size);
CHECK_ALLOC_R(tmp, size, -1);
ImageInfo->file.list[section_index].data = (uchar *)tmp;
ImageInfo->file.list[section_index].size = size;
return 0;
}
/* Parse the TIFF header; */
static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo,
size_t dir_offset, int section_index) {
int i, sn, num_entries, sub_section_index = 0;
unsigned char *dir_entry;
char tagname[64];
size_t ifd_size, dir_size, entry_offset, next_offset,
entry_length, entry_value=0, fgot;
int entry_tag , entry_type;
tag_table_type tag_table = exif_get_tag_table(section_index);
if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) {
return 0;
}
if (ImageInfo->FileSize >= dir_offset+2) {
sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL);
if (sn == -1) return 0;
/* we do not know the order of sections */
ImageInfo->infile->seek(dir_offset, SEEK_SET);
String snData = ImageInfo->infile->read(2);
memcpy(ImageInfo->file.list[sn].data, snData.c_str(), 2);
num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data,
ImageInfo->motorola_intel);
dir_size = 2/*num dir entries*/ +
12/*length of entry*/*num_entries +
4/* offset to next ifd (points to thumbnail or NULL)*/;
if (ImageInfo->FileSize >= dir_offset+dir_size) {
if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) {
return 0;
}
snData = ImageInfo->infile->read(dir_size-2);
memcpy(ImageInfo->file.list[sn].data+2, snData.c_str(), dir_size-2);
next_offset =
php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4,
ImageInfo->motorola_intel);
/* now we have the directory we can look how long it should be */
ifd_size = dir_size;
char *end = (char*)ImageInfo->file.list[sn].data + dir_size;
for(i=0;i<num_entries;i++) {
dir_entry = ImageInfo->file.list[sn].data+2+i*12;
CHECK_BUFFER_R(dir_entry+4, end, 4, 0);
entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel);
entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel);
if (entry_type > NUM_FORMATS) {
raise_notice("Read from TIFF: tag(0x%04X,%12s): "
"Illegal format code 0x%04X, switching to BYTE",
entry_tag,
exif_get_tagname(entry_tag, tagname, -12, tag_table),
entry_type);
/* Since this is repeated in exif_process_IFD_TAG make it a
notice here and make it a warning in the exif_process_IFD_TAG
which is called elsewhere. */
entry_type = TAG_FMT_BYTE;
}
entry_length =
php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel) *
get_php_tiff_bytes_per_format(entry_type);
if (entry_length <= 4) {
switch(entry_type) {
case TAG_FMT_USHORT:
CHECK_BUFFER_R(dir_entry+8, end, 2, 0);
entry_value = php_ifd_get16u(dir_entry+8,
ImageInfo->motorola_intel);
break;
case TAG_FMT_SSHORT:
CHECK_BUFFER_R(dir_entry+8, end, 2, 0);
entry_value = php_ifd_get16s(dir_entry+8,
ImageInfo->motorola_intel);
break;
case TAG_FMT_ULONG:
CHECK_BUFFER_R(dir_entry+8, end, 4, 0);
entry_value = php_ifd_get32u(dir_entry+8,
ImageInfo->motorola_intel);
break;
case TAG_FMT_SLONG:
CHECK_BUFFER_R(dir_entry+8, end, 4, 0);
entry_value = php_ifd_get32s(dir_entry+8,
ImageInfo->motorola_intel);
break;
}
switch(entry_tag) {
case TAG_IMAGEWIDTH:
case TAG_COMP_IMAGE_WIDTH:
ImageInfo->Width = entry_value;
break;
case TAG_IMAGEHEIGHT:
case TAG_COMP_IMAGE_HEIGHT:
ImageInfo->Height = entry_value;
break;
case TAG_PHOTOMETRIC_INTERPRETATION:
switch (entry_value) {
case PMI_BLACK_IS_ZERO:
case PMI_WHITE_IS_ZERO:
case PMI_TRANSPARENCY_MASK:
ImageInfo->IsColor = 0;
break;
case PMI_RGB:
case PMI_PALETTE_COLOR:
case PMI_SEPARATED:
case PMI_YCBCR:
case PMI_CIELAB:
ImageInfo->IsColor = 1;
break;
}
break;
}
} else {
CHECK_BUFFER_R(dir_entry+8, end, 4, 0);
entry_offset =
php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel);
/* if entry needs expading ifd cache and entry is at end of
current ifd cache. */
/* otherwise there may be huge holes between two entries */
if (entry_offset + entry_length > dir_offset + ifd_size &&
entry_offset == dir_offset + ifd_size) {
ifd_size = entry_offset + entry_length - dir_offset;
}
}
}
if (ImageInfo->FileSize >=
dir_offset + ImageInfo->file.list[sn].size) {
if (ifd_size > dir_size) {
if (dir_offset + ifd_size > ImageInfo->FileSize) {
raise_warning("Error in TIFF: filesize(x%04lX) less than "
"size of IFD(x%04lX + x%04lX)",
ImageInfo->FileSize, dir_offset, ifd_size);
return 0;
}
if (exif_file_sections_realloc(ImageInfo, sn, ifd_size)) {
return 0;
}
/* read values not stored in directory itself */
snData = ImageInfo->infile->read(ifd_size-dir_size);
memcpy(ImageInfo->file.list[sn].data+dir_size, snData.c_str(),
ifd_size-dir_size);
}
/* now process the tags */
for(i=0;i<num_entries;i++) {
dir_entry = ImageInfo->file.list[sn].data+2+i*12;
CHECK_BUFFER_R(dir_entry+2, end, 2, 0);
entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel);
entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel);
if (entry_tag == TAG_EXIF_IFD_POINTER ||
entry_tag == TAG_INTEROP_IFD_POINTER ||
entry_tag == TAG_GPS_IFD_POINTER ||
entry_tag == TAG_SUB_IFD) {
switch(entry_tag) {
case TAG_EXIF_IFD_POINTER:
ImageInfo->sections_found |= FOUND_EXIF;
sub_section_index = SECTION_EXIF;
break;
case TAG_GPS_IFD_POINTER:
ImageInfo->sections_found |= FOUND_GPS;
sub_section_index = SECTION_GPS;
break;
case TAG_INTEROP_IFD_POINTER:
ImageInfo->sections_found |= FOUND_INTEROP;
sub_section_index = SECTION_INTEROP;
break;
case TAG_SUB_IFD:
ImageInfo->sections_found |= FOUND_THUMBNAIL;
sub_section_index = SECTION_THUMBNAIL;
break;
}
CHECK_BUFFER_R(dir_entry+8, end, 4, 0);
entry_offset =
php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel);
ImageInfo->ifd_nesting_level++;
exif_process_IFD_in_TIFF(ImageInfo, entry_offset,
sub_section_index);
if (section_index!=SECTION_THUMBNAIL && entry_tag==TAG_SUB_IFD) {
if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN &&
ImageInfo->Thumbnail.size &&
ImageInfo->Thumbnail.offset &&
ImageInfo->read_thumbnail) {
if (!ImageInfo->Thumbnail.data) {
ImageInfo->Thumbnail.data =
(char *)IM_MALLOC(ImageInfo->Thumbnail.size);
ImageInfo->infile->seek(ImageInfo->Thumbnail.offset,
SEEK_SET);
String str =
ImageInfo->infile->read(ImageInfo->Thumbnail.size);
fgot = str.length();
if (fgot < ImageInfo->Thumbnail.size) {
raise_warning("Thumbnail goes IFD boundary or "
"end of file reached");
}
memcpy(ImageInfo->Thumbnail.data, str.c_str(), fgot);
exif_thumbnail_build(ImageInfo);
}
}
}
} else {
if (!exif_process_IFD_TAG(ImageInfo, (char*)dir_entry,
(char*)ImageInfo->file.list[sn].data + ifd_size,
(char*)(ImageInfo->file.list[sn].data-dir_offset),
ifd_size, 0, section_index, 0, tag_table)) {
return 0;
}
}
}
/* If we had a thumbnail in a SUB_IFD we have ANOTHER image in
NEXT IFD */
if (next_offset && section_index != SECTION_THUMBNAIL) {
/* this should be a thumbnail IFD */
/* the thumbnail itself is stored at Tag=StripOffsets */
ImageInfo->ifd_nesting_level++;
exif_process_IFD_in_TIFF(ImageInfo, next_offset,
SECTION_THUMBNAIL);
if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset &&
ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
ImageInfo->Thumbnail.data =
(char *)IM_MALLOC(ImageInfo->Thumbnail.size);
CHECK_ALLOC_R(ImageInfo->Thumbnail.data,
ImageInfo->Thumbnail.size, 0);
ImageInfo->infile->seek(ImageInfo->Thumbnail.offset, SEEK_SET);
String str = ImageInfo->infile->read(ImageInfo->Thumbnail.size);
fgot = str.length();
if (fgot < ImageInfo->Thumbnail.size) {
raise_warning("Thumbnail goes IFD boundary or "
"end of file reached");
}
memcpy(ImageInfo->Thumbnail.data, str.c_str(), fgot);
exif_thumbnail_build(ImageInfo);
}
}
return 1;
} else {
raise_warning("Error in TIFF: filesize(x%04lX) less than "
"size of IFD(x%04lX)",
ImageInfo->FileSize,
dir_offset+ImageInfo->file.list[sn].size);
return 0;
}
} else {
raise_warning("Error in TIFF: filesize(x%04lX) less than size "
"of IFD dir(x%04lX)",
ImageInfo->FileSize, dir_offset+dir_size);
return 0;
}
} else {
raise_warning("Error in TIFF: filesize(x%04lX) less than "
"start of IFD dir(x%04lX)",
ImageInfo->FileSize, dir_offset+2);
return 0;
}
}
/* Parse the marker stream until SOS or EOI is seen; */
static int exif_scan_FILE_header(image_info_type *ImageInfo) {
unsigned char *file_header;
int ret = 0;
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
if (ImageInfo->FileSize >= 2) {
ImageInfo->infile->seek(0, SEEK_SET);
String fileHeader = ImageInfo->infile->read(2);
if (fileHeader.length() != 2) {
return 0;
}
file_header = (unsigned char *)fileHeader.c_str();
if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) {
ImageInfo->FileType = IMAGE_FILETYPE_JPEG;
if (exif_scan_JPEG_header(ImageInfo)) {
ret = 1;
} else {
raise_warning("Invalid JPEG file");
}
} else if (ImageInfo->FileSize >= 8) {
String str = ImageInfo->infile->read(6);
if (str.length() != 6) {
return 0;
}
fileHeader += str;
file_header = (unsigned char *)fileHeader.c_str();
if (!memcmp(file_header, "II\x2A\x00", 4)) {
ImageInfo->FileType = IMAGE_FILETYPE_TIFF_II;
ImageInfo->motorola_intel = 0;
ImageInfo->sections_found |= FOUND_IFD0;
if (exif_process_IFD_in_TIFF(ImageInfo,
php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel),
SECTION_IFD0)) {
ret = 1;
} else {
raise_warning("Invalid TIFF file");
}
} else if (!memcmp(file_header, "MM\x00\x2a", 4)) {
ImageInfo->FileType = IMAGE_FILETYPE_TIFF_MM;
ImageInfo->motorola_intel = 1;
ImageInfo->sections_found |= FOUND_IFD0;
if (exif_process_IFD_in_TIFF(ImageInfo,
php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel),
SECTION_IFD0)) {
ret = 1;
} else {
raise_warning("Invalid TIFF file");
}
} else {
raise_warning("File not supported");
return 0;
}
}
} else {
raise_warning("File too small (%lu)", ImageInfo->FileSize);
}
return ret;
}
static int exif_read_file(image_info_type *ImageInfo, String FileName,
bool read_thumbnail, bool read_all) {
int ret;
struct stat st;
/* Start with an empty image information structure. */
memset(ImageInfo, 0, sizeof(*ImageInfo));
ImageInfo->motorola_intel = -1; /* flag as unknown */
Variant stream = f_fopen(FileName, "rb");
if (same(stream, false)) {
raise_warning("Unable to open file %s", FileName.c_str());
return 0;
}
ImageInfo->infile = Object(stream).getTyped<File>();
PlainFile *plain_file = dynamic_cast<PlainFile*>(ImageInfo->infile);
if (plain_file) {
if (stat(FileName.c_str(), &st) >= 0) {
if ((st.st_mode & S_IFMT) != S_IFREG) {
raise_warning("Not a file");
return 0;
}
}
/* Store file date/time. */
ImageInfo->FileDateTime = st.st_mtime;
ImageInfo->FileSize = st.st_size;
} else {
if (!ImageInfo->FileSize) {
f_fseek(stream, 0, SEEK_END);
ImageInfo->FileSize = ImageInfo->infile->tell();
f_fseek(stream, 0, SEEK_SET);
}
}
ImageInfo->FileName = f_basename(FileName);
ImageInfo->read_thumbnail = read_thumbnail;
ImageInfo->read_all = read_all;
ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_UNKNOWN;
PHP_STRDUP(ImageInfo->encode_unicode, "ISO-8859-15");
PHP_STRDUP(ImageInfo->decode_unicode_be, "UCS-2BE");
PHP_STRDUP(ImageInfo->decode_unicode_le, "UCS-2LE");
PHP_STRDUP(ImageInfo->encode_jis, "");
PHP_STRDUP(ImageInfo->decode_jis_be, "JIS");
PHP_STRDUP(ImageInfo->decode_jis_le, "JIS");
ImageInfo->ifd_nesting_level = 0;
/* Scan the JPEG headers. */
ret = exif_scan_FILE_header(ImageInfo);
f_fclose(stream);
return ret;
}
/* Free memory allocated for image_info */
static void exif_iif_free(image_info_type *image_info, int section_index) {
int i;
void *f; /* faster */
if (image_info->info_list[section_index].count) {
for (i=0; i < image_info->info_list[section_index].count; i++) {
if ((f=image_info->info_list[section_index].list[i].name) != NULL) {
IM_FREE(f);
}
switch(image_info->info_list[section_index].list[i].format) {
case TAG_FMT_SBYTE:
case TAG_FMT_BYTE:
/* in contrast to strings bytes do not need to allocate
buffer for NULL if length==0 */
if (image_info->info_list[section_index].list[i].length<1)
break;
default:
case TAG_FMT_UNDEFINED:
case TAG_FMT_STRING:
if ((f=image_info->info_list[section_index].list[i].value.s)
!= NULL) {
IM_FREE(f);
}
break;
case TAG_FMT_USHORT:
case TAG_FMT_ULONG:
case TAG_FMT_URATIONAL:
case TAG_FMT_SSHORT:
case TAG_FMT_SLONG:
case TAG_FMT_SRATIONAL:
case TAG_FMT_SINGLE:
case TAG_FMT_DOUBLE:
/* nothing to do here */
if (image_info->info_list[section_index].list[i].length > 1) {
if ((f=image_info->info_list[section_index].list[i].value.list)
!= NULL) {
IM_FREE(f);
}
}
break;
}
}
}
if (image_info->info_list[section_index].list) {
IM_FREE(image_info->info_list[section_index].list);
}
}
/* Discard all file_sections in ImageInfo */
static int exif_file_sections_free(image_info_type *ImageInfo) {
int i;
if (ImageInfo->file.count) {
for (i=0; i<ImageInfo->file.count; i++) {
if (ImageInfo->file.list[i].data) {
IM_FREE(ImageInfo->file.list[i].data);
}
}
}
if (ImageInfo->file.list) IM_FREE(ImageInfo->file.list);
ImageInfo->file.count = 0;
return 1;
}
/* Discard data scanned by exif_read_file. */
static int exif_discard_imageinfo(image_info_type *ImageInfo) {
int i;
if (ImageInfo->UserComment) IM_FREE(ImageInfo->UserComment);
if (ImageInfo->UserCommentEncoding) {
IM_FREE(ImageInfo->UserCommentEncoding);
}
if (ImageInfo->Copyright) IM_FREE(ImageInfo->Copyright);
if (ImageInfo->CopyrightPhotographer) {
IM_FREE(ImageInfo->CopyrightPhotographer);
}
if (ImageInfo->CopyrightEditor) IM_FREE(ImageInfo->CopyrightEditor);
if (ImageInfo->Thumbnail.data) IM_FREE(ImageInfo->Thumbnail.data);
if (ImageInfo->encode_unicode) IM_FREE(ImageInfo->encode_unicode);
if (ImageInfo->decode_unicode_be) {
IM_FREE(ImageInfo->decode_unicode_be);
}
if (ImageInfo->decode_unicode_le) {
IM_FREE(ImageInfo->decode_unicode_le);
}
if (ImageInfo->encode_jis) IM_FREE(ImageInfo->encode_jis);
if (ImageInfo->decode_jis_be) IM_FREE(ImageInfo->decode_jis_be);
if (ImageInfo->decode_jis_le) IM_FREE(ImageInfo->decode_jis_le);
if (ImageInfo->make) IM_FREE(ImageInfo->make);
if (ImageInfo->model) IM_FREE(ImageInfo->model);
for (i=0; i<ImageInfo->xp_fields.count; i++) {
if (ImageInfo->xp_fields.list[i].value) {
IM_FREE(ImageInfo->xp_fields.list[i].value);
}
}
if (ImageInfo->xp_fields.list) IM_FREE(ImageInfo->xp_fields.list);
for (i=0; i<SECTION_COUNT; i++) {
exif_iif_free(ImageInfo, i);
}
exif_file_sections_free(ImageInfo);
memset(ImageInfo, 0, sizeof(*ImageInfo));
return 1;
}
/* Add an int value to image_info */
static void exif_iif_add_int(image_info_type *image_info, int section_index,
char *name, int value) {
image_info_data *info_data;
image_info_data *list;
size_t realloc_size = (image_info->info_list[section_index].count+1) *
sizeof(image_info_data);
list = (image_info_data *)
IM_REALLOC(image_info->info_list[section_index].list, realloc_size);
CHECK_ALLOC(list, realloc_size);
image_info->info_list[section_index].list = list;
info_data = &image_info->info_list[section_index].
list[image_info->info_list[section_index].count];
memset(info_data, 0, sizeof(image_info_data));
info_data->tag = (unsigned short)TAG_NONE;
info_data->format = TAG_FMT_SLONG;
info_data->length = 1;
PHP_STRDUP(info_data->name, name);
info_data->value.i = value;
image_info->sections_found |= 1<<section_index;
image_info->info_list[section_index].count++;
}
/* Add a string value to image_info MUST BE NUL TERMINATED */
static void exif_iif_add_str(image_info_type *image_info,
int section_index, char *name, char *value) {
image_info_data *info_data;
image_info_data *list;
if (value) {
size_t realloc_size = (image_info->info_list[section_index].count+1) *
sizeof(image_info_data);
list = (image_info_data *)
IM_REALLOC(image_info->info_list[section_index].list, realloc_size);
CHECK_ALLOC(list, realloc_size);
image_info->info_list[section_index].list = list;
info_data = &image_info->info_list[section_index].
list[image_info->info_list[section_index].count];
memset(info_data, 0, sizeof(image_info_data));
info_data->tag = (unsigned short)TAG_NONE;
info_data->format = TAG_FMT_STRING;
info_data->length = 1;
PHP_STRDUP(info_data->name, name);
// TODO
// if (PG(magic_quotes_runtime)) {
// info_data->value.s = php_addslashes(value, strlen(value), NULL, 0);
// } else {
PHP_STRDUP(info_data->value.s, value);
image_info->sections_found |= 1<<section_index;
image_info->info_list[section_index].count++;
}
}
/* Add a format string value to image_info MUST BE NUL TERMINATED */
static void exif_iif_add_fmt(image_info_type *image_info, int section_index,
char *name, char *value, ...) {
va_list arglist;
va_start(arglist, value);
if (value) {
char *tmp = 0;
php_vspprintf_ap(&tmp, 0, value, arglist);
exif_iif_add_str(image_info, section_index, name, tmp);
if (tmp) IM_FREE(tmp);
}
va_end(arglist);
}
/* Add a string value to image_info MUST BE NUL TERMINATED */
static void exif_iif_add_buffer(image_info_type *image_info,
int section_index, char *name,
int length, char *value) {
image_info_data *info_data;
image_info_data *list;
if (value) {
size_t realloc_size = (image_info->info_list[section_index].count+1) *
sizeof(image_info_data);
list = (image_info_data *)
IM_REALLOC(image_info->info_list[section_index].list, realloc_size);
CHECK_ALLOC(list, realloc_size);
image_info->info_list[section_index].list = list;
info_data = &image_info->info_list[section_index].
list[image_info->info_list[section_index].count];
memset(info_data, 0, sizeof(image_info_data));
info_data->tag = (unsigned short)TAG_NONE;
info_data->format = TAG_FMT_UNDEFINED;
info_data->length = length;
PHP_STRDUP(info_data->name, name);
// if (PG(magic_quotes_runtime)) {
// info_data->value.s = php_addslashes(value, length, &length, 0);
// info_data->length = length;
// } else {
info_data->value.s = (char *)IM_MALLOC(length + 1);
if (!info_data->value.s) info_data->length = 0;
CHECK_ALLOC(info_data->value.s, length + 1);
memcpy(info_data->value.s, value, length);
info_data->value.s[length] = 0;
image_info->sections_found |= 1<<section_index;
image_info->info_list[section_index].count++;
}
}
/* scan JPEG in thumbnail (memory) */
static int exif_scan_thumbnail(image_info_type *ImageInfo) {
uchar c, *data = (uchar*)ImageInfo->Thumbnail.data;
int n, marker;
size_t length=2, pos=0;
jpeg_sof_info sof_info;
if (!data) {
return 0; /* nothing to do here */
}
if (memcmp(data, "\xFF\xD8\xFF", 3)) {
if (!ImageInfo->Thumbnail.width && !ImageInfo->Thumbnail.height) {
raise_warning("Thumbnail is not a JPEG image");
}
return 0;
}
for (;;) {
pos += length;
if (pos>=ImageInfo->Thumbnail.size)
return 0;
c = data[pos++];
if (pos>=ImageInfo->Thumbnail.size)
return 0;
if (c != 0xFF) {
return 0;
}
n = 8;
while ((c = data[pos++]) == 0xFF && n--) {
if (pos+3>=ImageInfo->Thumbnail.size)
return 0;
/* +3 = pos++ of next check when reaching marker + 2 bytes for length */
}
if (c == 0xFF)
return 0;
marker = c;
length = php_jpg_get16(data+pos);
if (pos+length>=ImageInfo->Thumbnail.size) {
return 0;
}
switch (marker) {
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:
/* handle SOFn block */
exif_process_SOFn(data+pos, marker, &sof_info);
ImageInfo->Thumbnail.height = sof_info.height;
ImageInfo->Thumbnail.width = sof_info.width;
return 1;
case M_SOS:
case M_EOI:
raise_warning("Could not compute size of thumbnail");
return 0;
break;
default:
/* just skip */
break;
}
}
raise_warning("Could not compute size of thumbnail");
return 0;
}
/* Add image_info to associative array value. */
static void add_assoc_image_info(Array &value, bool sub_array,
image_info_type *image_info,
int section_index) {
char buffer[64], *val, *name, uname[64];
int i, ap, l, b, idx=0, unknown=0;
image_info_value *info_value;
image_info_data *info_data;
Array tmp;
Array *tmpi = &tmp;
Array array;
if (image_info->info_list[section_index].count) {
if (!sub_array) {
tmpi = &value;
}
for(i=0; i<image_info->info_list[section_index].count; i++) {
info_data = &image_info->info_list[section_index].list[i];
info_value = &info_data->value;
if (!(name = info_data->name)) {
snprintf(uname, sizeof(uname), "%d", unknown++);
name = uname;
}
if (info_data->length==0) {
tmpi->set(String(name, CopyString), uninit_null());
} else {
switch (info_data->format) {
default:
/* Standard says more types possible but skip them...
* but allow users to handle data if they know how to
* So not return but use type UNDEFINED
* return;
*/
case TAG_FMT_BYTE:
case TAG_FMT_SBYTE:
case TAG_FMT_UNDEFINED:
if (!info_value->s) {
tmpi->set(String(name, CopyString), "");
} else {
tmpi->set(String(name, CopyString),
String(info_value->s, info_data->length, CopyString));
}
break;
case TAG_FMT_STRING:
if (!(val = info_value->s)) {
val = "";
}
if (section_index==SECTION_COMMENT) {
tmpi->set(idx++, String(val, CopyString));
} else {
tmpi->set(String(name, CopyString), String(val, CopyString));
}
break;
case TAG_FMT_URATIONAL:
case TAG_FMT_SRATIONAL:
/*case TAG_FMT_BYTE:
case TAG_FMT_SBYTE:*/
case TAG_FMT_USHORT:
case TAG_FMT_SSHORT:
case TAG_FMT_SINGLE:
case TAG_FMT_DOUBLE:
case TAG_FMT_ULONG:
case TAG_FMT_SLONG:
/* now the rest, first see if it becomes an array */
if ((l = info_data->length) > 1) {
array.clear();
}
for(ap=0; ap<l; ap++) {
if (l>1) {
info_value = &info_data->value.list[ap];
}
switch (info_data->format) {
case TAG_FMT_BYTE:
if (l>1) {
info_value = &info_data->value;
for (b=0;b<l;b++) {
array.set(b, (int)(info_value->s[b]));
}
break;
}
case TAG_FMT_USHORT:
case TAG_FMT_ULONG:
if (l==1) {
tmpi->set(String(name, CopyString), (int)info_value->u);
} else {
array.set(ap, (int)info_value->u);
}
break;
case TAG_FMT_URATIONAL:
snprintf(buffer, sizeof(buffer), "%i/%i",
info_value->ur.num, info_value->ur.den);
if (l==1) {
tmpi->set(String(name, CopyString),
String(buffer, CopyString));
} else {
array.set(ap, String(buffer, CopyString));
}
break;
case TAG_FMT_SBYTE:
if (l>1) {
info_value = &info_data->value;
for (b=0;b<l;b++) {
array.set(ap, (int)info_value->s[b]);
}
break;
}
case TAG_FMT_SSHORT:
case TAG_FMT_SLONG:
if (l==1) {
tmpi->set(String(name, CopyString), info_value->i);
} else {
array.set(ap, info_value->i);
}
break;
case TAG_FMT_SRATIONAL:
snprintf(buffer, sizeof(buffer), "%i/%i",
info_value->sr.num, info_value->sr.den);
if (l==1) {
tmpi->set(String(name, CopyString),
String(buffer, CopyString));
} else {
array.set(ap, String(buffer, CopyString));
}
break;
case TAG_FMT_SINGLE:
if (l==1) {
tmpi->set(String(name, CopyString), info_value->f);
} else {
array.set(ap, info_value->f);
}
break;
case TAG_FMT_DOUBLE:
if (l==1) {
tmpi->set(String(name, CopyString), info_value->d);
} else {
array.set(ap, info_value->d);
}
break;
}
info_value = &info_data->value.list[ap];
}
if (l>1) {
tmpi->set(String(name, CopyString), array);
}
break;
}
}
}
if (sub_array) {
value.set(exif_get_sectionname(section_index), tmp);
}
}
}
Variant f_exif_tagname(int index) {
char *szTemp;
szTemp = exif_get_tagname(index, NULL, 0, tag_table_IFD);
if (index <0 || !szTemp || !szTemp[0]) {
return false;
} else {
return String(szTemp, CopyString);
}
}
Variant f_exif_read_data(CStrRef filename,
CStrRef sections /* = null_string */,
bool arrays /* = false */,
bool thumbnail /* = false */) {
int i, ret, sections_needed=0;
image_info_type ImageInfo;
char tmp[64], *sections_str, *s;
memset(&ImageInfo, 0, sizeof(ImageInfo));
if (!sections.isNull()) {
php_vspprintf(&sections_str, 0, ",%s,", sections.c_str());
/* sections_str DOES start with , and SPACES are NOT allowed in names */
s = sections_str;
while(*++s) {
if(*s==' ') {
*s = ',';
}
}
for (i=0; i<SECTION_COUNT; i++) {
snprintf(tmp, sizeof(tmp), ",%s,", exif_get_sectionname(i).c_str());
if (strstr(sections_str, tmp)) {
sections_needed |= 1<<i;
}
}
if (sections_str) IM_FREE(sections_str);
}
ret = exif_read_file(&ImageInfo, filename, thumbnail, 0);
sections_str = exif_get_sectionlist(ImageInfo.sections_found);
/* do not inform about in debug*/
ImageInfo.sections_found |= FOUND_COMPUTED|FOUND_FILE;
if (ret==0|| (sections_needed &&
!(sections_needed&ImageInfo.sections_found))) {
exif_discard_imageinfo(&ImageInfo);
if (sections_str) IM_FREE(sections_str);
return false;
}
/* now we can add our information */
exif_iif_add_str(&ImageInfo, SECTION_FILE, "FileName",
(char *)ImageInfo.FileName.c_str());
exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileDateTime",
ImageInfo.FileDateTime);
exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileSize",
ImageInfo.FileSize);
exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileType",
ImageInfo.FileType);
exif_iif_add_str(&ImageInfo, SECTION_FILE, "MimeType",
(char*)php_image_type_to_mime_type(ImageInfo.FileType));
exif_iif_add_str(&ImageInfo, SECTION_FILE, "SectionsFound",
sections_str ? sections_str : (char *)"NONE");
if (ImageInfo.Width>0 && ImageInfo.Height>0) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "html",
"width=\"%d\" height=\"%d\"",
ImageInfo.Width, ImageInfo.Height);
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Height",
ImageInfo.Height);
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Width",
ImageInfo.Width);
}
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "IsColor",
ImageInfo.IsColor);
if (ImageInfo.motorola_intel != -1) {
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "ByteOrderMotorola",
ImageInfo.motorola_intel);
}
if (ImageInfo.FocalLength) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocalLength",
"%4.1Fmm", ImageInfo.FocalLength);
if(ImageInfo.CCDWidth) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "35mmFocalLength",
"%dmm",
(int)(ImageInfo.FocalLength/ImageInfo.CCDWidth*35+0.5));
}
}
if(ImageInfo.CCDWidth) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "CCDWidth",
"%dmm", (int)ImageInfo.CCDWidth);
}
if(ImageInfo.ExposureTime>0) {
if(ImageInfo.ExposureTime <= 0.5) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime",
"%0.3F s (1/%d)", ImageInfo.ExposureTime,
(int)(0.5 + 1/ImageInfo.ExposureTime));
} else {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime",
"%0.3F s", ImageInfo.ExposureTime);
}
}
if(ImageInfo.ApertureFNumber) {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ApertureFNumber",
"f/%.1F", ImageInfo.ApertureFNumber);
}
if(ImageInfo.Distance) {
if(ImageInfo.Distance<0) {
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "FocusDistance",
"Infinite");
} else {
exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocusDistance",
"%0.2Fm", ImageInfo.Distance);
}
}
if (ImageInfo.UserComment) {
exif_iif_add_buffer(&ImageInfo, SECTION_COMPUTED, "UserComment",
ImageInfo.UserCommentLength, ImageInfo.UserComment);
if (ImageInfo.UserCommentEncoding &&
strlen(ImageInfo.UserCommentEncoding)) {
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "UserCommentEncoding",
ImageInfo.UserCommentEncoding);
}
}
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright",
ImageInfo.Copyright);
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Photographer",
ImageInfo.CopyrightPhotographer);
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Editor",
ImageInfo.CopyrightEditor);
for (i=0; i<ImageInfo.xp_fields.count; i++) {
exif_iif_add_str(&ImageInfo, SECTION_WINXP,
exif_get_tagname(ImageInfo.xp_fields.list[i].tag,
NULL, 0, exif_get_tag_table(SECTION_WINXP)),
ImageInfo.xp_fields.list[i].value);
}
if (ImageInfo.Thumbnail.size) {
if (thumbnail) {
/* not exif_iif_add_str : this is a buffer */
exif_iif_add_tag(&ImageInfo, SECTION_THUMBNAIL, "THUMBNAIL",
TAG_NONE, TAG_FMT_UNDEFINED, ImageInfo.Thumbnail.size,
ImageInfo.Thumbnail.data);
}
if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) {
/* try to evaluate if thumbnail data is present */
exif_scan_thumbnail(&ImageInfo);
}
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.FileType",
ImageInfo.Thumbnail.filetype);
exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Thumbnail.MimeType",
(char*)php_image_type_to_mime_type(ImageInfo.Thumbnail.filetype));
}
if (ImageInfo.Thumbnail.width && ImageInfo.Thumbnail.height) {
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.Height",
ImageInfo.Thumbnail.height);
exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.Width",
ImageInfo.Thumbnail.width);
}
if (sections_str) IM_FREE(sections_str);
Array retarr;
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_FILE);
add_assoc_image_info(retarr, true, &ImageInfo,
SECTION_COMPUTED);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_ANY_TAG);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_IFD0);
add_assoc_image_info(retarr, true, &ImageInfo,
SECTION_THUMBNAIL);
add_assoc_image_info(retarr, true, &ImageInfo,
SECTION_COMMENT);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_EXIF);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_GPS);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_INTEROP);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_FPIX);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_APP12);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_WINXP);
add_assoc_image_info(retarr, arrays, &ImageInfo,
SECTION_MAKERNOTE);
exif_discard_imageinfo(&ImageInfo);
return retarr;
}
Variant f_read_exif_data(CStrRef filename,
CStrRef sections /* = null_string */,
bool arrays /* = false */,
bool thumbnail /* = false */) {
return f_exif_read_data(filename, sections, arrays, thumbnail);
}
Variant f_exif_thumbnail(CStrRef filename, VRefParam width /* = null */,
VRefParam height /* = null */,
VRefParam imagetype /* = null */) {
image_info_type ImageInfo;
memset(&ImageInfo, 0, sizeof(ImageInfo));
int ret = exif_read_file(&ImageInfo, filename.c_str(), 1, 0);
if (ret==0) {
exif_discard_imageinfo(&ImageInfo);
return false;
}
if (!ImageInfo.Thumbnail.data || !ImageInfo.Thumbnail.size) {
exif_discard_imageinfo(&ImageInfo);
return false;
}
if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) {
exif_scan_thumbnail(&ImageInfo);
}
width = (int64_t)ImageInfo.Thumbnail.width;
height = (int64_t)ImageInfo.Thumbnail.height;
imagetype = ImageInfo.Thumbnail.filetype;
String str(ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size, CopyString);
exif_discard_imageinfo(&ImageInfo);
return str;
}
Variant f_exif_imagetype(CStrRef filename) {
Variant stream = f_fopen(filename, "rb");
if (same(stream, false)) {
raise_warning("failed to open file: %s", filename.c_str());
return false;
}
int itype = php_getimagetype(stream);
f_fclose(stream);
if (itype == IMAGE_FILETYPE_UNKNOWN) return false;
return itype;
}
///////////////////////////////////////////////////////////////////////////////
}