Skip to content

Commit e8c2e83

Browse files
committed
AVIF support in getimagesize()
1 parent 068d35a commit e8c2e83

File tree

1 file changed

+102
-5
lines changed

1 file changed

+102
-5
lines changed

ext/standard/image.c

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,12 +1149,102 @@ static struct gfxinfo *php_handle_webp(php_stream * stream)
11491149
}
11501150
/* }}} */
11511151

1152-
/* {{{ php_handle_avif }}} */
1153-
/* TODO: This is just a stub */
1152+
/* {{{ php_handle_avif */
1153+
/* There's no simple way to get this information - so, for now, this is unsupported.
1154+
Simply return 0 for everything.
1155+
*/
11541156
static struct gfxinfo *php_handle_avif(php_stream * stream) {
1155-
return NULL;
1157+
struct gfxinfo * result;
1158+
1159+
result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo));
1160+
return result;
1161+
}
1162+
/* }}} */
1163+
1164+
/* {{{ php_little2bigendian
1165+
* Reverse the bytes in a little-endian uint32, returning the big-endian equivalent.
1166+
* Adapted lovingly from libavif's read.c.
1167+
*/
1168+
static uint32_t php_little2bigendian(val) {
1169+
uint8_t data[4];
1170+
1171+
memcpy(&data, &val, sizeof(data));
1172+
return ((uint32_t)data[3] << 0) |
1173+
((uint32_t)data[2] << 8) |
1174+
((uint32_t)data[1] << 16) |
1175+
((uint32_t)data[0] << 24);
11561176
}
1177+
/* }}} */
1178+
1179+
/* {{{ php_is_image_avif
1180+
detect whether an image is of type AVIF
1181+
1182+
An AVIF image will start off a header "box".
1183+
This starts with with a four-byte integer containing the number of bytes in the filetype box.
1184+
This must be followed by the string "ftyp".
1185+
Next comes a four-byte string indicating the "major brand".
1186+
If that's "avif" or "avis", this is an AVIF image.
1187+
Next, there's a four-byte "minor version" field, which we can ignore.
1188+
Next comes an array of four-byte strings containing "compatible brands".
1189+
These extend to the end of the box.
1190+
If any of the compatible brands is "avif" or "avis", then this is an AVIF image.
1191+
Otherwise, well, it's not.
1192+
1193+
For more, see https://mpeg.chiariglione.org/standards/mpeg-4/iso-base-media-file-format/text-isoiec-14496-12-5th-edition
1194+
*/
1195+
static int php_is_image_avif(php_stream * stream) {
1196+
uint32_t header_size_reversed, header_size, i;
1197+
char box_type[4], brand[4];
1198+
1199+
if (php_stream_rewind(stream)) {
1200+
return 0;
1201+
}
1202+
1203+
if (php_stream_read(stream, (char *) &header_size_reversed, 4) != 4) {
1204+
return 0;
1205+
}
1206+
1207+
header_size = php_little2bigendian(header_size_reversed);
1208+
1209+
// If the box type isn't "ftyp", it can't be an AVIF image.
1210+
if (php_stream_read(stream, box_type, 4) != 4) {
1211+
return 0;
1212+
}
1213+
1214+
if (memcmp(box_type, "ftyp", 4)) {
1215+
return 0;
1216+
}
1217+
1218+
// If the major brand is "avif" or "avis", it's an AVIF image.
1219+
if (php_stream_read(stream, brand, 4) != 4) {
1220+
return 0;
1221+
}
1222+
1223+
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1224+
return 1;
1225+
}
1226+
1227+
// Skip the next four bytes, which are the "minor version".
1228+
if (php_stream_read(stream, brand, 4) != 4) {
1229+
return 0;
1230+
}
1231+
1232+
// Look for "avif" or "avis" in any member of compatible_brands[], to the end of the header.
1233+
// Note we've already read four groups of four bytes.
1234+
1235+
for (i = 16; i < header_size; i += 4) {
1236+
if (php_stream_read(stream, brand, 4) != 4) {
1237+
return 0;
1238+
}
11571239

1240+
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1241+
return 1;
1242+
}
1243+
}
1244+
1245+
return 0;
1246+
}
1247+
/* }}} */
11581248

11591249
/* {{{ php_image_type_to_mime_type
11601250
* Convert internal image_type to mime type */
@@ -1365,13 +1455,20 @@ PHPAPI int php_getimagetype(php_stream * stream, const char *input, char *filety
13651455
if (php_get_wbmp(stream, NULL, 1)) {
13661456
return IMAGE_FILETYPE_WBMP;
13671457
}
1368-
if (!twelve_bytes_read) {
1458+
1459+
if (php_is_image_avif(stream)) {
1460+
return IMAGE_FILETYPE_AVIF;
1461+
}
1462+
1463+
if (!twelve_bytes_read) {
13691464
php_error_docref(NULL, E_NOTICE, "Error reading from %s!", input);
13701465
return IMAGE_FILETYPE_UNKNOWN;
1371-
}
1466+
}
1467+
13721468
if (php_get_xbm(stream, NULL)) {
13731469
return IMAGE_FILETYPE_XBM;
13741470
}
1471+
13751472
return IMAGE_FILETYPE_UNKNOWN;
13761473
}
13771474
/* }}} */

0 commit comments

Comments
 (0)