From 840d327139f9ec100604a90bac3e1f043a43de0f Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 13 Feb 2003 07:16:55 +0000 Subject: [PATCH] - MFH: improved jpeg 2000 and wbmp support for getimagesize() --- ext/standard/image.c | 370 +++++++++++++++--- ext/standard/php_image.h | 5 +- ext/standard/tests/image/bug13213.phpt | 4 +- ext/standard/tests/image/getimagesize.phpt | 25 +- .../tests/image/image_type_to_mime_type.phpt | 6 +- 5 files changed, 356 insertions(+), 54 deletions(-) diff --git a/ext/standard/image.c b/ext/standard/image.c index eefec586da..27d7ba60b7 100644 --- a/ext/standard/image.c +++ b/ext/standard/image.c @@ -46,10 +46,13 @@ PHPAPI const char php_sig_swf[3] = {'F', 'W', 'S'}; PHPAPI const char php_sig_swc[3] = {'C', 'W', 'S'}; PHPAPI const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff}; PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47, -(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a}; + (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a}; PHPAPI const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00}; PHPAPI const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A}; -PHPAPI const char php_sig_jpc[3] = {(char)0xFF, (char)0x4F, (char)0xff}; +PHPAPI const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff}; +PHPAPI 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}; PHPAPI const char php_sig_iff[4] = {'F','O','R','M'}; /* REMEMBER TO ADD MIME-TYPE TO FUNCTION php_image_type_to_mime_type */ @@ -84,6 +87,9 @@ PHP_MINIT_FUNCTION(imagetypes) REGISTER_LONG_CONSTANT("IMAGETYPE_SWC", IMAGE_FILETYPE_SWC, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("IMAGETYPE_IFF", IMAGE_FILETYPE_IFF, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMAGETYPE_WBMP", IMAGE_FILETYPE_WBMP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMAGETYPE_JPEG2000",IMAGE_FILETYPE_JPC, CONST_CS | CONST_PERSISTENT); /* keep alias */ + REGISTER_LONG_CONSTANT("IMAGETYPE_XBM", IMAGE_FILETYPE_XBM, CONST_CS | CONST_PERSISTENT); return SUCCESS; } /* }}} */ @@ -210,10 +216,6 @@ static struct gfxinfo *php_handle_swc(php_stream * stream TSRMLS_DC) do { szlength=slength*(1<bits = php_stream_getc(stream); result->height = php_read2(stream TSRMLS_CC); @@ -533,12 +532,6 @@ static struct gfxinfo *php_handle_jpeg (php_stream * stream, pval *info TSRMLS_D } /* }}} */ -/* {{{ jpeg2000 constants - See ext/exif for more */ -#define JC_SOC 0x4F /* Start of codestream */ -#define JC_SIZ 0x51 /* Image and tile size */ -/* }}} */ - /* {{{ php_read4 */ static unsigned int php_read4(php_stream * stream TSRMLS_DC) @@ -555,37 +548,140 @@ static unsigned int php_read4(php_stream * stream TSRMLS_DC) } /* }}} */ -/* {{{ php_handle_tiff - main loop to parse TIFF structure */ +/* {{{ 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 */ +/* }}} */ + +/* {{{ php_handle_jpc + Main loop to parse JPEG2000 raw codestream structure */ static struct gfxinfo *php_handle_jpc(php_stream * stream TSRMLS_DC) { struct gfxinfo *result = NULL; - unsigned int marker, dummy; - unsigned short length, ff_read = 1; + unsigned short dummy_short; + int dummy_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 */ + first_marker_id = php_stream_getc(stream); + + /* Ensure that this marker is SIZ (as is mandated by the standard) */ + if (first_marker_id != JPEG2000_MARKER_SIZ) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "JPEG2000 codestream corrupt(Expected SIZ marker not found after SOC)"); + return NULL; + } + + result = (struct gfxinfo *)ecalloc(1, sizeof(struct gfxinfo)); + + dummy_short = php_read2(stream TSRMLS_CC); /* Lsiz */ + dummy_short = php_read2(stream TSRMLS_CC); /* Rsiz */ + result->height = php_read4(stream TSRMLS_CC); /* Xsiz */ + result->width = php_read4(stream TSRMLS_CC); /* Ysiz */ + + dummy_int = php_read4(stream TSRMLS_CC); /* XOsiz */ + dummy_int = php_read4(stream TSRMLS_CC); /* YOsiz */ + dummy_int = php_read4(stream TSRMLS_CC); /* XTsiz */ + dummy_int = php_read4(stream TSRMLS_CC); /* YTsiz */ + dummy_int = php_read4(stream TSRMLS_CC); /* XTOsiz */ + dummy_int = php_read4(stream TSRMLS_CC); /* YTOsiz */ + + result->channels = php_read2(stream TSRMLS_CC); /* Csiz */ + + /* Collect bit depth info */ + highest_bit_depth = bit_depth = 0; + for (i = 0; i < result->channels; i++) { + bit_depth = php_stream_getc(stream); /* Ssiz[i] */ + bit_depth++; + if (bit_depth > highest_bit_depth) { + highest_bit_depth = bit_depth; + } + + php_stream_getc(stream); /* XRsiz[i] */ + php_stream_getc(stream); /* YRsiz[i] */ + } + + result->bits = highest_bit_depth; - marker = php_next_marker(stream, 0, 0, ff_read TSRMLS_CC); - ff_read = 0; - if ( marker == JC_SIZ) + return result; +} +/* }}} */ + +/* {{{ php_handle_jp2 + main loop to parse JPEG 2000 JP2 wrapper format structure */ +static struct gfxinfo *php_handle_jp2(php_stream *stream TSRMLS_DC) +{ + 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 (;;) { - length = php_read2(stream TSRMLS_CC); /* Lsiz: length of segment */ - if ( length<42 || length>49191) /* read the spec */ - return NULL; - result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo)); - if ( !result) + box_length = php_read4(stream TSRMLS_CC); /* LBox */ + /* TBox */ + if (php_stream_read(stream, (void *)&box_type, sizeof(box_type)) != sizeof(box_type)) { + /* Use this as a general "out of stream" error */ + break; + } + + if (box_length == 1) { + /* We won't handle XLBoxes */ return NULL; - dummy = php_read2(stream TSRMLS_CC); /* Rsiz: capabilities */ - result->height = php_read4(stream TSRMLS_CC); /* Xsiz */ - result->width = php_read4(stream TSRMLS_CC); /* Ysiz */ - dummy = php_read4(stream TSRMLS_CC); /* X0siz */ - dummy = php_read4(stream TSRMLS_CC); /* Y0siz */ - dummy = php_read4(stream TSRMLS_CC); /* XTsiz */ - dummy = php_read4(stream TSRMLS_CC); /* YTsiz */ - dummy = php_read4(stream TSRMLS_CC); /* XT0siz */ - dummy = php_read4(stream TSRMLS_CC); /* YT0siz */ - result->bits = php_read2(stream TSRMLS_CC); /* Csiz: precision in bitss */ - result->channels = 0; /* don't know yet */ - return result; + } + + if (!memcmp(&box_type, jp2c_box_id, 4)) + { + /* Skip the first 3 bytes to emulate the file type examination */ + php_stream_seek(stream, 3, SEEK_CUR); + + result = php_handle_jpc(stream TSRMLS_CC); + break; + } + + /* Skip over LBox (Which includes both TBox and LBox itself */ + php_stream_seek(stream, box_length - 8, SEEK_CUR); + } + + if (result == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "JP2 file has no codestreams at root level"); } + return result; } /* }}} */ @@ -786,6 +882,146 @@ static struct gfxinfo *php_handle_iff(php_stream * stream TSRMLS_DC) } /* }}} */ +/* {{{ php_get_wbmp + * 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(php_stream *stream, struct gfxinfo **result, int check TSRMLS_DC) +{ + int i, width = 0, height = 0; + + if (php_stream_rewind(stream)) { + return 0; + } + + /* get type */ + if (php_stream_getc(stream) != 0) { + return 0; + } + + /* skip header */ + do { + i = php_stream_getc(stream); + if (i < 0) { + return 0; + } + } while (i & 0x80); + + /* get width */ + do { + i = php_stream_getc(stream); + if (i < 0) { + return 0; + } + width = (width << 7) | (i & 0x7f); + } while (i & 0x80); + + /* get height */ + do { + i = php_stream_getc(stream); + if (i < 0) { + return 0; + } + height = (height << 7) | (i & 0x7f); + } while (i & 0x80); + + if (!check) { + (*result)->width = width; + (*result)->height = height; + } + + return IMAGE_FILETYPE_WBMP; +} +/* }}} */ + +/* {{{ php_handle_wbmp +*/ +static struct gfxinfo *php_handle_wbmp(php_stream * stream TSRMLS_DC) +{ + struct gfxinfo *result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo)); + + if (!php_get_wbmp(stream, &result, 0 TSRMLS_CC)) { + efree(result); + return NULL; + } + + return result; +} +/* }}} */ + +/* {{{ php_get_xbm + */ +#define MAX_XBM_LINE_SIZE 255 +static int php_get_xbm(php_stream *stream, struct gfxinfo **result TSRMLS_DC) +{ + char fline[MAX_XBM_LINE_SIZE]; + char iname[MAX_XBM_LINE_SIZE]; + char *type; + int value; + unsigned int width = 0, height = 0; + + if (result) { + *result = NULL; + } + if (php_stream_rewind(stream)) { + return 0; + } + while (php_stream_gets(stream, fline, MAX_XBM_LINE_SIZE)) { + fline[MAX_XBM_LINE_SIZE-1] = '\0'; + if (strlen(fline) == MAX_XBM_LINE_SIZE-1) { + return 0; + } + + if (sscanf(fline, "#define %s %d", iname, &value) == 2) { + if (!(type = strrchr(iname, '_'))) { + type = iname; + } else { + type++; + } + + if (!strcmp("width", type)) { + width = (unsigned int) value; + if (height) { + break; + } + } + if (!strcmp("height", type)) { + height = (unsigned int) value; + if (width) { + break; + } + } + } + } + + if (width && height) { + if (result) { + *result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo)); + (*result)->width = width; + (*result)->height = height; + } + return IMAGE_FILETYPE_XBM; + } + + return 0; +} +/* }}} */ + +/* {{{ php_handle_xbm + */ +static struct gfxinfo *php_handle_xbm(php_stream * stream TSRMLS_DC) +{ + struct gfxinfo *result; + php_get_xbm(stream, &result TSRMLS_CC); + return result; +} +/* }}} */ + /* {{{ php_image_type_to_mime_type * Convert internal image_type to mime type */ PHPAPI const char * php_image_type_to_mime_type(int image_type) @@ -794,7 +1030,6 @@ PHPAPI const char * php_image_type_to_mime_type(int image_type) case IMAGE_FILETYPE_GIF: return "image/gif"; case IMAGE_FILETYPE_JPEG: - case IMAGE_FILETYPE_JPC: return "image/jpeg"; case IMAGE_FILETYPE_PNG: return "image/png"; @@ -810,6 +1045,14 @@ PHPAPI const char * php_image_type_to_mime_type(int image_type) 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 */ @@ -837,20 +1080,24 @@ PHP_FUNCTION(image_type_to_mime_type) detect filetype from first bytes */ PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC) { - char tmp[8]; + char tmp[12]; if ( !filetype) filetype = tmp; - if((php_stream_read(stream, filetype, 3)) <= 0) { + if((php_stream_read(stream, filetype, 3)) != 3) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!"); return IMAGE_FILETYPE_UNKNOWN; } +/* BYTES READ: 3 */ if (!memcmp(filetype, php_sig_gif, 3)) { return IMAGE_FILETYPE_GIF; } else if (!memcmp(filetype, php_sig_jpg, 3)) { return IMAGE_FILETYPE_JPEG; } else if (!memcmp(filetype, php_sig_png, 3)) { - php_stream_read(stream, filetype+3, 5); + if (php_stream_read(stream, filetype+3, 5) != 5) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!"); + return IMAGE_FILETYPE_UNKNOWN; + } if (!memcmp(filetype, php_sig_png, 8)) { return IMAGE_FILETYPE_PNG; } else { @@ -868,7 +1115,12 @@ PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC) } else if (!memcmp(filetype, php_sig_jpc, 3)) { return IMAGE_FILETYPE_JPC; } - php_stream_read(stream, filetype+3, 1); + + if (php_stream_read(stream, filetype+3, 1) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!"); + return IMAGE_FILETYPE_UNKNOWN; + } +/* BYTES READ: 4 */ if (!memcmp(filetype, php_sig_tif_ii, 4)) { return IMAGE_FILETYPE_TIFF_II; } else @@ -878,7 +1130,23 @@ PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC) if (!memcmp(filetype, php_sig_iff, 4)) { return IMAGE_FILETYPE_IFF; } - + + if (php_stream_read(stream, filetype+4, 8) != 8) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!"); + return IMAGE_FILETYPE_UNKNOWN; + } +/* BYTES READ: 12 */ + if (!memcmp(filetype, php_sig_jp2, 12)) { + return IMAGE_FILETYPE_JP2; + } + +/* AFTER ALL ABOVE FAILED */ + if (php_get_wbmp(stream, NULL, 1 TSRMLS_CC)) { + return IMAGE_FILETYPE_WBMP; + } + if (php_get_xbm(stream, NULL TSRMLS_CC)) { + return IMAGE_FILETYPE_XBM; + } return IMAGE_FILETYPE_UNKNOWN; } /* }}} */ @@ -964,8 +1232,18 @@ PHP_FUNCTION(getimagesize) case IMAGE_FILETYPE_JPC: result = php_handle_jpc(stream TSRMLS_CC); break; + case IMAGE_FILETYPE_JP2: + result = php_handle_jp2(stream TSRMLS_CC); + break; case IMAGE_FILETYPE_IFF: - result = php_handle_iff(stream TSRMLS_CC); + result = php_handle_iff(stream TSRMLS_CC); + break; + case IMAGE_FILETYPE_WBMP: + result = php_handle_wbmp(stream TSRMLS_CC); + break; + case IMAGE_FILETYPE_XBM: + result = php_handle_xbm(stream TSRMLS_CC); + break; default: case IMAGE_FILETYPE_UNKNOWN: break; diff --git a/ext/standard/php_image.h b/ext/standard/php_image.h index 31e291b4d9..0b5cb0171f 100644 --- a/ext/standard/php_image.h +++ b/ext/standard/php_image.h @@ -45,7 +45,10 @@ typedef enum IMAGE_FILETYPE_JPX, IMAGE_FILETYPE_JB2, IMAGE_FILETYPE_SWC, - IMAGE_FILETYPE_IFF + IMAGE_FILETYPE_IFF, + IMAGE_FILETYPE_WBMP, + /* IMAGE_FILETYPE_JPEG2000 is a userland alias for IMAGE_FILETYPE_JPC */ + IMAGE_FILETYPE_XBM /* WHEN EXTENDING: PLEASE ALSO REGISTER IN image.c:PHP_MINIT_FUNCTION(imagetypes) */ } image_filetype; /* }}} */ diff --git a/ext/standard/tests/image/bug13213.phpt b/ext/standard/tests/image/bug13213.phpt index c14ce36017..c97b7016b4 100644 --- a/ext/standard/tests/image/bug13213.phpt +++ b/ext/standard/tests/image/bug13213.phpt @@ -1,5 +1,5 @@ --TEST-- -Bug #13213 (GetImageSize and wrong JPEG Comments +Bug #13213 (GetImageSize and wrong JPEG Comments) --FILE-- string(10) "image/jpeg" -} \ No newline at end of file +} diff --git a/ext/standard/tests/image/getimagesize.phpt b/ext/standard/tests/image/getimagesize.phpt index 9d28acf7ac..46003cffac 100644 --- a/ext/standard/tests/image/getimagesize.phpt +++ b/ext/standard/tests/image/getimagesize.phpt @@ -23,7 +23,7 @@ GetImageSize() var_dump($result); ?> --EXPECT-- -array(10) { +array(11) { ["test1pix.bmp"]=> array(6) { [0]=> @@ -39,8 +39,25 @@ array(10) { ["mime"]=> string(9) "image/bmp" } + ["test1pix.jp2"]=> + array(7) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + int(10) + [3]=> + string(20) "width="1" height="1"" + ["bits"]=> + int(8) + ["channels"]=> + int(3) + ["mime"]=> + string(9) "image/jp2" + } ["test1pix.jpc"]=> - array(6) { + array(7) { [0]=> int(1) [1]=> @@ -50,9 +67,11 @@ array(10) { [3]=> string(20) "width="1" height="1"" ["bits"]=> + int(8) + ["channels"]=> int(3) ["mime"]=> - string(10) "image/jpeg" + string(24) "application/octet-stream" } ["test1pix.jpg"]=> array(7) { diff --git a/ext/standard/tests/image/image_type_to_mime_type.phpt b/ext/standard/tests/image/image_type_to_mime_type.phpt index 0f2eed1e32..94aabba0b9 100644 --- a/ext/standard/tests/image/image_type_to_mime_type.phpt +++ b/ext/standard/tests/image/image_type_to_mime_type.phpt @@ -25,11 +25,13 @@ image_type_to_mime_type() var_dump($result); ?> --EXPECT-- -array(10) { +array(11) { ["test1pix.bmp"]=> string(9) "image/bmp" + ["test1pix.jp2"]=> + string(9) "image/jp2" ["test1pix.jpc"]=> - string(10) "image/jpeg" + string(24) "application/octet-stream" ["test1pix.jpg"]=> string(10) "image/jpeg" ["test2pix.gif"]=> -- 2.40.0