Skip to content

Commit 38460c2

Browse files
y-guyoncmb69
authored andcommitted
Implement php_handle_avif() using libavifinfo
See #80828 and the internals@ mailing list discussion at https://externals.io/message/116543 Use libavifinfo's AvifInfoGetFeaturesStream() in php_handle_avif() to get the width, height, bit depth and channel count from an AVIF payload. Implement stream reading/skipping functions and data struct. Use libavifinfo's AvifInfoIdentifyStream() in php_is_image_avif(). Update the expected features read from "test1pix.avif" in getimagesize.phpt. Closes phpGH-7711.
1 parent 78ef25b commit 38460c2

File tree

12 files changed

+1058
-83
lines changed

12 files changed

+1058
-83
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ PHP NEWS
1717
- Standard:
1818
. net_get_interfaces() also reports wireless network interfaces on Windows.
1919
(Yurun)
20+
. Finished AVIF support in getimagesize(). (Yannis Guyon)
2021

2122
- Zip:
2223
. add ZipArchive::clearError() method

README.REDIST.BINS

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
15. ext/phar/zip.c portion extracted from libzip
1616
16. libbcmath (ext/bcmath) see ext/bcmath/libbcmath/LICENSE
1717
17. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
18+
18. avifinfo (ext/standard/libavifinfo) see ext/standard/libavifinfo/LICENSE
1819

1920

2021
3. pcre2lib (ext/pcre)
@@ -591,7 +592,7 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
591592
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
592593

593594

594-
16. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
595+
17. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
595596

596597
The OpenLDAP Public License
597598
Version 2.8, 17 August 2003

UPGRADING

+5
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ PHP 8.2 UPGRADE NOTES
129129
- OCI8:
130130
. The minimum Oracle Client library version required is now 11.2.
131131

132+
- Standard:
133+
. getimagesize() now reports the actual image dimensions, bits and channels
134+
of AVIF images. Previously, the dimensions have been reported as 0x0, and
135+
bits and channels have not been reported at all.
136+
132137
- Zip:
133138
. extension updated to 1.20.0 with new methods:
134139
ZipArchive::clearError, getStreamName and getStreamIndex

ext/standard/config.m4

+1-1
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32.
460460
http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \
461461
var_unserializer.c ftok.c sha1.c user_filters.c uuencode.c \
462462
filters.c proc_open.c streamsfuncs.c http.c password.c \
463-
random.c net.c hrtime.c crc32_x86.c,,,
463+
random.c net.c hrtime.c crc32_x86.c libavifinfo/avifinfo.c,,,
464464
-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
465465

466466
PHP_ADD_MAKEFILE_FRAGMENT

ext/standard/config.w32

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \
3737
user_filters.c uuencode.c filters.c proc_open.c password.c \
3838
streamsfuncs.c http.c flock_compat.c random.c hrtime.c", false /* never shared */,
3939
'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
40+
ADD_SOURCES("ext/standard/libavifinfo", "avifinfo.c", "standard");
4041
PHP_STANDARD = "yes";
4142
ADD_MAKEFILE_FRAGMENT();
4243
PHP_INSTALL_HEADERS("", "ext/standard");

ext/standard/image.c

+59-77
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#endif
2323
#include "fopen_wrappers.h"
2424
#include "ext/standard/fsock.h"
25+
#include "libavifinfo/avifinfo.h"
2526
#if HAVE_UNISTD_H
2627
#include <unistd.h>
2728
#endif
@@ -1155,95 +1156,76 @@ static struct gfxinfo *php_handle_webp(php_stream * stream)
11551156
}
11561157
/* }}} */
11571158

1158-
/* {{{ php_handle_avif
1159-
* There's no simple way to get this information - so, for now, this is unsupported.
1160-
* Simply return 0 for everything.
1161-
*/
1162-
static struct gfxinfo *php_handle_avif(php_stream * stream) {
1163-
return ecalloc(1, sizeof(struct gfxinfo));
1164-
}
1165-
/* }}} */
1166-
1167-
/* {{{ php_ntohl
1168-
* Convert a big-endian network uint32 to host order -
1169-
* which may be either little-endian or big-endian.
1170-
* Thanks to Rob Pike via Joe Drago:
1171-
* https://commandcenter.blogspot.nl/2012/04/byte-order-fallacy.html
1172-
*/
1173-
static uint32_t php_ntohl(uint32_t val) {
1174-
uint8_t data[4];
1175-
1176-
memcpy(&data, &val, sizeof(data));
1177-
return ((uint32_t)data[3] << 0) |
1178-
((uint32_t)data[2] << 8) |
1179-
((uint32_t)data[1] << 16) |
1180-
((uint32_t)data[0] << 24);
1181-
}
1182-
/* }}} */
1183-
1184-
/* {{{ php_is_image_avif
1185-
* detect whether an image is of type AVIF
1186-
*
1187-
* An AVIF image will start off a header "box".
1188-
* This starts with with a four-byte integer containing the number of bytes in the filetype box.
1189-
* This must be followed by the string "ftyp".
1190-
* Next comes a four-byte string indicating the "major brand".
1191-
* If that's "avif" or "avis", this is an AVIF image.
1192-
* Next, there's a four-byte "minor version" field, which we can ignore.
1193-
* Next comes an array of four-byte strings containing "compatible brands".
1194-
* These extend to the end of the box.
1195-
* If any of the compatible brands is "avif" or "avis", then this is an AVIF image.
1196-
* Otherwise, well, it's not.
1197-
* For more, see https://mpeg.chiariglione.org/standards/mpeg-4/iso-base-media-file-format/text-isoiec-14496-12-5th-edition
1198-
*/
1199-
bool php_is_image_avif(php_stream * stream) {
1200-
uint32_t header_size_reversed, header_size, i;
1201-
char box_type[4], brand[4];
1159+
/* {{{ User struct and stream read/skip implementations for libavifinfo API */
1160+
struct php_avif_stream {
1161+
php_stream* stream;
1162+
uint8_t buffer[AVIFINFO_MAX_NUM_READ_BYTES];
1163+
};
12021164

1203-
ZEND_ASSERT(stream != NULL);
1165+
static const uint8_t* php_avif_stream_read(void* stream, size_t num_bytes) {
1166+
struct php_avif_stream* avif_stream = (struct php_avif_stream*)stream;
12041167

1205-
if (php_stream_read(stream, (char *) &header_size_reversed, 4) != 4) {
1206-
return 0;
1168+
if (avif_stream == NULL || avif_stream->stream == NULL) {
1169+
return NULL;
12071170
}
1208-
1209-
header_size = php_ntohl(header_size_reversed);
1210-
1211-
/* If the box type isn't "ftyp", it can't be an AVIF image. */
1212-
if (php_stream_read(stream, box_type, 4) != 4) {
1213-
return 0;
1171+
if (php_stream_read(avif_stream->stream, (char*)avif_stream->buffer, num_bytes) != num_bytes) {
1172+
avif_stream->stream = NULL; /* fail further calls */
1173+
return NULL;
12141174
}
1175+
return avif_stream->buffer;
1176+
}
12151177

1216-
if (memcmp(box_type, "ftyp", 4)) {
1217-
return 0;
1218-
}
1219-
1220-
/* If the major brand is "avif" or "avis", it's an AVIF image. */
1221-
if (php_stream_read(stream, brand, 4) != 4) {
1222-
return 0;
1223-
}
1178+
static void php_avif_stream_skip(void* stream, size_t num_bytes) {
1179+
struct php_avif_stream* avif_stream = (struct php_avif_stream*)stream;
12241180

1225-
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1226-
return 1;
1181+
if (avif_stream == NULL || avif_stream->stream == NULL) {
1182+
return;
12271183
}
1228-
1229-
/* Skip the next four bytes, which are the "minor version". */
1230-
if (php_stream_read(stream, brand, 4) != 4) {
1231-
return 0;
1184+
if (php_stream_seek(avif_stream->stream, num_bytes, SEEK_CUR)) {
1185+
avif_stream->stream = NULL; /* fail further calls */
12321186
}
1187+
}
1188+
/* }}} */
12331189

1234-
/* Look for "avif" or "avis" in any member of compatible_brands[], to the end of the header.
1235-
Note we've already read four groups of four bytes. */
1190+
/* {{{ php_handle_avif
1191+
* Parse AVIF features
1192+
*
1193+
* The stream must be positioned at the beginning of a box, so it does not
1194+
* matter whether the "ftyp" box was already read by php_is_image_avif() or not.
1195+
* It will read bytes from the stream until features are found or the file is
1196+
* declared as invalid. Around 450 bytes are usually enough.
1197+
* Transforms such as mirror and rotation are not applied on width and height.
1198+
*/
1199+
static struct gfxinfo *php_handle_avif(php_stream * stream) {
1200+
struct gfxinfo* result = NULL;
1201+
AvifInfoFeatures features;
1202+
struct php_avif_stream avif_stream;
1203+
avif_stream.stream = stream;
1204+
1205+
if (AvifInfoGetFeaturesStream(&avif_stream, php_avif_stream_read, php_avif_stream_skip, &features) == kAvifInfoOk) {
1206+
result = (struct gfxinfo*)ecalloc(1, sizeof(struct gfxinfo));
1207+
result->width = features.width;
1208+
result->height = features.height;
1209+
result->bits = features.bit_depth;
1210+
result->channels = features.num_channels;
1211+
}
1212+
return result;
1213+
}
1214+
/* }}} */
12361215

1237-
for (i = 16; i < header_size; i += 4) {
1238-
if (php_stream_read(stream, brand, 4) != 4) {
1239-
return 0;
1240-
}
1216+
/* {{{ php_is_image_avif
1217+
* Detect whether an image is of type AVIF
1218+
*
1219+
* Only the first "ftyp" box is read.
1220+
* For a valid file, 12 bytes are usually read, but more might be necessary.
1221+
*/
1222+
bool php_is_image_avif(php_stream* stream) {
1223+
struct php_avif_stream avif_stream;
1224+
avif_stream.stream = stream;
12411225

1242-
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1243-
return 1;
1244-
}
1226+
if (AvifInfoIdentifyStream(&avif_stream, php_avif_stream_read, php_avif_stream_skip) == kAvifInfoOk) {
1227+
return 1;
12451228
}
1246-
12471229
return 0;
12481230
}
12491231
/* }}} */

ext/standard/libavifinfo/LICENSE

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2021, Alliance for Open Media. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions
5+
are met:
6+
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in
12+
the documentation and/or other materials provided with the
13+
distribution.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18+
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19+
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25+
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
POSSIBILITY OF SUCH DAMAGE.

ext/standard/libavifinfo/PATENTS

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
Alliance for Open Media Patent License 1.0
2+
3+
1. License Terms.
4+
5+
1.1. Patent License. Subject to the terms and conditions of this License, each
6+
Licensor, on behalf of itself and successors in interest and assigns,
7+
grants Licensee a non-sublicensable, perpetual, worldwide, non-exclusive,
8+
no-charge, royalty-free, irrevocable (except as expressly stated in this
9+
License) patent license to its Necessary Claims to make, use, sell, offer
10+
for sale, import or distribute any Implementation.
11+
12+
1.2. Conditions.
13+
14+
1.2.1. Availability. As a condition to the grant of rights to Licensee to make,
15+
sell, offer for sale, import or distribute an Implementation under
16+
Section 1.1, Licensee must make its Necessary Claims available under
17+
this License, and must reproduce this License with any Implementation
18+
as follows:
19+
20+
a. For distribution in source code, by including this License in the
21+
root directory of the source code with its Implementation.
22+
23+
b. For distribution in any other form (including binary, object form,
24+
and/or hardware description code (e.g., HDL, RTL, Gate Level Netlist,
25+
GDSII, etc.)), by including this License in the documentation, legal
26+
notices, and/or other written materials provided with the
27+
Implementation.
28+
29+
1.2.2. Additional Conditions. This license is directly from Licensor to
30+
Licensee. Licensee acknowledges as a condition of benefiting from it
31+
that no rights from Licensor are received from suppliers, distributors,
32+
or otherwise in connection with this License.
33+
34+
1.3. Defensive Termination. If any Licensee, its Affiliates, or its agents
35+
initiates patent litigation or files, maintains, or voluntarily
36+
participates in a lawsuit against another entity or any person asserting
37+
that any Implementation infringes Necessary Claims, any patent licenses
38+
granted under this License directly to the Licensee are immediately
39+
terminated as of the date of the initiation of action unless 1) that suit
40+
was in response to a corresponding suit regarding an Implementation first
41+
brought against an initiating entity, or 2) that suit was brought to
42+
enforce the terms of this License (including intervention in a third-party
43+
action by a Licensee).
44+
45+
1.4. Disclaimers. The Reference Implementation and Specification are provided
46+
"AS IS" and without warranty. The entire risk as to implementing or
47+
otherwise using the Reference Implementation or Specification is assumed
48+
by the implementer and user. Licensor expressly disclaims any warranties
49+
(express, implied, or otherwise), including implied warranties of
50+
merchantability, non-infringement, fitness for a particular purpose, or
51+
title, related to the material. IN NO EVENT WILL LICENSOR BE LIABLE TO
52+
ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL,
53+
INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF
54+
ACTION OF ANY KIND WITH RESPECT TO THIS LICENSE, WHETHER BASED ON BREACH
55+
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR
56+
NOT THE OTHER PARTRY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57+
58+
2. Definitions.
59+
60+
2.1. Affiliate. "Affiliate" means an entity that directly or indirectly
61+
Controls, is Controlled by, or is under common Control of that party.
62+
63+
2.2. Control. "Control" means direct or indirect control of more than 50% of
64+
the voting power to elect directors of that corporation, or for any other
65+
entity, the power to direct management of such entity.
66+
67+
2.3. Decoder. "Decoder" means any decoder that conforms fully with all
68+
non-optional portions of the Specification.
69+
70+
2.4. Encoder. "Encoder" means any encoder that produces a bitstream that can
71+
be decoded by a Decoder only to the extent it produces such a bitstream.
72+
73+
2.5. Final Deliverable. "Final Deliverable" means the final version of a
74+
deliverable approved by the Alliance for Open Media as a Final
75+
Deliverable.
76+
77+
2.6. Implementation. "Implementation" means any implementation, including the
78+
Reference Implementation, that is an Encoder and/or a Decoder. An
79+
Implementation also includes components of an Implementation only to the
80+
extent they are used as part of an Implementation.
81+
82+
2.7. License. "License" means this license.
83+
84+
2.8. Licensee. "Licensee" means any person or entity who exercises patent
85+
rights granted under this License.
86+
87+
2.9. Licensor. "Licensor" means (i) any Licensee that makes, sells, offers
88+
for sale, imports or distributes any Implementation, or (ii) a person
89+
or entity that has a licensing obligation to the Implementation as a
90+
result of its membership and/or participation in the Alliance for Open
91+
Media working group that developed the Specification.
92+
93+
2.10. Necessary Claims. "Necessary Claims" means all claims of patents or
94+
patent applications, (a) that currently or at any time in the future,
95+
are owned or controlled by the Licensor, and (b) (i) would be an
96+
Essential Claim as defined by the W3C Policy as of February 5, 2004
97+
(https://www.w3.org/Consortium/Patent-Policy-20040205/#def-essential)
98+
as if the Specification was a W3C Recommendation; or (ii) are infringed
99+
by the Reference Implementation.
100+
101+
2.11. Reference Implementation. "Reference Implementation" means an Encoder
102+
and/or Decoder released by the Alliance for Open Media as a Final
103+
Deliverable.
104+
105+
2.12. Specification. "Specification" means the specification designated by
106+
the Alliance for Open Media as a Final Deliverable for which this
107+
License was issued.

ext/standard/libavifinfo/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# AVIF-info
2+
3+
There is no compact, reliable way to determine the size of an AVIF image. A
4+
standalone C snippet called
5+
[libavifinfo](https://aomedia.googlesource.com/libavifinfo) was created to
6+
partially parse an AVIF payload and to extract the width, height, bit depth and
7+
channel count without depending on the full libavif library.
8+
9+
`avifinfo.h`, `avifinfo.c`, `LICENSE` and `PATENTS` were copied verbatim from: \
10+
https://aomedia.googlesource.com/libavifinfo/+/96f34d945ac7dac229feddfa94dbae66e202b838 \
11+
They can easily be kept up-to-date the same way.

0 commit comments

Comments
 (0)