Skip to content

Commit 8958762

Browse files
committed
- corrected error in file detection for very small files
- JPEG 2000 support, mostly Adam Wright <adam@elysium.ltd.uk> @enhanced jpeg 2000 support for GetImageSize(). (marcus, Adam Wright)
1 parent b1613e1 commit 8958762

File tree

2 files changed

+175
-42
lines changed

2 files changed

+175
-42
lines changed

ext/standard/image.c

Lines changed: 174 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ PHPAPI const char php_sig_swf[3] = {'F', 'W', 'S'};
4646
PHPAPI const char php_sig_swc[3] = {'C', 'W', 'S'};
4747
PHPAPI const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff};
4848
PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
49-
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
49+
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
5050
PHPAPI const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00};
5151
PHPAPI const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A};
52-
PHPAPI const char php_sig_jpc[3] = {(char)0xFF, (char)0x4F, (char)0xff};
52+
PHPAPI const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff};
53+
PHPAPI const char php_sig_jp2[12] = {(char)0x00, (char)0x00, (char)0x00, (char)0x0c,
54+
(char)0x6a, (char)0x50, (char)0x20, (char)0x20,
55+
(char)0x0d, (char)0x0a, (char)0x87, (char)0x0a};
5356
PHPAPI const char php_sig_iff[4] = {'F','O','R','M'};
5457

5558
/* REMEMBER TO ADD MIME-TYPE TO FUNCTION php_image_type_to_mime_type */
@@ -76,7 +79,7 @@ PHP_MINIT_FUNCTION(imagetypes)
7679
REGISTER_LONG_CONSTANT("IMAGETYPE_BMP", IMAGE_FILETYPE_BMP, CONST_CS | CONST_PERSISTENT);
7780
REGISTER_LONG_CONSTANT("IMAGETYPE_TIFF_II", IMAGE_FILETYPE_TIFF_II, CONST_CS | CONST_PERSISTENT);
7881
REGISTER_LONG_CONSTANT("IMAGETYPE_TIFF_MM", IMAGE_FILETYPE_TIFF_MM, CONST_CS | CONST_PERSISTENT);
79-
REGISTER_LONG_CONSTANT("IMAGETYPE_JPC", IMAGE_FILETYPE_JPC, CONST_CS | CONST_PERSISTENT);
82+
REGISTER_LONG_CONSTANT("IMAGETYPE_JPC", IMAGE_FILETYPE_JPC ,CONST_CS | CONST_PERSISTENT);
8083
REGISTER_LONG_CONSTANT("IMAGETYPE_JP2", IMAGE_FILETYPE_JP2, CONST_CS | CONST_PERSISTENT);
8184
REGISTER_LONG_CONSTANT("IMAGETYPE_JPX", IMAGE_FILETYPE_JPX, CONST_CS | CONST_PERSISTENT);
8285
REGISTER_LONG_CONSTANT("IMAGETYPE_JB2", IMAGE_FILETYPE_JB2, CONST_CS | CONST_PERSISTENT);
@@ -85,6 +88,7 @@ PHP_MINIT_FUNCTION(imagetypes)
8588
#endif
8689
REGISTER_LONG_CONSTANT("IMAGETYPE_IFF", IMAGE_FILETYPE_IFF, CONST_CS | CONST_PERSISTENT);
8790
REGISTER_LONG_CONSTANT("IMAGETYPE_WBMP", IMAGE_FILETYPE_WBMP, CONST_CS | CONST_PERSISTENT);
91+
REGISTER_LONG_CONSTANT("IMAGETYPE_JPEG2000",IMAGE_FILETYPE_JPC ,CONST_CS | CONST_PERSISTENT);/* keep alias */
8892
return SUCCESS;
8993
}
9094
/* }}} */
@@ -534,12 +538,6 @@ static struct gfxinfo *php_handle_jpeg (php_stream * stream, pval *info TSRMLS_D
534538
}
535539
/* }}} */
536540

537-
/* {{{ jpeg2000 constants
538-
See ext/exif for more */
539-
#define JC_SOC 0x4F /* Start of codestream */
540-
#define JC_SIZ 0x51 /* Image and tile size */
541-
/* }}} */
542-
543541
/* {{{ php_read4
544542
*/
545543
static unsigned int php_read4(php_stream * stream TSRMLS_DC)
@@ -556,37 +554,147 @@ static unsigned int php_read4(php_stream * stream TSRMLS_DC)
556554
}
557555
/* }}} */
558556

559-
/* {{{ php_handle_tiff
560-
main loop to parse TIFF structure */
557+
/* {{{ JPEG 2000 Marker Codes */
558+
#define JPEG2000_MARKER_PREFIX 0xFF /* All marker codes start with this */
559+
#define JPEG2000_MARKER_SOC 0x4F /* Start of Codestream */
560+
#define JPEG2000_MARKER_SOT 0x90 /* Start of Tile part */
561+
#define JPEG2000_MARKER_SOD 0x93 /* Start of Data */
562+
#define JPEG2000_MARKER_EOC 0xD9 /* End of Codestream */
563+
#define JPEG2000_MARKER_SIZ 0x51 /* Image and tile size */
564+
#define JPEG2000_MARKER_COD 0x52 /* Coding style default */
565+
#define JPEG2000_MARKER_COC 0x53 /* Coding style component */
566+
#define JPEG2000_MARKER_RGN 0x5E /* Region of interest */
567+
#define JPEG2000_MARKER_QCD 0x5C /* Quantization default */
568+
#define JPEG2000_MARKER_QCC 0x5D /* Quantization component */
569+
#define JPEG2000_MARKER_POC 0x5F /* Progression order change */
570+
#define JPEG2000_MARKER_TLM 0x55 /* Tile-part lengths */
571+
#define JPEG2000_MARKER_PLM 0x57 /* Packet length, main header */
572+
#define JPEG2000_MARKER_PLT 0x58 /* Packet length, tile-part header */
573+
#define JPEG2000_MARKER_PPM 0x60 /* Packed packet headers, main header */
574+
#define JPEG2000_MARKER_PPT 0x61 /* Packed packet headers, tile part header */
575+
#define JPEG2000_MARKER_SOP 0x91 /* Start of packet */
576+
#define JPEG2000_MARKER_EPH 0x92 /* End of packet header */
577+
#define JPEG2000_MARKER_CRG 0x63 /* Component registration */
578+
#define JPEG2000_MARKER_COM 0x64 /* Comment */
579+
/* }}} */
580+
581+
/* {{{ php_handle_jpc
582+
Main loop to parse JPEG2000 raw codestream structure */
561583
static struct gfxinfo *php_handle_jpc(php_stream * stream TSRMLS_DC)
562584
{
563585
struct gfxinfo *result = NULL;
564-
unsigned int marker, dummy;
565-
unsigned short length, ff_read = 1;
586+
unsigned short dummy_short;
587+
int dummy_int, highest_bit_depth, bit_depth;
588+
unsigned char first_marker_id;
589+
int i;
590+
591+
/* JPEG 2000 components can be vastly different from one another.
592+
Each component can be sampled at a different resolution, use
593+
a different colour space, have a seperate colour depth, and
594+
be compressed totally differently! This makes giving a single
595+
"bit depth" answer somewhat problematic. For this implementation
596+
we'll use the highest depth encountered. */
597+
598+
/* Get the single byte that remains after the file type indentification */
599+
first_marker_id = php_stream_getc(stream);
600+
601+
/* Ensure that this marker is SIZ (as is mandated by the standard) */
602+
if (first_marker_id != JPEG2000_MARKER_SIZ) {
603+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "JPEG2000 codestream corrupt(Expected SIZ marker not found after SOC)");
604+
return NULL;
605+
}
606+
607+
result = (struct gfxinfo *)ecalloc(1, sizeof(struct gfxinfo));
608+
if (!result) {
609+
return NULL;
610+
}
611+
612+
dummy_short = php_read2(stream TSRMLS_CC); /* Lsiz */
613+
dummy_short = php_read2(stream TSRMLS_CC); /* Rsiz */
614+
result->height = php_read4(stream TSRMLS_CC); /* Xsiz */
615+
result->width = php_read4(stream TSRMLS_CC); /* Ysiz */
616+
617+
dummy_int = php_read4(stream TSRMLS_CC); /* XOsiz */
618+
dummy_int = php_read4(stream TSRMLS_CC); /* YOsiz */
619+
dummy_int = php_read4(stream TSRMLS_CC); /* XTsiz */
620+
dummy_int = php_read4(stream TSRMLS_CC); /* YTsiz */
621+
dummy_int = php_read4(stream TSRMLS_CC); /* XTOsiz */
622+
dummy_int = php_read4(stream TSRMLS_CC); /* YTOsiz */
623+
624+
result->channels = php_read2(stream TSRMLS_CC); /* Csiz */
625+
626+
/* Collect and average bit depth info */
627+
highest_bit_depth = bit_depth = 0;
628+
for (i = 0; i < result->channels; i++) {
629+
bit_depth = php_stream_getc(stream); /* Ssiz[i] */
630+
bit_depth++;
631+
if (bit_depth > highest_bit_depth) {
632+
highest_bit_depth = bit_depth;
633+
}
634+
635+
php_stream_getc(stream); /* XRsiz[i] */
636+
php_stream_getc(stream); /* YRsiz[i] */
637+
}
638+
639+
result->bits = highest_bit_depth;
566640

567-
marker = php_next_marker(stream, 0, 0, ff_read TSRMLS_CC);
568-
ff_read = 0;
569-
if ( marker == JC_SIZ)
641+
return result;
642+
}
643+
/* }}} */
644+
645+
/* {{{ php_handle_jp2
646+
main loop to parse JPEG 2000 JP2 wrapper format structure */
647+
static struct gfxinfo *php_handle_jp2(php_stream *stream TSRMLS_DC)
648+
{
649+
struct gfxinfo *result = NULL;
650+
unsigned int box_length;
651+
unsigned int box_type;
652+
char jp2c_box_id[] = {(char)0x6a, (char)0x70, (char)0x32, (char)0x63};
653+
654+
/* JP2 is a wrapper format for JPEG 2000. Data is contained within "boxes".
655+
Boxes themselves can be contained within "super-boxes". Super-Boxes can
656+
contain super-boxes which provides us with a hierarchical storage system.
657+
658+
It is valid for a JP2 file to contain multiple individual codestreams.
659+
We'll just look for the first codestream at the root of the box structure
660+
and handle that.
661+
*/
662+
663+
for (;;)
570664
{
571-
length = php_read2(stream TSRMLS_CC); /* Lsiz: length of segment */
572-
if ( length<42 || length>49191) /* read the spec */
573-
return NULL;
574-
result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo));
575-
if ( !result)
665+
box_length = php_read4(stream TSRMLS_CC); /* LBox */
666+
/* TBox */
667+
if (php_stream_read(stream, (void *)&box_type, sizeof(box_type)) != sizeof(box_type)) {
668+
break;
669+
}
670+
671+
/* Safe to use the 0 return for EOF as neither of these can be 0 */
672+
if (box_length == 0 || box_type == 0) {
673+
break;
674+
}
675+
676+
if (box_length == 1) {
677+
/* We won't handle XLBoxes */
576678
return NULL;
577-
dummy = php_read2(stream TSRMLS_CC); /* Rsiz: capabilities */
578-
result->height = php_read4(stream TSRMLS_CC); /* Xsiz */
579-
result->width = php_read4(stream TSRMLS_CC); /* Ysiz */
580-
dummy = php_read4(stream TSRMLS_CC); /* X0siz */
581-
dummy = php_read4(stream TSRMLS_CC); /* Y0siz */
582-
dummy = php_read4(stream TSRMLS_CC); /* XTsiz */
583-
dummy = php_read4(stream TSRMLS_CC); /* YTsiz */
584-
dummy = php_read4(stream TSRMLS_CC); /* XT0siz */
585-
dummy = php_read4(stream TSRMLS_CC); /* YT0siz */
586-
result->bits = php_read2(stream TSRMLS_CC); /* Csiz: precision in bitss */
587-
result->channels = 0; /* don't know yet */
588-
return result;
679+
}
680+
681+
if (!memcmp(&box_type, jp2c_box_id, 4))
682+
{
683+
/* Skip the first 3 bytes to emulate the file type examination */
684+
php_stream_seek(stream, 3, SEEK_CUR);
685+
686+
result = php_handle_jpc(stream TSRMLS_CC);
687+
break;
688+
}
689+
690+
/* Skip over LBox (Which includes both TBox and LBox itself */
691+
php_stream_seek(stream, box_length - 8, SEEK_CUR);
692+
}
693+
694+
if (result == NULL) {
695+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "JP2 file has no codestreams at root level");
589696
}
697+
590698
return result;
591699
}
592700
/* }}} */
@@ -867,7 +975,6 @@ PHPAPI const char * php_image_type_to_mime_type(int image_type)
867975
case IMAGE_FILETYPE_GIF:
868976
return "image/gif";
869977
case IMAGE_FILETYPE_JPEG:
870-
case IMAGE_FILETYPE_JPC:
871978
return "image/jpeg";
872979
case IMAGE_FILETYPE_PNG:
873980
return "image/png";
@@ -885,6 +992,10 @@ PHPAPI const char * php_image_type_to_mime_type(int image_type)
885992
return "image/iff";
886993
case IMAGE_FILETYPE_WBMP:
887994
return "image/vnd.wap.wbmp";
995+
case IMAGE_FILETYPE_JPC:
996+
return "application/octet-stream";
997+
case IMAGE_FILETYPE_JP2:
998+
return "image/jp2";
888999
default:
8891000
case IMAGE_FILETYPE_UNKNOWN:
8901001
return "application/octet-stream"; /* suppose binary format */
@@ -912,20 +1023,24 @@ PHP_FUNCTION(image_type_to_mime_type)
9121023
detect filetype from first bytes */
9131024
PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC)
9141025
{
915-
char tmp[8];
1026+
char tmp[12];
9161027

9171028
if ( !filetype) filetype = tmp;
918-
if((php_stream_read(stream, filetype, 3)) <= 0) {
1029+
if((php_stream_read(stream, filetype, 3)) != 3) {
9191030
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!");
9201031
return IMAGE_FILETYPE_UNKNOWN;
9211032
}
9221033

1034+
/* BYTES READ: 3 */
9231035
if (!memcmp(filetype, php_sig_gif, 3)) {
9241036
return IMAGE_FILETYPE_GIF;
9251037
} else if (!memcmp(filetype, php_sig_jpg, 3)) {
9261038
return IMAGE_FILETYPE_JPEG;
9271039
} else if (!memcmp(filetype, php_sig_png, 3)) {
928-
php_stream_read(stream, filetype+3, 5);
1040+
if (php_stream_read(stream, filetype+3, 5) != 5) {
1041+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!");
1042+
return IMAGE_FILETYPE_UNKNOWN;
1043+
}
9291044
if (!memcmp(filetype, php_sig_png, 8)) {
9301045
return IMAGE_FILETYPE_PNG;
9311046
} else {
@@ -943,7 +1058,12 @@ PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC)
9431058
} else if (!memcmp(filetype, php_sig_jpc, 3)) {
9441059
return IMAGE_FILETYPE_JPC;
9451060
}
946-
php_stream_read(stream, filetype+3, 1);
1061+
1062+
if (php_stream_read(stream, filetype+3, 1) != 1) {
1063+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!");
1064+
return IMAGE_FILETYPE_UNKNOWN;
1065+
}
1066+
/* BYTES READ: 4 */
9471067
if (!memcmp(filetype, php_sig_tif_ii, 4)) {
9481068
return IMAGE_FILETYPE_TIFF_II;
9491069
} else
@@ -956,7 +1076,16 @@ PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC)
9561076
if (php_get_wbmp(stream, NULL, 1 TSRMLS_CC)) {
9571077
return IMAGE_FILETYPE_WBMP;
9581078
}
959-
1079+
1080+
if (php_stream_read(stream, filetype+4, 8) != 8) {
1081+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Read error!");
1082+
return IMAGE_FILETYPE_UNKNOWN;
1083+
}
1084+
/* BYTES READ: 12 */
1085+
if (!memcmp(filetype, php_sig_jp2, 12)) {
1086+
return IMAGE_FILETYPE_JP2;
1087+
}
1088+
9601089
return IMAGE_FILETYPE_UNKNOWN;
9611090
}
9621091
/* }}} */
@@ -1042,10 +1171,13 @@ PHP_FUNCTION(getimagesize)
10421171
case IMAGE_FILETYPE_JPC:
10431172
result = php_handle_jpc(stream TSRMLS_CC);
10441173
break;
1174+
case IMAGE_FILETYPE_JP2:
1175+
result = php_handle_jp2(stream TSRMLS_CC);
1176+
break;
10451177
case IMAGE_FILETYPE_IFF:
1046-
result = php_handle_iff(stream TSRMLS_CC);
1047-
break;
1048-
case IMAGE_FILETYPE_WBMP:
1178+
result = php_handle_iff(stream TSRMLS_CC);
1179+
break;
1180+
case IMAGE_FILETYPE_WBMP:
10491181
result = php_handle_wbmp(stream TSRMLS_CC);
10501182
break;
10511183
default:

ext/standard/php_image.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ typedef enum
4747
IMAGE_FILETYPE_SWC,
4848
IMAGE_FILETYPE_IFF,
4949
IMAGE_FILETYPE_WBMP,
50+
/* IMAGE_FILETYPE_JPEG2000 is a userland alias for IMAGE_FILETYPE_JPC */
5051
/* WHEN EXTENDING: PLEASE ALSO REGISTER IN image.c:PHP_MINIT_FUNCTION(imagetypes) */
5152
} image_filetype;
5253
/* }}} */

0 commit comments

Comments
 (0)